ruby-concurrency/concurrent-ruby · error · ArgumentError

size must be greater than 0

Error message

size must be greater than 0

What it means

Concurrent::Channel::Buffer::Buffered is the bounded FIFO buffer behind buffered channels. Its ns_initialize coerces size with to_i and requires the result to be positive, because a bounded queue with zero or negative capacity cannot exist; 0.5.to_i == 0, so fractional sizes also raise. Note that Channel.new(buffer: :buffered, capacity: 0) never reaches this - channel.rb maps that combination to an unbuffered buffer; direct Buffer construction has no such mapping.

Source

Thrown at lib/concurrent-ruby-edge/concurrent/channel/buffer/buffered.rb:89

        def poll
          synchronize do
            if ns_empty?
              Concurrent::NULL
            else
              buffer.shift
            end
          end
        end

        private

        # @!macro channel_buffer_initialize
        #
        # @param [Integer] size the maximum capacity of the buffer; must be
        #   greater than zero.
        # @raise [ArgumentError] when the size is zero (0) or less.
        def ns_initialize(size)
          raise ArgumentError.new('size must be greater than 0') if size.to_i <= 0
          self.capacity = size.to_i
          self.buffer = []
        end

        # @!macro channel_buffer_size_reader
        def ns_size
          buffer.size
        end

        # @!macro channel_buffer_empty_question
        def ns_empty?
          ns_size == 0
        end

        # @!macro channel_buffer_full_question
        def ns_full?
          ns_size == capacity
        end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass a positive integer size: Concurrent::Channel::Buffer::Buffered.new(10).
  2. Default and validate config-derived sizes before construction, failing loudly at startup rather than deep in a worker thread.
  3. For zero-capacity rendezvous semantics use Buffer::Unbuffered (or an unbuffered Channel).

Example fix

# before
buffer = Concurrent::Channel::Buffer::Buffered.new(ENV.fetch('QUEUE_SIZE', 0).to_i)

# after
size = ENV.fetch('QUEUE_SIZE', 1).to_i
raise ArgumentError, 'QUEUE_SIZE must be >= 1' if size < 1
buffer = Concurrent::Channel::Buffer::Buffered.new(size)
Defensive patterns

Strategy: validation

Validate before calling

size = Integer(size_param)
raise ArgumentError, 'buffer size must be >= 1' if size < 1
buffer = Concurrent::Channel::Buffer::Buffered.new(size)

Try / catch

begin
  buffer = Concurrent::Channel::Buffer::Buffered.new(size)
rescue ArgumentError => e
  raise ConfigError, "bad buffer size #{size.inspect}: #{e.message}"
end

Prevention

When it happens

Trigger: Concurrent::Channel::Buffer::Buffered.new(0), .new(-1), or .new(0.5); also building channels by injecting a raw buffer (Channel.new(some_buffer)) where the buffer was constructed with a config-derived size that is 0 or nil.

Common situations: Capacity from ENV/config whose default converts to 0 when missing (ENV.fetch('QUEUE_SIZE', 0).to_i); reusing a capacity constant that was set to 0 to mean 'unbounded'; tests constructing buffers with placeholder sizes.

Related errors


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