ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
SerializedExecutionDelegator wraps another executor so that submitted tasks run one at a time in submission order; its #post requires the task as a block. If you call post without a block (for example passing a Proc or Method as a positional argument), it raises ArgumentError 'no block given' before anything is queued.
Source
Thrown at lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb:23
module Concurrent
# A wrapper/delegator for any `ExecutorService` that
# guarantees serialized execution of tasks.
#
# @see [SimpleDelegator](http://www.ruby-doc.org/stdlib-2.1.2/libdoc/delegate/rdoc/SimpleDelegator.html)
# @see Concurrent::SerializedExecution
class SerializedExecutionDelegator < SimpleDelegator
include SerialExecutorService
def initialize(executor)
@executor = executor
@serializer = SerializedExecution.new
super(executor)
end
# @!macro executor_service_method_post
def post(*args, &task)
raise ArgumentError.new('no block given') unless block_given?
return false unless running?
@serializer.post(@executor, *args, &task)
end
end
end
View on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass the task as a block: delegator.post { do_work }
- If the task is a Proc/Method object, splat it as a block: delegator.post(&task)
- In wrapper methods, forward the block explicitly: def enqueue(&task) delegator.post(&task) end
Example fix
# before
task = ->(arg) { process(arg) }
delegator.post(task)
# after
task = ->(arg) { process(arg) }
delegator.post(&task) Defensive patterns
Strategy: validation
Validate before calling
def enqueue(serializer, &task) raise ArgumentError, 'task block required' unless task serializer.post(&task) end
Type guard
->(obj) { obj.respond_to?(:call) } # then post(&obj) Try / catch
begin
delegator.post(&task)
rescue ArgumentError => e
raise ArgumentError, "enqueue rejected: #{e.message}" if /no block/.match?(e.message)
raise
end Prevention
- Never pass callables positionally to executor APIs — convert with & at the call site
- In wrapper/forwarder methods always declare (&task) explicitly so blocks survive the hop
- Add a unit test that posts a stored callable through every wrapper you own
When it happens
Trigger: delegator.post(task) where task is a Proc/Method instead of delegator.post(&task); forwarding a block through a wrapper method that forgets the &; calling post on an executor obtained from SerializedExecutionDelegator.new(executor) with the task in a variable.
Common situations: Wrapping a custom or global executor for serialized (one-at-a-time) execution and reusing callable objects built elsewhere; refactoring code that stored lambdas in variables; test doubles that call post with positional callables.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/ea2b06c8ca388cfb.
Report an issue: GitHub.