Lock acquisition by the same thread - deadlock protection

Dieter Maurer dieter at handshake.de
Sun Mar 15 06:32:43 EDT 2020


Cameron Simpson wrote at 2020-3-15 10:17 +1100:
>On 12Mar2020 20:08, Dieter Maurer <dieter at handshake.de> wrote:
> ...
>>The documentation for the basic lock explicitely allows
>>lock release by a foreign thread.
>>As I understand the documentation, this lock type is by design
>>very basic - a mechanism to implement higher level abstractions.
>>I use other synchronisation mechanisms when the basic lock does
>>not fit my requirements.
>
>Aye. I have certainly gone:
>- take lock associated with some task specific resource
>- set up task
>- kick off worker Thread (or queue worker function for later)
>- worker releases the lock
>
>However, having an error check mode disallowing attempts by the same
>Thread to take a Lock it itself took seems valuable. If the lock object
>has that piece of information (IIRC it does).

Below is a Python 3 implementation for such a lock.
The quirks required to get a nested "with" safe might be seen
as an indication that it should not be implemented in the base lock
class.

from _thread import allocate_lock, get_ident

class NestedTLockError(RuntimeError):
  """Special error used to make nested ``with`` statements safe."""
  def __init__(self, message, lock):
    self._lock = lock
    super().__init__(message, lock)

class TLock:
  """A lock preventing the same thread to get the lock twice."""

  def __init__(self):
    self._lock = allocate_lock()
    self._tid = None

  def acquire(self, wait=True, timeout=-1):
    tid = get_ident()
    if self._tid == tid:
      # this remains stable
      raise NestedTLockError("already acquired", self)
    acquired = self._lock.acquire(0) or self._lock.acquire(wait, timeout)
    if acquired:
      # Lock successfully acquired - no other thread will interfere
      self._tid = tid
    return acquired

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

  def release(self):
    # the application is responsible that nothing interferes
    #  with our release
    self._tid = None
    self._lock.release()

  def __exit__(self, t, v, tb):
    if not self.locked(): return # the ``__enter__`` failed
    if t is NestedTLockError and v._lock is self:
      # the ``__enter__`` failed due to nested lock acquisition
      # we must not release the lock (it would be too early)
      # but must allow the outer ``__exit__`` to release the lock
      v._lock = None
      return
    self.release()

  def locked(self): return self._lock.locked()



More information about the Python-list mailing list