ruby-concurrency/concurrent-ruby · error · ArgumentError

#{value} cannot be negative

Error message

#{value} cannot be negative

What it means

ensure_positive rejects negative values with ArgumentError('x cannot be negative'); zero is allowed. It guards the counts of CountDownLatch (count) and Semaphore (permits, and the per-call amounts in acquire/release/drain-style methods). These primitives treat a negative count as a caller bug, since waiting threads could never be released.

Source

Thrown at lib/concurrent-ruby/concurrent/utility/native_integer.rb:39

        value
      end

      def ensure_integer(value)
        unless value.is_a?(Integer)
          raise ArgumentError.new("#{value} is not an Integer")
        end
        value
      end

      def ensure_integer_and_bounds(value)
        ensure_integer value
        ensure_upper_bound value
        ensure_lower_bound value
      end

      def ensure_positive(value)
        if value < 0
          raise ArgumentError.new("#{value} cannot be negative")
        end
        value
      end

      def ensure_positive_and_no_zero(value)
        if value < 1
          raise ArgumentError.new("#{value} cannot be negative or zero")
        end
        value
      end

      extend self
    end
  end
end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Clamp computed counts: Concurrent::CountDownLatch.new([count, 0].max) when zero is a valid degenerate case
  2. Fail loudly at the source when a negative count indicates a logic bug: raise ArgumentError if count < 0
  3. Guard per-call acquire/release amounts: raise if n < 0 before calling the primitive

Example fix

# before (empty list -> -1 -> ArgumentError)
latch = Concurrent::CountDownLatch.new(items.size - 1)

# after
count = items.size - 1
latch = Concurrent::CountDownLatch.new(count.clamp(0..))
# or fail explicitly:  raise ArgumentError, 'negative count' if count < 0
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, "count #{count} must be >= 0" if count < 0
Concurrent::CountDownLatch.new(count)

Type guard

def non_negative_int?(v)
  v.is_a?(Integer) && v >= 0
end

Try / catch

begin
  latch = Concurrent::CountDownLatch.new(count)
rescue ArgumentError
  latch = Concurrent::CountDownLatch.new(0) # degenerate but valid; log the anomaly
end

Prevention

When it happens

Trigger: Concurrent::CountDownLatch.new(-1); Concurrent::Semaphore.new(-5); semaphore.acquire(-1) or semaphore.release(negative) with a computed amount; latch counts derived as items.size - completed.size going negative under a race or off-by-one.

Common situations: size - 1 computations on empty collections (e.g. CountDownLatch.new(list.size - 1) when list is empty); counts read from telemetry that can be negative; retry logic passing negative permit deltas.

Related errors


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