ruby/ruby · error · ThreadError

must be called with a block

Error message

must be called with a block

What it means

Thread::Mutex#synchronize acquires the lock, runs a block, and releases it in an ensure; without a block there is nothing to execute and no safe lock/unlock pairing, so it raises ThreadError 'must be called with a block' (detected with defined?(yield) before entering the primitive). Unlike lock/unlock, synchronize has no block-less use case.

Source

Thrown at thread_sync.rb:388

      Primitive.rb_mut_trylock
    end

    # call-seq:
    #    mutex.unlock  -> self
    #
    # Releases the lock.
    # Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
    def unlock
      Primitive.rb_mut_unlock
    end

    # call-seq:
    #    mutex.synchronize { ... }    -> result of the block
    #
    # Obtains a lock, runs the block, and releases the lock when the block
    # completes.  See the example under Thread::Mutex.
    def synchronize
      raise ThreadError, "must be called with a block" unless defined?(yield)

      Primitive.rb_mut_synchronize
    end

    # call-seq:
    #    mutex.sleep(timeout = nil)    -> number or nil
    #
    # Releases the lock and sleeps +timeout+ seconds if it is given and
    # non-nil or forever.  Raises +ThreadError+ if +mutex+ wasn't locked by
    # the current thread.
    #
    # When the thread is next woken up, it will attempt to reacquire
    # the lock.
    #
    # Note that this method can wakeup without explicit Thread#wakeup call.
    # For example, receiving signal and so on.
    #
    # Returns the slept time in seconds if woken up, or +nil+ if timed out.

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Always pass the body as a block: mutex.synchronize { ... }
  2. In wrappers, raise your own clear error when blk is nil before delegating: mutex.synchronize(&blk)
  3. Branch around the synchronize call when work is conditional, instead of calling it block-less

Example fix

# before
result = mutex.synchronize(update_counters) # runs update_counters unlocked, then raises ThreadError

# after
result = mutex.synchronize { update_counters }
Defensive patterns

Strategy: validation

Validate before calling

def with_lock(mutex, &blk)
  raise ArgumentError, 'with_lock requires a block' if blk.nil?
  mutex.synchronize(&blk)
end

Prevention

When it happens

Trigger: mutex.synchronize with no block; passing a method call's return value instead of a block: mutex.synchronize(update_counters) — update_counters runs first, its result is passed as a positional argument, and the call raises; wrappers declared with &blk but called without one, forwarding &nil.

Common situations: Refactoring manual lock/unlock code into synchronize; method-reference or lazy-block refactors dropping the ampersand; copying Monitor examples onto Mutex; metaprogramming wrappers that forget a block nil-check.

Related errors


AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21). Data as JSON: /api/errors/47ba8acc0d9b4a9b. Report an issue: GitHub.