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

  1. Pass a capacity of at least 1 alongside the buffer type: Concurrent::Channel.new(buffer: :dropping, capacity: 5).
  2. For unbuffered semantics use no options at all, or buffer: :unbuffered (or buffer: :buffered, capacity: 0).
  3. 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

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


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/fa4bc77df02944d2. Report an issue: GitHub.