{"record":{"id":"b3b136a60282c9f3","repo":"ruby-concurrency/concurrent-ruby","slug":"cannot-release-a-write-lock-which-is-not-held-by-t","errorCode":null,"errorMessage":"Cannot release a write lock which is not held by the current thread","messagePattern":"Cannot release a write lock which is not held by the current thread","errorType":"exception","errorClass":"Concurrent::IllegalOperationError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb","lineNumber":208,"sourceCode":"            c = @Counter.value\n            break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)\n          end\n          break\n        end\n      end\n      @Writer.set(Thread.current)\n      true\n    end\n\n    # Release a previously acquired write lock.\n    #\n    # @return [Boolean] true if the lock is successfully released\n    #\n    # @raise [Concurrent::IllegalOperationError] if the write lock is not held\n    #   by the current thread.\n    def release_write_lock\n      unless @Writer.compare_and_set(Thread.current, nil)\n        raise IllegalOperationError, 'Cannot release a write lock which is not held by the current thread'\n      end\n\n      c = @Counter.update { |counter| counter - RUNNING_WRITER }\n      @ReadLock.broadcast\n      @WriteLock.signal if waiting_writers(c) > 0\n      true\n    end\n\n    # Queries if the write lock is held by any thread.\n    #\n    # @return [Boolean] true if the write lock is held else false`\n    def write_locked?\n      @Counter.value >= RUNNING_WRITER\n    end\n\n    # Queries whether any threads are waiting to acquire the read or write lock.\n    #\n    # @return [Boolean] true if any threads are waiting for a lock else false","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb#L190-L226","documentation":"`Concurrent::ReadWriteLock` records the write-lock owner in `@Writer`; `release_write_lock` does a compare_and_set of `Thread.current` to nil and raises IllegalOperationError when it fails. That means the calling thread is not the current write owner: released without acquiring, released twice, or — most commonly — acquired on one thread (often a pool thread) and released on another. The lock's `write_locked?` predicate only reports that some thread holds it, not which.","triggerScenarios":"Acquiring inside a worker/pool thread (`Concurrent::Future.execute { lock.acquire_write_lock; work }`) and releasing from the main thread after join; a double release on one thread; releasing in ensure after an exception aborted the acquire mid-way.","commonSituations":"Refactoring block-scoped locking into manual acquire/release across method or thread boundaries; work handed to a thread pool while the coordinator releases; cleanup code run in a different fiber/thread than the critical section.","solutions":["Keep acquire and release on the same thread — move the whole critical section into that thread.","Use `lock.with_write_lock { ... }` so the pair is structural, not manual.","When ownership must cross calls, track the owning Thread yourself and release only from it.","In cleanup paths where ownership is unknown, rescue Concurrent::IllegalOperationError and log; do not blindly retry."],"exampleFix":"// before\nworker = Thread.new { lock.acquire_write_lock; do_work }\nworker.join\nlock.release_write_lock # raises: @Writer is the worker thread\n\n// after\nworker = Thread.new do\n  lock.acquire_write_lock\n  begin\n    do_work\n  ensure\n    lock.release_write_lock\n  end\nend\nworker.join","handlingStrategy":"try-catch","validationCode":"@write_owner = Thread.current # record at acquire time\nlock.release_write_lock if @write_owner == Thread.current","typeGuard":null,"tryCatchPattern":"begin\n  lock.release_write_lock\nrescue Concurrent::IllegalOperationError\n  logger.debug 'write lock not held by this thread'\nend","preventionTips":["Acquire and release on the same thread; keep the pair inside one method with ensure.","Prefer lock.with_write_lock { ... }.","write_locked? means 'held by any thread' — do not use it as an ownership check.","Track the owning Thread when the critical section crosses call boundaries."],"tags":["concurrent-ruby","readwritelock","illegaloperationerror","lock-ownership","threading"],"backgroundTag":"release-unheld-lock","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}