tqdm/tqdm · critical · RuntimeError

cannot release un-acquired lock

Error message

cannot release un-acquired lock

What it means

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'.

Source

Thrown at tqdm/contrib/concurrent.py:56

            return True
        try:
            if not blocking:
                self._queue.get_nowait()
            elif timeout == -1:
                self._queue.get()
            else:
                remaining = max(0, timeout - (self._time() - start))
                self._queue.get(timeout=remaining)
        except Empty:
            self._lock.release()
            return False
        self._owner = self.get_ident()
        self._depth = 1
        return True

    def release(self):
        if self._owner != self.get_ident():
            raise RuntimeError("cannot release un-acquired lock")
        self._depth -= 1
        if not self._depth:
            self._owner = None
            self._queue.put(None)
        self._lock.release()

    def __enter__(self):
        self.acquire()
        return self

    def __exit__(self, *exc):
        self.release()


@contextmanager
def ensure_lock(tqdm_class, lock_name="", lock=None):
    """get (create if necessary) and then restore `tqdm_class`'s lock"""
    old_lock = getattr(tqdm_class, '_lock', None)  # don't create a new lock

View on GitHub (pinned to 96f2e60e45)

Solutions

  1. Only release from the thread that acquired; rely on the context manager (with lock:)
  2. Avoid calling internal _lock methods of tqdm.contrib.concurrent directly
  3. Report a bug if this occurs with plain thread_map usage

Example fix

# before
lock.acquire()
threading.Thread(target=lock.release).start()
# after
with lock:
    do_work()
Defensive patterns

Strategy: try-catch

Try / catch

try:
    with ops_lock:  # always use context manager
        do_work()
except RuntimeError as e:
    if 'un-acquired' in str(e):
        log.exception('lock misuse in thread %s', threading.get_ident())
    raise

Prevention

When it happens

Trigger: 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.

Common situations: Mixing manual acquire/release around thread_map; callbacks that spawn work in other threads; misuse of the internal API.

Related errors


AI-assisted analysis of tqdm/tqdm@96f2e60e45 (2026-08-28). Data as JSON: /api/errors/b939def748cc05a9. Report an issue: GitHub.