{"record":{"id":"086ee16b671fd8c4","repo":"ruby-concurrency/concurrent-ruby","slug":"value-cannot-be-negative","errorCode":null,"errorMessage":"#{value} cannot be negative","messagePattern":"#(.+?) cannot be negative","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/utility/native_integer.rb","lineNumber":39,"sourceCode":"        value\n      end\n\n      def ensure_integer(value)\n        unless value.is_a?(Integer)\n          raise ArgumentError.new(\"#{value} is not an Integer\")\n        end\n        value\n      end\n\n      def ensure_integer_and_bounds(value)\n        ensure_integer value\n        ensure_upper_bound value\n        ensure_lower_bound value\n      end\n\n      def ensure_positive(value)\n        if value < 0\n          raise ArgumentError.new(\"#{value} cannot be negative\")\n        end\n        value\n      end\n\n      def ensure_positive_and_no_zero(value)\n        if value < 1\n          raise ArgumentError.new(\"#{value} cannot be negative or zero\")\n        end\n        value\n      end\n\n      extend self\n    end\n  end\nend\n","sourceCodeStart":21,"sourceCodeEnd":55,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/utility/native_integer.rb#L21-L55","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Clamp computed counts: Concurrent::CountDownLatch.new([count, 0].max) when zero is a valid degenerate case","Fail loudly at the source when a negative count indicates a logic bug: raise ArgumentError if count < 0","Guard per-call acquire/release amounts: raise if n < 0 before calling the primitive"],"exampleFix":"# before (empty list -> -1 -> ArgumentError)\nlatch = Concurrent::CountDownLatch.new(items.size - 1)\n\n# after\ncount = items.size - 1\nlatch = Concurrent::CountDownLatch.new(count.clamp(0..))\n# or fail explicitly:  raise ArgumentError, 'negative count' if count < 0","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"count #{count} must be >= 0\" if count < 0\nConcurrent::CountDownLatch.new(count)","typeGuard":"def non_negative_int?(v)\n  v.is_a?(Integer) && v >= 0\nend","tryCatchPattern":"begin\n  latch = Concurrent::CountDownLatch.new(count)\nrescue ArgumentError\n  latch = Concurrent::CountDownLatch.new(0) # degenerate but valid; log the anomaly\nend","preventionTips":["Clamp computed counts ([n, 0].max) when zero is a valid degenerate case","Never derive latch counts from size - 1 without guarding the empty case","Validate acquire/release amounts (>= 0) before calling semaphore methods"],"tags":["ruby","concurrency","semaphore","countdown-latch","argument-error","validation"],"backgroundTag":"invalid-count-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}