ruby-concurrency/concurrent-ruby · error · ArgumentError
ImmediateExecutor is not supported
Error message
ImmediateExecutor is not supported
What it means
In the concurrent-ruby-edge actor library, Actor.spawn explicitly rejects ImmediateExecutor with ArgumentError. Actors depend on messages being processed asynchronously on an executor; ImmediateExecutor runs tasks inline on the caller thread, which would break actor isolation and deadlock the mailbox dispatch, so the library refuses it up front instead of failing obscurely later.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/actor.rb:75
# @example by class and name
# Actor.spawn(AdHoc, :ping1) { -> message { message } }
#
# @example by option hash
# inc2 = Actor.spawn(class: AdHoc,
# name: 'increment by 2',
# args: [2],
# executor: Concurrent.global_io_executor) do |increment_by|
# lambda { |number| number + increment_by }
# end
# inc2.ask!(2) # => 4
#
# @param block for context_class instantiation
# @param args see {.to_spawn_options}
# @return [Reference] never the actual actor
def self.spawn(*args, &block)
options = to_spawn_options(*args)
if options[:executor] && options[:executor].is_a?(ImmediateExecutor)
raise ArgumentError, 'ImmediateExecutor is not supported'
end
if Actor.current
Core.new(options.merge(parent: Actor.current), &block).reference
else
root.ask([:spawn, options, block]).value!
end
end
# as {.spawn} but it'll block until actor is initialized or it'll raise exception on error
def self.spawn!(*args, &block)
spawn(to_spawn_options(*args).merge(initialized: future = Concurrent::Promises.resolvable_future), &block).tap { future.wait! }
end
# @overload to_spawn_options(context_class, name, *args)
# @param [AbstractContext] context_class to be spawned
# @param [String, Symbol] name of the instance, it's used to generate the
# {Core#path} of the actor
# @param args for context_class instantiationView on GitHub (pinned to 0b88d5ff75)
Solutions
- Drop the :executor option and let actors default to Concurrent.global_io_executor
- Use a real pool for actors: executor: Concurrent.new_io_executor(:my_actors) or any ThreadPoolExecutor / Concurrent.global_fast_executor
- Keep ImmediateExecutor usage scoped to non-actor code paths, and assert executor type in a spec before spawning
Example fix
# before
Concurrent::Actor.spawn(executor: Concurrent::ImmediateExecutor.new, args: [1]) { |i| i }
# ArgumentError: ImmediateExecutor is not supported
# after
Concurrent::Actor.spawn(executor: Concurrent.global_io_executor, args: [1]) { |i| i }
# or simply omit it
Concurrent::Actor.spawn(args: [1]) { |i| i } Defensive patterns
Strategy: validation
Validate before calling
if executor.is_a?(Concurrent::ImmediateExecutor) raise ArgumentError, 'ImmediateExecutor cannot back actors; use a thread pool' end Concurrent::Actor.spawn(executor: executor || Concurrent.global_io_executor, &context)
Type guard
def actor_safe_executor?(exec) !exec.is_a?(Concurrent::ImmediateExecutor) end
Try / catch
begin Concurrent::Actor.spawn(spawn_opts, &context) rescue ArgumentError => e raise unless e.message == 'ImmediateExecutor is not supported' Concurrent::Actor.spawn(spawn_opts.except(:executor), &context) # fall back to default pool end
Prevention
- Never share executor configuration between promises/tests and actor spawning
- Let actors default to global_io_executor unless profiling justifies a custom pool
- Assert executor type in a helper used by all spawn sites so misconfiguration fails early
When it happens
Trigger: Concurrent::Actor.spawn(executor: Concurrent::ImmediateExecutor.new) { ... } or the equivalent spawn! / to_spawn_options hash carrying :executor; sharing one executor configuration between promises code (where ImmediateExecutor is legal) and actor code.
Common situations: Test suites that globally substitute ImmediateExecutor for determinism and then spawn actors through the same config; performance tuning that tries to eliminate thread hops for lightweight actors; copy-pasting executor setup from promise pipelines into actor spawning.
Related errors
- number of threads must be greater than zero
- no block given
- no block given
- no block given
- #{@fallback_policy} is not a valid fallback policy
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/185c14525f5cbe84.
Report an issue: GitHub.