locking.py 4.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
from threading import Lock as threading_Lock
from threading import RLock as threading_RLock
from threading import currentThread

"""
  Verbose locking classes.

  Python threading module contains a simple logging mechanism, but:
    - It's limitted to RLock class
    - It's enabled instance by instance
    - Choice to log or not is done at instanciation
    - It does not emit any log before trying to acquire lock

  This file defines a VerboseLock class implementing basic lock API and
  logging in appropriate places with extensive details.

  It can be globaly toggled by changing VERBOSE_LOCKING value.
  There is no overhead at all when disabled (passthrough to threading
  classes).
"""

__all__ = ['Lock', 'RLock']

VERBOSE_LOCKING = False

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
import traceback
import sys
import os

class LockUser(object):
    def __init__(self, level=0):
        self.ident = currentThread().getName()
        # This class is instanciated from a place desiring to known what
        # called it.
        # limit=1 would return execution position in this method
        # limit=2 would return execution position in caller
        # limit=3 returns execution position in caller's caller
        # Additionnal level value (should be positive only) can be used when
        # more intermediate calls are involved
        self.stack = stack = traceback.extract_stack()[:-(2 + level)]
        path, line_number, func_name, line = stack[-1]
        # Simplify path. Only keep 3 last path elements. It is enough for
        # current Neo directory structure.
        path = os.path.join('...', *path.split(os.path.sep)[-3:])
        self.caller = (path, line_number, func_name, line)

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.ident == other.ident

    def __repr__(self):
        return '%s@%s:%s %s' % (self.ident, self.caller[0], self.caller[1], self.caller[3])

    def formatStack(self):
        return ''.join(traceback.format_list(self.stack))

class VerboseLockBase(object):
    def __init__(self, reentrant=False):
        self.reentrant = reentrant
        self.owner = None
        self.waiting = []
        self._note('%s@%X created by %r', self.__class__.__name__, id(self), LockUser(1))

    def _note(self, fmt, *args):
        sys.stderr.write(fmt % args + '\n')
        sys.stderr.flush()

    def _getOwner(self):
        if self._locked():
            owner = self.owner
        else:
            owner = None
        return owner

    def acquire(self, blocking=1):
        me = LockUser()
        owner = self._getOwner()
        self._note('[%r]%s.acquire(%s) Waiting for lock. Owned by:%r Waiting:%r', me, self, blocking, owner, self.waiting)
        if not self.reentrant and blocking and me == owner:
            self._note('[%r]%s.acquire(%s): Deadlock detected: I already own this lock:%r', me, self, blocking, owner)
            self._note('Owner traceback:\n%s', owner.formatStack())
            self._note('My traceback:\n%s', me.formatStack())
        self.waiting.append(me)
        try:
            return self.lock.acquire(blocking)
        finally:
            self.owner = me
            self.waiting.remove(me)
            self._note('[%r]%s.acquire(%s) Lock granted. Waiting: %r', me, self, blocking, self.waiting)

    def release(self):
        me = LockUser()
        self._note('[%r]%s.release() Waiting: %r', me, self, self.waiting)
        return self.lock.release()

    def _locked(self):
        raise NotImplementedError

    def __repr__(self):
        return '<%s@%X>' % (self.__class__.__name__, id(self))

class VerboseRLock(VerboseLockBase):
    def __init__(self, verbose=None):
103
        super(VerboseRLock, self).__init__(reentrant=True)
104 105 106 107 108 109 110 111 112 113
        self.lock = threading_RLock()

    def _locked(self):
        return self.lock.__block.locked()

    def _is_owned(self):
        return self.lock._is_owned()

class VerboseLock(VerboseLockBase):
    def __init__(self, verbose=None):
114
        super(VerboseLock, self).__init__()
115 116 117 118 119 120
        self.lock = threading_Lock()

    def locked(self):
        return self.lock.locked()
    _locked = locked

121
if VERBOSE_LOCKING:
122 123
    Lock = VerboseLock
    RLock = VerboseRLock
124 125 126 127
else:
    Lock = threading_Lock
    RLock = threading_RLock