ruby-concurrency/concurrent-ruby · error · ArgumentError

`synchronous` cannot be set unless `max_queue` is 0

Error message

`synchronous` cannot be set unless `max_queue` is 0

What it means

On MRI, ThreadPoolExecutor with synchronous: true posts tasks by direct hand-off to a worker (no buffering, mirroring a synchronous queue). Since there is no queue to size, ns_initialize raises ArgumentError when synchronous is true while max_queue > 0. The default max_queue is 0, so the error requires both options together.

Source

Thrown at lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:154

    end

    # @!macro thread_pool_executor_method_prune_pool
    def prune_pool
      deprecated "#prune_pool has no effect and will be removed in next the release, see https://github.com/ruby-concurrency/concurrent-ruby/pull/1082."
    end

    private

    # @!visibility private
    def ns_initialize(opts)
      @min_length      = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
      @max_length      = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
      @idletime        = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
      @max_queue       = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
      @synchronous     = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS)
      @fallback_policy = opts.fetch(:fallback_policy, :abort)

      raise ArgumentError.new("`synchronous` cannot be set unless `max_queue` is 0") if @synchronous && @max_queue > 0
      raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
      raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @max_length < DEFAULT_MIN_POOL_SIZE
      raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if @max_length > DEFAULT_MAX_POOL_SIZE
      raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE
      raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length

      @pool                 = [] # all workers
      @ready                = [] # used as a stash (most idle worker is at the start)
      @queue                = [] # used as queue
      # @ready or @queue is empty at all times
      @scheduled_task_count = 0
      @completed_task_count = 0
      @largest_length       = 0
      @workers_counter      = 0
      @ruby_pid             = $$ # detects if Ruby has forked
    end

    # @!visibility private

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Drop synchronous: true if a bounded queue is wanted
  2. Set max_queue: 0 (or omit it) for synchronous hand-off
  3. Validate the pair before construction and keep the two options mutually exclusive in config schemas

Example fix

# before
Concurrent::ThreadPoolExecutor.new(max_queue: 100, synchronous: true)
# after
Concurrent::ThreadPoolExecutor.new(max_queue: 100)
Defensive patterns

Strategy: validation

Validate before calling

opts = cfg.slice(:min_threads, :max_threads, :idletime, :synchronous, :fallback_policy)
raise ArgumentError, 'max_queue must be 0 when synchronous is true' if opts[:synchronous] && opts[:max_queue].to_i > 0
Concurrent::ThreadPoolExecutor.new(**opts)

Try / catch

begin
  Concurrent::ThreadPoolExecutor.new(**pool_opts)
rescue ArgumentError => e
  raise unless e.message.include?('`synchronous` cannot be set unless `max_queue` is 0')
  pool_opts = pool_opts.except(:max_queue)
  retry
end

Prevention

When it happens

Trigger: Concurrent::ThreadPoolExecutor.new(max_queue: 100, synchronous: true) on MRI; adding synchronous: true to an existing bounded-queue config; config deep-merge combining a max_queue layer with a synchronous layer.

Common situations: Wanting bounded-queue backpressure AND direct hand-off at once, which is contradictory; shared config presets that mix incompatible options; upgrading pool settings without removing the now-conflicting max_queue.

Related errors


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