ruby-concurrency/concurrent-ruby · error · ArgumentError

number of threads must be greater than zero

Error message

number of threads must be greater than zero

What it means

Concurrent::FixedThreadPool.new(num_threads) pins min_threads = max_threads = num_threads. It raises ArgumentError when num_threads.to_i < 1. Because the value is coerced with to_i, nil (nil.to_i == 0), non-numeric strings ('abc'.to_i == 0), 0, and negatives all fail the check.

Source

Thrown at lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb:214

  #   The API and behavior of this class are based on Java's `FixedThreadPool`
  #
  # @!macro thread_pool_options
  class FixedThreadPool < ThreadPoolExecutor

    # @!macro fixed_thread_pool_method_initialize
    #
    #   Create a new thread pool.
    #
    #   @param [Integer] num_threads the number of threads to allocate
    #   @param [Hash] opts the options defining pool behavior.
    #   @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy
    #
    #   @raise [ArgumentError] if `num_threads` is less than or equal to zero
    #   @raise [ArgumentError] if `fallback_policy` is not a known policy
    #
    #   @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int-
    def initialize(num_threads, opts = {})
      raise ArgumentError.new('number of threads must be greater than zero') if num_threads.to_i < 1
      defaults  = { max_queue:   DEFAULT_MAX_QUEUE_SIZE,
                    idletime:    DEFAULT_THREAD_IDLETIMEOUT }
      overrides = { min_threads: num_threads,
                    max_threads: num_threads }
      super(defaults.merge(opts).merge(overrides))
    end
  end
end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Strictly convert and validate before constructing: size = Integer(ENV.fetch('POOL_SIZE', 5)); raise ArgumentError if size < 1
  2. Default missing config to a sane positive value instead of nil or 0
  3. Treat a 'disabled' intent as not creating the pool at all rather than passing 0
  4. Use Integer() (raises on junk) instead of relying on to_i coercion

Example fix

# before
pool = Concurrent::FixedThreadPool.new(ENV['POOL_SIZE'])
# after
size = Integer(ENV.fetch('POOL_SIZE', 5))
raise ArgumentError, "POOL_SIZE must be >= 1, got #{size}" if size < 1
pool = Concurrent::FixedThreadPool.new(size)
Defensive patterns

Strategy: validation

Validate before calling

size = Integer(env_value.nil? || env_value.empty? ? 5 : env_value)
raise ArgumentError, "pool size must be >= 1, got #{size}" if size < 1
pool = Concurrent::FixedThreadPool.new(size)

Type guard

def valid_fixed_pool_size?(v)
  v.is_a?(Numeric) && v.to_i >= 1
end

Try / catch

begin
  Concurrent::FixedThreadPool.new(size)
rescue ArgumentError => e
  raise unless e.message == 'number of threads must be greater than zero'
  Concurrent::FixedThreadPool.new(DEFAULT_POOL_SIZE)
end

Prevention

When it happens

Trigger: Concurrent::FixedThreadPool.new(0) or .new(-3); .new(nil); .new(ENV['POOL_SIZE']) where the variable is unset or non-numeric; sizing arithmetic that yields zero (count * 0).

Common situations: Pool size read from ENV or YAML that is missing in the deployed environment; 0 used to mean 'disabled'; string sizes from CLI flags ('4' works, '' or 'four' raises); arithmetic on unknown core counts producing zero or negative values.

Related errors


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