{"record":{"id":"47ba8acc0d9b4a9b","repo":"ruby/ruby","slug":"must-be-called-with-a-block-47ba8a","errorCode":null,"errorMessage":"must be called with a block","messagePattern":"must be called with a block","errorType":"exception","errorClass":"ThreadError","httpStatus":null,"severity":"error","filePath":"thread_sync.rb","lineNumber":388,"sourceCode":"      Primitive.rb_mut_trylock\n    end\n\n    # call-seq:\n    #    mutex.unlock  -> self\n    #\n    # Releases the lock.\n    # Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.\n    def unlock\n      Primitive.rb_mut_unlock\n    end\n\n    # call-seq:\n    #    mutex.synchronize { ... }    -> result of the block\n    #\n    # Obtains a lock, runs the block, and releases the lock when the block\n    # completes.  See the example under Thread::Mutex.\n    def synchronize\n      raise ThreadError, \"must be called with a block\" unless defined?(yield)\n\n      Primitive.rb_mut_synchronize\n    end\n\n    # call-seq:\n    #    mutex.sleep(timeout = nil)    -> number or nil\n    #\n    # Releases the lock and sleeps +timeout+ seconds if it is given and\n    # non-nil or forever.  Raises +ThreadError+ if +mutex+ wasn't locked by\n    # the current thread.\n    #\n    # When the thread is next woken up, it will attempt to reacquire\n    # the lock.\n    #\n    # Note that this method can wakeup without explicit Thread#wakeup call.\n    # For example, receiving signal and so on.\n    #\n    # Returns the slept time in seconds if woken up, or +nil+ if timed out.","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/thread_sync.rb#L370-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nresult = mutex.synchronize(update_counters) # runs update_counters unlocked, then raises ThreadError\n\n# after\nresult = mutex.synchronize { update_counters }","handlingStrategy":"validation","validationCode":"def with_lock(mutex, &blk)\n  raise ArgumentError, 'with_lock requires a block' if blk.nil?\n  mutex.synchronize(&blk)\nend","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["thread","mutex","ruby","threaderror","block-required","concurrency"],"backgroundTag":"missing-block-argument","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}