{"record":{"id":"b939def748cc05a9","repo":"tqdm/tqdm","slug":"cannot-release-un-acquired-lock","errorCode":null,"errorMessage":"cannot release un-acquired lock","messagePattern":"cannot release un-acquired lock","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"tqdm/contrib/concurrent.py","lineNumber":56,"sourceCode":"            return True\n        try:\n            if not blocking:\n                self._queue.get_nowait()\n            elif timeout == -1:\n                self._queue.get()\n            else:\n                remaining = max(0, timeout - (self._time() - start))\n                self._queue.get(timeout=remaining)\n        except Empty:\n            self._lock.release()\n            return False\n        self._owner = self.get_ident()\n        self._depth = 1\n        return True\n\n    def release(self):\n        if self._owner != self.get_ident():\n            raise RuntimeError(\"cannot release un-acquired lock\")\n        self._depth -= 1\n        if not self._depth:\n            self._owner = None\n            self._queue.put(None)\n        self._lock.release()\n\n    def __enter__(self):\n        self.acquire()\n        return self\n\n    def __exit__(self, *exc):\n        self.release()\n\n\n@contextmanager\ndef ensure_lock(tqdm_class, lock_name=\"\", lock=None):\n    \"\"\"get (create if necessary) and then restore `tqdm_class`'s lock\"\"\"\n    old_lock = getattr(tqdm_class, '_lock', None)  # don't create a new lock","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/tqdm/tqdm/blob/96f2e60e4584cdab57a23602e27043d0465254ad/tqdm/contrib/concurrent.py#L38-L74","documentation":"This is tqdm.contrib.concurrent's thread-ownership lock: release() checks the releasing thread owns the lock; releasing from a different thread (or without acquire) raises RuntimeError 'cannot release un-acquired lock'.","triggerScenarios":"Calling release() on the ThreadOps lock from a thread other than the acquirer, or double-release after the depth counter hit zero, when using tqdm.contrib.concurrent.thread_map.","commonSituations":"Mixing manual acquire/release around thread_map; callbacks that spawn work in other threads; misuse of the internal API.","solutions":["Only release from the thread that acquired; rely on the context manager (with lock:)","Avoid calling internal _lock methods of tqdm.contrib.concurrent directly","Report a bug if this occurs with plain thread_map usage"],"exampleFix":"# before\nlock.acquire()\nthreading.Thread(target=lock.release).start()\n# after\nwith lock:\n    do_work()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    with ops_lock:  # always use context manager\n        do_work()\nexcept RuntimeError as e:\n    if 'un-acquired' in str(e):\n        log.exception('lock misuse in thread %s', threading.get_ident())\n    raise","preventionTips":["Never call acquire/release manually on tqdm.contrib.concurrent internals","Keep acquire and release on the same thread","Prefer thread_map's public API over lock juggling"],"tags":["tqdm","concurrency","threading","lock-misuse"],"backgroundTag":"lock-not-held-by-current-thread","analyzedSha":"96f2e60e4584cdab57a23602e27043d0465254ad","analyzedAt":"2026-08-28T11:23:10.474Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}