locking.py 4.37 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 26
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

if VERBOSE_LOCKING:
27 28 29 30
    import traceback
    import sys
    import os

31 32 33 34 35 36 37 38 39 40
    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
Vincent Pelletier's avatar
Vincent Pelletier committed
41 42
            self.stack = stack = traceback.extract_stack()[:-(2 + level)]
            path, line_number, func_name, line = stack[-1]
43 44 45 46 47 48 49 50 51 52 53
            # 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])

Vincent Pelletier's avatar
Vincent Pelletier committed
54 55 56
        def formatStack(self):
            return ''.join(traceback.format_list(self.stack))

57
    class VerboseLock(object):
Vincent Pelletier's avatar
Vincent Pelletier committed
58 59
        def __init__(self, reentrant=False):
            self.reentrant = reentrant
60 61 62 63
            self.owner = None
            self.waiting = []
            self._note('%s@%X created by %r', self.__class__.__name__, id(self), LockUser(1))

64 65
        def _note(self, fmt, *args):
            sys.stderr.write(fmt % args + '\n')
66 67 68 69 70 71 72 73 74 75 76 77 78
            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)
Vincent Pelletier's avatar
Vincent Pelletier committed
79
            if not self.reentrant and blocking and me == owner:
80
                self._note('[%r]%s.acquire(%s): Deadlock detected: I already own this lock:%r', me, self, blocking, owner)
Vincent Pelletier's avatar
Vincent Pelletier committed
81 82
                self._note('Owner traceback:\n%s', owner.formatStack())
                self._note('My traceback:\n%s', me.formatStack())
83 84 85 86 87 88 89 90 91 92 93 94 95 96
            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):
97
            raise NotImplementedError
98 99 100 101 102 103

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

    class RLock(VerboseLock):
        def __init__(self, verbose=None):
104
            VerboseLock.__init__(self, reentrant=True)
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
            self.lock = threading_RLock()

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

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

    class Lock(VerboseLock):
        def __init__(self, verbose=None):
            VerboseLock.__init__(self)
            self.lock = threading_Lock()

        def locked(self):
            return self.lock.locked()
        _locked = locked
else:
    Lock = threading_Lock
    RLock = threading_RLock