ruby-concurrency/concurrent-ruby · error · ArgumentError

no block given

Error message

no block given

What it means

On JRuby, thread pools (ThreadPoolExecutor, FixedThreadPool, SingleThreadExecutor, CachedThreadPool) are backed by JavaExecutorService, whose post wraps the block in a Job and submits it to a java.util.concurrent executor. It raises ArgumentError('no block given') when post is called without a block.

Source

Thrown at lib/concurrent-ruby/concurrent/executor/java_executor_service.rb:22

  require 'concurrent/errors'
  require 'concurrent/executor/abstract_executor_service'

  module Concurrent

    # @!macro abstract_executor_service_public_api
    # @!visibility private
    class JavaExecutorService < AbstractExecutorService
      java_import 'java.lang.Runnable'

      FALLBACK_POLICY_CLASSES = {
        abort:       java.util.concurrent.ThreadPoolExecutor::AbortPolicy,
        discard:     java.util.concurrent.ThreadPoolExecutor::DiscardPolicy,
        caller_runs: java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy
      }.freeze
      private_constant :FALLBACK_POLICY_CLASSES

      def post(*args, &task)
        raise ArgumentError.new('no block given') unless block_given?
        return fallback_action(*args, &task).call unless running?
        @executor.submit Job.new(args, task)
        true
      rescue Java::JavaUtilConcurrent::RejectedExecutionException
        raise RejectedExecutionError
      end

      def wait_for_termination(timeout = nil)
        if timeout.nil?
          ok = @executor.awaitTermination(60, java.util.concurrent.TimeUnit::SECONDS) until ok
          true
        else
          @executor.awaitTermination(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
        end
      end

      def shutdown
        synchronize do

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Always call with a block: pool.post { do_work }
  2. Forward blocks explicitly when wrapping executors: 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 JRuby; passing a callable positionally instead of with &; a dispatcher that forwards *args but not &block.

Common situations: Same missing-block bugs as MRI, but the backtrace points at java_executor_service.rb because concurrent-ruby selects the implementation per interpreter; teams debugging on MRI cannot reproduce the identical stack on JRuby and vice versa.

Related errors


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