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
- Always pass the body as a block: mutex.synchronize { ... }
- In wrappers, raise your own clear error when blk is nil before delegating: mutex.synchronize(&blk)
- 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
- Always pass a literal block to synchronize; never its result-position argument
- Forward &blk explicitly and nil-check it in every synchronization wrapper
- Prefer synchronize over manual lock/unlock so mistakes fail fast instead of deadlocking
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
- must be called with a block
- can't set a timeout if non_block is enabled
- already initialized
- specify at least one Ractor::Port or Ractor
- should be Ractor::Port or Ractor
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/47ba8acc0d9b4a9b.
Report an issue: GitHub.