ruby-concurrency/concurrent-ruby · error · ArgumentError

no block given

Error message

no block given

What it means

On MRI, thread pools (RubyThreadPoolExecutor and friends) are backed by RubyExecutorService, whose post schedules the block via ns_execute or routes it to the fallback action when not running. It raises ArgumentError('no block given') when post is called without a block, before any state check.

Source

Thrown at lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb:18

require 'concurrent/executor/abstract_executor_service'
require 'concurrent/atomic/event'

module Concurrent

  # @!macro abstract_executor_service_public_api
  # @!visibility private
  class RubyExecutorService < AbstractExecutorService
    safe_initialization!

    def initialize(*args, &block)
      super
      @StopEvent    = Event.new
      @StoppedEvent = Event.new
    end

    def post(*args, &task)
      raise ArgumentError.new('no block given') unless block_given?
      deferred_action = synchronize {
        if running?
          ns_execute(*args, &task)
        else
          fallback_action(*args, &task)
        end
      }
      if deferred_action
        deferred_action.call
      else
        true
      end
    end

    def shutdown
      synchronize do
        break unless running?
        stop_event.set

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Always call with a block: pool.post { do_work }
  2. Forward blocks explicitly when wrapping: def enqueue(&task); pool.post(&task); end
  3. Pass stored callables with &: pool.post(&task)

Example fix

# before
def enqueue(task)
  pool.post(task)
end
# after
def enqueue(&task)
  pool.post(&task)
end
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'task must respond to #call' unless task.respond_to?(:call)
pool.post(&task)

Try / catch

begin
  pool.post { do_work }
rescue ArgumentError => e
  raise unless e.message == 'no block given'
  raise ArgumentError, 'post requires a block'
end

Prevention

When it happens

Trigger: pool.post with no block on MRI; executor.post(arg) passing only arguments; a generic dispatcher forwarding *args but not &block; passing a proc positionally.

Common situations: Wrapper/gem code that enqueues optional tasks; refactoring a call chain and losing the block; test stubs replacing executors where the block was optional.

Related errors


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