{"record":{"id":"247d04164e97ca6b","repo":"ruby-concurrency/concurrent-ruby","slug":"max-threads-cannot-be-less-than-default-min-po-247d04","errorCode":null,"errorMessage":"`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}","messagePattern":"`max_threads` cannot be less than #(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb","lineNumber":156,"sourceCode":"    # @!macro thread_pool_executor_method_prune_pool\n    def prune_pool\n      deprecated \"#prune_pool has no effect and will be removed in next the release, see https://github.com/ruby-concurrency/concurrent-ruby/pull/1082.\"\n    end\n\n    private\n\n    # @!visibility private\n    def ns_initialize(opts)\n      @min_length      = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i\n      @max_length      = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i\n      @idletime        = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i\n      @max_queue       = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i\n      @synchronous     = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS)\n      @fallback_policy = opts.fetch(:fallback_policy, :abort)\n\n      raise ArgumentError.new(\"`synchronous` cannot be set unless `max_queue` is 0\") if @synchronous && @max_queue > 0\n      raise ArgumentError.new(\"#{@fallback_policy} is not a valid fallback policy\") unless FALLBACK_POLICIES.include?(@fallback_policy)\n      raise ArgumentError.new(\"`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}\") if @max_length < DEFAULT_MIN_POOL_SIZE\n      raise ArgumentError.new(\"`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}\") if @max_length > DEFAULT_MAX_POOL_SIZE\n      raise ArgumentError.new(\"`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}\") if @min_length < DEFAULT_MIN_POOL_SIZE\n      raise ArgumentError.new(\"`min_threads` cannot be more than `max_threads`\") if min_length > max_length\n\n      @pool                 = [] # all workers\n      @ready                = [] # used as a stash (most idle worker is at the start)\n      @queue                = [] # used as queue\n      # @ready or @queue is empty at all times\n      @scheduled_task_count = 0\n      @completed_task_count = 0\n      @largest_length       = 0\n      @workers_counter      = 0\n      @ruby_pid             = $$ # detects if Ruby has forked\n    end\n\n    # @!visibility private\n    def ns_limited_queue?\n      @max_queue != 0","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb#L138-L174","documentation":"On MRI, ThreadPoolExecutor coerces max_threads with to_i and raises ArgumentError when it is below DEFAULT_MIN_POOL_SIZE (0), i.e. when negative. This guards the Ruby worker-spawn logic from impossible sizes before any thread is created.","triggerScenarios":"max_threads: -5 on MRI; string '-2' coerced by to_i; sizing formulas (cores - overhead) that go negative when CPU detection fails or reports oddly in containers.","commonSituations":"Container/cgroup CPU quota weirdness; negative sentinels meant as 'unbounded'; config sign typos; CLI flag parsing producing negative numbers.","solutions":["Validate before constructing: raise if Integer(opts[:max_threads]) < 0","Clamp computed sizes: [computed, 0].max","Use a positive fallback default when core detection is unavailable"],"exampleFix":"# before\nConcurrent::ThreadPoolExecutor.new(max_threads: detected_cores - 4)\n# after\nmax_threads = [detected_cores - 4, 1].max\nConcurrent::ThreadPoolExecutor.new(max_threads: max_threads)","handlingStrategy":"validation","validationCode":"max_threads = Integer(cfg[:max_threads])\nraise ArgumentError, \"max_threads must be >= 0, got #{max_threads}\" if max_threads.negative?\nConcurrent::ThreadPoolExecutor.new(max_threads: max_threads)","typeGuard":"def valid_thread_count?(v, min = 0, max = 2_147_483_647)\n  v.is_a?(Numeric) && v.to_i.between?(min, max)\nend","tryCatchPattern":"begin\n  Concurrent::ThreadPoolExecutor.new(max_threads: n)\nrescue ArgumentError => e\n  raise unless e.message.start_with?('`max_threads` cannot be less than')\n  Concurrent::ThreadPoolExecutor.new(max_threads: [n, 0].max)\nend","preventionTips":["Clamp CPU-derived sizing in containers: [computed, 0].max","Parse counts with Integer() so junk fails at the boundary","Default to a sane positive size when detection fails"],"tags":["ruby","concurrency","thread-pool","argument-error","configuration"],"backgroundTag":"invalid-thread-pool-configuration","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}