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 privateView on GitHub (pinned to 0b88d5ff75)
Solutions
- Drop synchronous: true if a bounded queue is wanted
- Set max_queue: 0 (or omit it) for synchronous hand-off
- 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
- Model max_queue and synchronous as mutually exclusive in config schemas
- Deep-merged YAML presets are a common source: audit merged results once at boot
- Prefer bounded queue OR synchronous hand-off, never both
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
- number of threads must be greater than zero
- `synchronous` cannot be set unless `max_queue` is 0
- `max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}
- `max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE
- `min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/4397887acb6f726b.
Report an issue: GitHub.