ruby-concurrency/concurrent-ruby · error · ArgumentError
#{value} cannot be negative or zero
Error message
#{value} cannot be negative or zero What it means
ensure_positive_and_no_zero requires values >= 1, raising ArgumentError('x cannot be negative or zero'). Its main caller is CyclicBarrier's constructor: a barrier needs at least one party for wait semantics to make sense, unlike Semaphore/CountDownLatch which accept zero. So CyclicBarrier.new(0) is explicitly rejected.
Source
Thrown at lib/concurrent-ruby/concurrent/utility/native_integer.rb:46
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
- Guard the degenerate case before constructing: skip barrier setup when parties < 1
- Enforce a minimum at config load: parties = [cfg.fetch('parties', 1), 1].max
- If a no-op barrier is needed, wrap: barrier = parties > 1 ? CyclicBarrier.new(parties) : nil and nil-check at wait sites
Example fix
# before (workers empty -> CyclicBarrier.new(0) -> ArgumentError) barrier = Concurrent::CyclicBarrier.new(workers.size) # after barrier = workers.size >= 1 ? Concurrent::CyclicBarrier.new(workers.size) : nil barrier&.wait
Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, 'barrier needs at least 1 party' unless parties >= 1 barrier = Concurrent::CyclicBarrier.new(parties)
Type guard
def positive_int?(v) v.is_a?(Integer) && v >= 1 end
Prevention
- Guard parties >= 1 before constructing; skip barrier setup for empty worker sets
- Enforce a config minimum: parties = [cfg.fetch('parties', 1), 1].max
- Cover the zero-party case in specs for barrier-based fan-out code
When it happens
Trigger: Concurrent::CyclicBarrier.new(threads.size) when threads is empty; CyclicBarrier.new(0) used as a 'disabled barrier'; parties computed from config that is unset (nil -> type error) or 0.
Common situations: Sizing a barrier from a worker list or config value that can legitimately be empty; scaling configs where the minimum was assumed to be 1; test fixtures with degenerate zero-party barriers.
Related errors
- #{value} cannot be negative
- no block given
- Not all dependencies are IVars. Dependencies: #{ inputs.insp
- no block given
- number of threads must be greater than zero
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/c05a9c0d7433978a.
Report an issue: GitHub.