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

  1. Guard the degenerate case before constructing: skip barrier setup when parties < 1
  2. Enforce a minimum at config load: parties = [cfg.fetch('parties', 1), 1].max
  3. 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

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


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