ruby-concurrency/concurrent-ruby · error · ArgumentError
capacity must be at least 1 for this buffer type
Error message
capacity must be at least 1 for this buffer type
What it means
Channel#initialize validates capacity for the buffered buffer types (:buffered, :dropping, :sliding). After special cases are handled (no options -> unbuffered; capacity == 0 with buffer: :buffered -> unbuffered; buffer: :unbuffered), any remaining nil or sub-1 capacity raises, because a buffered/dropping/sliding buffer needs at least one slot.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/channel.rb:66
# undocumented -- for internal use only
if opts.is_a? Buffer::Base
self.buffer = opts
return
end
capacity = opts[:capacity] || opts[:size]
buffer = opts[:buffer]
if capacity && buffer == :unbuffered
raise ArgumentError.new('unbuffered channels cannot have a capacity')
elsif capacity.nil? && buffer.nil?
self.buffer = BUFFER_TYPES[:unbuffered].new
elsif capacity == 0 && buffer == :buffered
self.buffer = BUFFER_TYPES[:unbuffered].new
elsif buffer == :unbuffered
self.buffer = BUFFER_TYPES[:unbuffered].new
elsif capacity.nil? || capacity < 1
raise ArgumentError.new('capacity must be at least 1 for this buffer type')
else
buffer ||= :buffered
self.buffer = BUFFER_TYPES[buffer].new(capacity)
end
self.validator = opts.fetch(:validator, DEFAULT_VALIDATOR)
end
def put(item)
return false unless validate(item, false, false)
do_put(item)
end
alias_method :send, :put
alias_method :<<, :put
def put!(item)
validate(item, false, true)
ok = do_put(item)View on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass a capacity of at least 1 alongside the buffer type: Concurrent::Channel.new(buffer: :dropping, capacity: 5).
- For unbuffered semantics use no options at all, or buffer: :unbuffered (or buffer: :buffered, capacity: 0).
- Coerce and validate config-derived capacities before Channel.new so misconfiguration fails at startup.
Example fix
# before ch = Concurrent::Channel.new(buffer: :dropping) # no capacity -> raises ch2 = Concurrent::Channel.new(capacity: 0) # no buffer key -> raises # after ch = Concurrent::Channel.new(buffer: :dropping, capacity: 5) ch2 = Concurrent::Channel.new # unbuffered
Defensive patterns
Strategy: validation
Validate before calling
capacity = opts[:capacity] || opts[:size]
buffer = opts[:buffer]
ok = buffer.nil? || buffer == :unbuffered ||
(capacity == 0 && buffer == :buffered) ||
(capacity && capacity >= 1)
raise ArgumentError, 'buffered channels need capacity >= 1' unless ok
ch = Concurrent::Channel.new(**opts) Try / catch
begin
ch = Concurrent::Channel.new(**opts)
rescue ArgumentError => e
raise ConfigError, "invalid channel options: #{e.message}"
end Prevention
- Remember 0 is truthy in Ruby: capacity: 0 alone does not become unbuffered - only buffer: :buffered with capacity: 0 does.
- Default config-derived capacities explicitly (ENV.fetch('N', 1).to_i) instead of letting nil/0 through.
- Cover channel option combinations with a unit test on your construction factory.
When it happens
Trigger: Concurrent::Channel.new(buffer: :dropping) with no capacity; Concurrent::Channel.new(capacity: 0) with no :buffer key (0 is truthy in Ruby, so it is not defaulted away and does not hit the == 0 special case which requires buffer: :buffered); Concurrent::Channel.new(buffer: :sliding, capacity: -2).
Common situations: Capacity read from ENV or config that defaults to 0 or is unset (ENV['N'].to_i -> 0); expecting capacity: 0 alone to mean unbuffered - only the explicit buffer: :buffered, capacity: 0 combination gets that mapping; negative capacities from arithmetic on sizes.
Related errors
- size must be greater than 0
- unbuffered channels cannot have a capacity
- no block given
- invalid action
- no block given
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/fa4bc77df02944d2.
Report an issue: GitHub.