ruby-concurrency/concurrent-ruby · error · ArgumentError
unbuffered channels cannot have a capacity
Error message
unbuffered channels cannot have a capacity
What it means
Concurrent::Channel.new builds the channel's buffer from the :buffer/:capacity options. An unbuffered channel is a pure rendezvous between sender and receiver with no storage, so a capacity is meaningless; passing both buffer: :unbuffered and a capacity (also accepted under the :size alias) is rejected with this ArgumentError.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/channel.rb:58
def_delegators :buffer,
:size, :capacity, :close, :closed?,
:blocking?, :empty?, :full?
alias_method :length, :size
alias_method :stop, :close
def initialize(opts = {})
# 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)View on GitHub (pinned to 0b88d5ff75)
Solutions
- Drop the capacity: Concurrent::Channel.new(buffer: :unbuffered), or simply Concurrent::Channel.new which is unbuffered by default.
- If you actually want bounded storage, use buffer: :buffered (or :dropping/:sliding) with a capacity of at least 1.
- Validate channel option hashes once at config-load time instead of deep inside worker code.
Example fix
# before ch = Concurrent::Channel.new(buffer: :unbuffered, capacity: 10) # after ch = Concurrent::Channel.new(buffer: :unbuffered) # or, if capacity is actually wanted: ch = Concurrent::Channel.new(buffer: :buffered, capacity: 10)
Defensive patterns
Strategy: validation
Validate before calling
def channel_opts_conflict?(opts) (opts[:capacity] || opts[:size]) && opts[:buffer] == :unbuffered end raise ArgumentError, 'drop :capacity when buffer is :unbuffered' if channel_opts_conflict?(opts) 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
- Centralize channel construction in one factory that validates the option hash.
- Remember :size is an alias of :capacity - check both keys when sanitizing config.
- Document each channel's intended buffer type at its definition site.
When it happens
Trigger: Concurrent::Channel.new(buffer: :unbuffered, capacity: 10), or via the alias Concurrent::Channel.new(size: 3, buffer: :unbuffered). Any truthy capacity together with buffer: :unbuffered triggers it.
Common situations: Switching a channel from buffered to unbuffered in a shared config without removing the capacity key; YAML/ENV-driven channel options where capacity is defaulted for every channel; copy-pasting option hashes between channel definitions.
Related errors
- capacity must be at least 1 for this buffer type
- size must be greater than 0
- 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/d89389d2dfc27ed6.
Report an issue: GitHub.