RE: Patch to DorothyLocker
"Garth T Kidd" <garth-OnzZ1s1DREKDegMON/[email protected]> Thu, 23 Sep 2004 13:01:19 +1000
| Newsgroups | gmane.comp.pythin.pyds.devel |
|---|---|
| Organization | Deadly Bloody Serious |
| Message-ID | <[email protected]> |
This is a multi-part message in MIME format. ------=_NextPart_000_0013_01C4A16D.6CD28660 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit All done! I renamed the exception, made it a subclass of AssertionError, added specific sub-exceptions, and <drumroll/> added unit tests to make sure everything works. If anyone has any ideas for additional unit tests (stress tests with a hundred threads each going for the same lock, for example), please let me know and/or submit code. Thomas, I'd much appreciate it if you could go over my code to see if you can spot anything. Yours, Garth. -----Original Message----- From: Garth T Kidd [mailto:garth-OnzZ1s1DREKDegMON/[email protected]] Sent: Thursday, 23 September 2004 7:25 AM To: 'Thomas Klaeger' Cc: 'pyds-dev-iYtK5bfT9M//Ad8WF/[email protected]' Subject: RE: [Pyds-dev] Patch to DorothyLocker Excellent idea, that. Whining and whining loudly is fine; all I had to do was consolidate down to only one ``print`` so output didn't interleave. New version attached. -----Original Message----- From: Garth T Kidd [mailto:garth-OnzZ1s1DREKDegMON/[email protected]] Sent: Thursday, 23 September 2004 7:18 AM To: 'Thomas Klaeger' Cc: 'pyds-dev-iYtK5bfT9M//Ad8WF/[email protected]' Subject: RE: [Pyds-dev] Patch to DorothyLocker No need, but thanks for the offer! I stripped back and simplified the code, kept in mind that if the calling thread was the lock owner I knew I wasn't re-entrant, and it all behaved pretty quickly. My main cause for concern now is not knowing how well the LockWhiner reporting will go. I guess I should set the whine period to a tenth of a second to try it out. :) -----Original Message----- From: pyds-dev-admin-iYtK5bfT9M//Ad8WF/[email protected] [mailto:pyds-dev-admin-iYtK5bfT9M//Ad8WF/[email protected]] On Behalf Of Thomas Klaeger Sent: Wednesday, 22 September 2004 7:55 PM To: Garth T Kidd Cc: pyds-dev-iYtK5bfT9M//Ad8WF/[email protected] Subject: Re: [Pyds-dev] Patch to DorothyLocker Is there any possibility I can help out? ------=_NextPart_000_0013_01C4A16D.6CD28660 Content-Type: application/octet-stream; name="20040923-1255-dorothy.diff" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="20040923-1255-dorothy.diff" Index: PyDS/Tool.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /pyds/PyDS/PyDS/Tool.py,v retrieving revision 1.195 diff -c -r1.195 Tool.py *** PyDS/Tool.py 14 Sep 2004 08:57:38 -0000 1.195 --- PyDS/Tool.py 23 Sep 2004 02:19:10 -0000 *************** *** 49,55 **** =20 import PyDS.StructuredText import PyDS.CommandQueue ! from PyDS.DorothyLocker import DorothyRLock, ReleaseFailure, = print_minitrace import PyDS =20 version =3D 'Python Desktop Server %s (http://pyds.muensterland.org/)' = % PyDS.__version__ --- 49,55 ---- =20 import PyDS.StructuredText import PyDS.CommandQueue ! from PyDS.DorothyLocker import DorothyRLock, LockAssertionError, = print_minitrace import PyDS =20 version =3D 'Python Desktop Server %s (http://pyds.muensterland.org/)' = % PyDS.__version__ *************** *** 827,832 **** --- 827,833 ---- self.background =3D threading.Thread( target=3Dself.loop, args=3D[], + name=3D'pyds-processor', kwargs=3D{} ) _flet.clone(self.background) *************** *** 1143,1149 **** self.rss =3D 0 self.hasAPI =3D 1 if _PyDS.verbose: ! self.lock =3D DorothyRLock()=20 else: self.lock =3D threading.RLock() self.lockedFrom =3D None --- 1144,1150 ---- self.rss =3D 0 self.hasAPI =3D 1 if _PyDS.verbose: ! self.lock =3D DorothyRLock(verbose=3DNone, name=3D'%s.lock' % name) = else: self.lock =3D threading.RLock() self.lockedFrom =3D None *************** *** 1335,1340 **** --- 1336,1342 ---- background =3D threading.Thread( target=3Dthunk, args=3D[self, status], + name=3Dself.name, kwargs=3D{} ) self._mythread =3D background *************** *** 1375,1381 **** def _acquire(self, blocking=3D1): try:=20 return self.lock.acquire(blocking) ! except ReleaseFailure, e:=20 msg, frame, t =3D e print "%s caught; lock was acquired in frame id %d at time %s" % ( msg, id(frame), time.ctime(t)) --- 1377,1383 ---- def _acquire(self, blocking=3D1): try:=20 return self.lock.acquire(blocking) ! except LockAssertionError, e:=20 msg, frame, t =3D e print "%s caught; lock was acquired in frame id %d at time %s" % ( msg, id(frame), time.ctime(t)) *************** *** 1394,1400 **** def _release(self): try:=20 return self.lock.release() ! except ReleaseFailure, e:=20 msg, frame, t =3D e print "%s caught; lock was acquired in frame id %d at time %s" % ( msg, id(frame), time.ctime(t)) --- 1396,1402 ---- def _release(self): try:=20 return self.lock.release() ! except LockAssertionError, e:=20 msg, frame, t =3D e print "%s caught; lock was acquired in frame id %d at time %s" % ( msg, id(frame), time.ctime(t)) Index: PyDS/DorothyLocker.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /pyds/PyDS/PyDS/DorothyLocker.py,v retrieving revision 1.2 diff -c -r1.2 DorothyLocker.py *** PyDS/DorothyLocker.py 14 Sep 2004 08:51:06 -0000 1.2 --- PyDS/DorothyLocker.py 23 Sep 2004 02:54:44 -0000 *************** *** 15,27 **** actually causing the locks and releases.=20 """ =20 ! from threading import _RLock, currentThread, Thread import time import inspect import sys import traceback =20 ! IGNORE =3D ['_acquire', '_release'] =20 def ltrim_common(oblist1, oblist2):=20 "Return the arguments with all common first elements removed." --- 15,30 ---- actually causing the locks and releases.=20 """ =20 ! import threading ! from threading import _Verbose, currentThread, Thread ! from thread import allocate_lock import time import inspect import sys import traceback + import os.path =20 ! IGNORE =3D ['_acquire', '_release', '__acquire', '__release', = '__DorothyRLock_acquire', '__DorothyRLock_release'] =20 def ltrim_common(oblist1, oblist2):=20 "Return the arguments with all common first elements removed." *************** *** 32,37 **** --- 35,51 ---- pos =3D pos + 1 return oblist1[pos:], oblist2[pos:] =20 + def ping(*args):=20 + myContext =3D context(ignorecodes=3D[ping.func_code]) + frame =3D myContext[-1] + filename, line, name =3D frame.f_code.co_filename, frame.f_lineno, = frame.f_code.co_name + filename =3D os.path.basename(filename) + me =3D currentThread() + args =3D ", ".join([repr(a) for a in args]) + print "ping! thread %d/%s %s line %d %s" % ( + id(me), me.getName(), filename, line, args + ) + =09 def extract_minitrace(frame): "Extract a mini-trace from a locking context." mycontext =3D context() *************** *** 46,57 **** frame =3D frame.f_back return items =20 def print_minitrace(frame):=20 "Print a mini-trace from a locking context." ! for filename, line, name in extract_minitrace(frame):=20 ! print 'File "%s", line %d, in %s' % ( ! filename, line, name) ! =09 def context(ignorecodes=3D[], ignorenames=3D[]):=20 """Distil a calling context, ignoring certain code objects and=20 function names.""" --- 60,77 ---- frame =3D frame.f_back return items =20 + def format_minitrace(frame):=20 + "Format a mini-trace from a locking context." + msg =3D [] + for filename, line, name in extract_minitrace(frame):=20 + msg.append('File "%s", line %d, in %s' % ( + filename, line, name)) + return msg +=20 def print_minitrace(frame):=20 "Print a mini-trace from a locking context." ! print '\n'.join(format_minitrace(frame)) !=20 def context(ignorecodes=3D[], ignorenames=3D[]):=20 """Distil a calling context, ignoring certain code objects and=20 function names.""" *************** *** 69,113 **** finally:=20 del frame =20 ! class ReleaseFailure(Exception):=20 "Raised if DorothyRLock detects a failure to release an acquired = lock." pass =20 ! class DorothyRLock(_RLock):=20 """DorothyRLock insists upon release() being called from the same=20 execution frame that acquire()d it in the first place. Frames named=20 in DorothyLocker.IGNORE will be ignored. =09 If a subframe re-acquires, that's okay, so long as it releases before = acquire or release is called via further up the frame stack.""" ! =09 ! def __init__(self, verbose=3DNone):=20 ! "Initialise the RLock." ! _RLock.__init__(self, verbose) ! self.__threads =3D {} self.__ignores =3D [ self.acquire.func_code,=20 self.release.func_code, self.callContext.func_code] =09 def callContext(self):=20 "Distil our calling context, with instance-specific ignores." return context(self.__ignores, IGNORE) =09 def acquire(self, blocking=3D1):=20 ! """Acquire the lock, first checking that any previous locks have=20 ! been released.""" ! # First, get our context:=20 me =3D currentThread() - myLocks =3D self.__threads.setdefault(me, []) mycontext, myt =3D self.callContext(), time.time() =20 ! # If there were any locks...=20 ! if myLocks:=20 # Try to figure out whether the previous lock should have been=20 # released.=20 ! # TODO: shouldn't we check all the locks?=20 ! prevcontext, prevt =3D myLocks[-1] prevframe =3D prevcontext[-1] # eliminate the common parts of the call stack pc, mc =3D ltrim_common(prevcontext, mycontext) --- 89,175 ---- finally:=20 del frame =20 ! class LockAssertionError(AssertionError):=20 "Raised if DorothyRLock detects a failure to release an acquired = lock." pass =20 ! class ReAcquireFromSameFrame(LockAssertionError):=20 ! """Release failure was detected when a frame tried to acquire = again.""" !=20 ! class ReAcquireFromCallingFrame(LockAssertionError):=20 ! """Release failure was detected when a calling frame of the locking=20 ! frame tried to acquire the lock.""" ! =09 ! class ReAcquireFromForeignFrame(LockAssertionError):=20 ! """Release failure was detected when another frame in the same=20 ! thread tried to acquire the lock.""" !=20 ! class ReleaseFromForeignFrame(LockAssertionError):=20 ! """Release failure was detected when another frame in the same=20 ! thread tried to release the lock. Strictly speaking, this requires=20 ! both a failure to release and a failure to acquire.""" ! =09 ! class ReleaseFromForeignThread(LockAssertionError):=20 ! """An attempt to release the lock from a thread that didn't own it.=20 ! Strictly speaking, this is probably a failure to acquire.""" ! # RAISED BUT NOT YET TESTED FOR !=20 ! class ReleaseOfUnAcquiredLock(LockAssertionError):=20 ! """An attempt to release an un-acquired lock. Probably caused by=20 ! failure to acquire.""" ! # RAISED BUT NOT YET TESTED FOR !=20 ! class DorothyRLock(_Verbose):=20 """DorothyRLock insists upon release() being called from the same=20 execution frame that acquire()d it in the first place. Frames named=20 in DorothyLocker.IGNORE will be ignored. =09 If a subframe re-acquires, that's okay, so long as it releases before = acquire or release is called via further up the frame stack.""" !=20 ! def squawk(cls):=20 ! "Let users know if we're in use." ! att =3D 'squawked' ! if not hasattr(cls, att):=20 ! msg =3D "Enabled verbose lock debugging via DorothyLocker." ! dash =3D '-' * len(msg) ! print "\n%s\n%s\n%s\n" % (dash, msg, dash) ! setattr(cls, att, True) ! squawk =3D classmethod(squawk) ! =09 ! def __init__(self, verbose=3DNone, name=3D'(unknown)'):=20 ! "Initialise the DorothyRLock." ! _Verbose.__init__(self, verbose) ! self.name =3D name self.__ignores =3D [ self.acquire.func_code,=20 self.release.func_code, self.callContext.func_code] + self.__owner =3D None + self.__lockStack =3D [] + self.__block =3D allocate_lock() + self.__class__.squawk() =09 def callContext(self):=20 "Distil our calling context, with instance-specific ignores." return context(self.__ignores, IGNORE) =09 def acquire(self, blocking=3D1):=20 ! """Acquire the lock, first checking that any prior locks by this=20 ! thread have been released if they're not still on our call chain. ! """ ! =09 ! # First, get our context.=20 me =3D currentThread() mycontext, myt =3D self.callContext(), time.time() =20 ! # Are we the owner?=20 ! # This check is safe because owner is me only if we've called=20 ! # acquire() from the same thread.=20 ! if self.__owner is me:=20 # Try to figure out whether the previous lock should have been=20 # released.=20 ! prevcontext, prevt =3D self.__lockStack[-1] prevframe =3D prevcontext[-1] # eliminate the common parts of the call stack pc, mc =3D ltrim_common(prevcontext, mycontext) *************** *** 115,125 **** if mc:=20 # different parts of the call stack # =3D> failure to release ! raise ReleaseFailure, ("failure to release", prevframe, prevt) else:=20 # mc matched, but shorter # =3D> re-acquire from calling frame ! raise ReleaseFailure, ("failure to release", prevframe, prevt) else:=20 if mc:=20 # re-acquired from further down the call stack --- 177,187 ---- if mc:=20 # different parts of the call stack # =3D> failure to release ! raise ReAcquireFromForeignFrame, ("failure to release", = prevframe, prevt) else:=20 # mc matched, but shorter # =3D> re-acquire from calling frame ! raise ReAcquireFromCallingFrame, ("failure to release", = prevframe, prevt) else:=20 if mc:=20 # re-acquired from further down the call stack *************** *** 127,183 **** else:=20 # not pc AND not mc # =3D> identical contexts ! raise ReleaseFailure, ("frame re-called acquire", prevframe, = prevt) ! =09 ! # If we got this far, it's safe to lock.=20 ! if blocking:=20 ! result =3D _RLock.acquire(self, blocking=3D0) ! if not result:=20 ! # Whine whilst we wait for the lock to clear.=20 ! whiner =3D LockWhiner(self, mycontext, myt, prevcontext, prevt) ! whiner.start() ! result =3D _RLock.acquire(self, blocking) ! whiner.stop() ! else:=20 ! result =3D _RLock.acquire(self, blocking=3D0) ! =09 ! if result:=20 ! myLocks.append((mycontext, myt)) =20 ! return result =20 def release(self):=20 """Release the lock, first checking that we're releasing from the=20 same frame that acquired us.""" ! result =3D _RLock.release(self) # raises AssertionError if not = acquired in this thread ! context =3D self.callContext() me =3D currentThread() ! myLocks =3D self.__threads[me] ! prevcontext, prevt =3D myLocks[-1] ! if prevcontext =3D=3D context:=20 ! myLocks.pop() # Permament! myLocks is bound to the=20 ! # original, NOT a copy.=20 ! return # TODO: figure out whether this should return `result` ! raise ReleaseFailure, ("failure to release", prevcontext[-1], prevt) =20 class LockWhiner(Thread):=20 ! def __init__(self, dorothy, waitcontext, waittime, prevcontext, = prevtime, every=3D10):=20 Thread.__init__(self) self.dorothy =3D dorothy ! self.whineTime =3D waittime ! self.whineContext =3D waitcontext ! self.lockTime =3D prevtime ! self.lockContext =3D prevcontext self.whineEvery =3D every ! self.thread =3D currentThread() self.active =3D 1 self.setDaemon(1) # let PyDS shut down even if we're active =09 def stop(self):=20 self.stopTime =3D time.time() self.active =3D 0 ! print "%s: LockWhiner stopped; lock acquired after %.2f seconds" % ( time.ctime(self.stopTime),=20 self.stopTime - self.whineTime) =20 def run(self):=20 --- 189,305 ---- else:=20 # not pc AND not mc # =3D> identical contexts ! raise ReAcquireFromSameFrame, ("frame re-called acquire", = prevframe, prevt) =20 ! # If we got this far, we're OK.=20 ! # Add our details to the lock stack and return success.=20 ! self.__lockStack.append((mycontext, myt)) ! if __debug__:=20 ! self._note("%s.acquire(%s): recursive success", self, blocking) ! return 1 ! =09 ! # The lock isn't owned by this thread. The only way to know for ! # sure it isn't owned by anyone else is to acquire it. Let's try=20 ! # doing it without blocking, first.=20 ! result =3D self.__block.acquire(0) ! =09 ! if not result: # Our attempt to lock failed.=20 ! if not blocking:=20 ! # If we didn't want a blocking call, we can fail outright here.=20 ! if __debug__:=20 ! self._note("%s.acquire(%s): failure", self, blocking) ! return 0 !=20 ! # We know for sure someone else had self.__block at the time=20 ! # we tried to get it, but by now the ownership might have=20 ! # changed or it might have been released. So, we'll start a=20 ! # LockWhiner and then perform a blocking wait.=20 ! whiner =3D LockWhiner(self, mycontext, myt) ! whiner.start() ! result =3D self.__block.acquire() ! =09 ! # We can ONLY get here if we succeeded... ! assert result # ... but you can never be too careful.=20 ! =09 ! # Stop the whiner.=20 ! whiner.stop() ! =09 ! # Success! Let's grab the goodies and run.=20 ! self.__owner =3D me ! self.__lockStack =3D [(mycontext, myt)] ! if __debug__:=20 ! self._note("%s.acquire(%s): initial success", self, blocking) ! return 1 =20 def release(self):=20 """Release the lock, first checking that we're releasing from the=20 same frame that acquired us.""" ! =09 ! # First, get our context.=20 me =3D currentThread() ! mycontext, myt =3D self.callContext(), time.time() ! owner =3D self.__owner # just in case it changes !=20 ! if owner is me:=20 ! prevcontext, prevt =3D self.__lockStack[-1] ! if not prevcontext =3D=3D mycontext:=20 ! # TODO: unwind to some adequate level ! raise ReleaseFromForeignFrame, ("failure to release", = prevcontext[-1], prevt) ! elif owner is None:=20 ! raise ReleaseOfUnAcquiredLock, ("release of un-acquired lock", = mycontext[-1], myt) ! else:=20 ! raise ReleaseFromForeignThread, ("releasing thread doesn't own the = lock", mycontext[-1], myt) ! =09 ! # If we got here, all is well.=20 ! topContext, topTime =3D self.__lockStack.pop() ! delay =3D myt - topTime ! if self.__lockStack:=20 ! if __debug__:=20 ! self._note("%s.release(): non-final release after %.2fs", self, = delay) ! else:=20 ! if __debug__:=20 ! self._note("%s.release(): final release after %.2fs", self, delay) ! self.__owner =3D None ! self.__block.release() !=20 ! def __repr__(self):=20 ! "Return a representation of this object." ! return "<%s owned by %s with count %d>" % ( ! self.name,=20 ! self.__owner and self.__owner.getName(),=20 ! len(self.__lockStack)) !=20 ! def lockDetails(self):=20 ! """Return top lock details: owner, lock count, top context, time. ! Because of race conditions, details might not be consistent.""" ! try:=20 ! top =3D self.__lockStack[-1] ! mycontext, myt =3D top ! except IndexError:=20 ! mycontext =3D myt =3D None ! return self.__owner, len(self.__lockStack), mycontext, myt =20 class LockWhiner(Thread):=20 ! def __init__(self, dorothy, whineContext, whineTime, every=3D10, = loudEvery=3D60):=20 Thread.__init__(self) self.dorothy =3D dorothy ! self.whineTime =3D whineTime ! self.whineContext =3D whineContext self.whineEvery =3D every ! self.whineLoudlyEvery =3D loudEvery ! self.whineThread =3D currentThread() self.active =3D 1 self.setDaemon(1) # let PyDS shut down even if we're active =09 def stop(self):=20 self.stopTime =3D time.time() self.active =3D 0 ! print "%s: LockWhiner %d stopped for %s; acquired by thread %d (%s) = after %.2f seconds" % ( time.ctime(self.stopTime),=20 + id(self),=20 + self.dorothy.name,=20 + id(self.whineThread),=20 + self.whineThread.getName(), self.stopTime - self.whineTime) =20 def run(self):=20 *************** *** 191,272 **** print 'Exception %s: %s' % (e, d) for row in traceback.extract_tb(tb): print repr(row) =20 def _run(self):=20 ! fun =3D self.whineContext[-1][2] ! print "%s: LockWhiner started by %s thread %d" % (\ time.ctime(self.whineTime),=20 ! self.dorothy.name,=20 ! id(self.thread)) waits =3D 0 while self.active:=20 time.sleep(self.whineEvery) waits =3D waits + 1 now =3D time.time() ! print "%s: LockWhiner has been waiting for %.2fs" % ( time.ctime(), now - self.whineTime) ! if waits=3D=3D1:=20 ! print "-"*70 ! print "Just in case this is a deadlock, here's some additional " ! print "information for debugging purposes:\n" ! print "Lock obtained: %s" % (time.ctime(self.lockTime)) ! print "\nLock holding stack:" ! print_minitrace(self.lockContext[-1]) ! print "\nWaiting stack:"=20 ! print_minitrace(self.whineContext[-1]) ! print "\nLock held by:", ! lockOwner =3D self.dorothy._RLock__owner ! if lockOwner is None:=20 ! print "(None.)" else:=20 ! print id(lockOwner) ! print "-"*70 =20 if __name__ =3D=3D '__main__':=20 ! import pprint =20 ! nrl =3D DorothyRLock() ! def spit():=20 ! pprint.pprint(nrl.callContext()) ! def a():=20 ! spit() ! def b():=20 ! a() ! a() ! print ! b() ! def _release(): ! assert nrl.callContext()[-1].f_code.co_name !=3D '_release' ! _release() ! =09 ! nrl.acquire() ! nrl.release() !=20 ! def fail1_top():=20 ! fail1_mid1() ! fail1_mid2() ! def fail1_mid1():=20 ! nrl.acquire() ! def fail1_mid2():=20 ! nrl.release() !=20 ! def fail2_top():=20 ! fail2_mid1() ! fail2_mid2() ! def fail2_mid1():=20 ! nrl.acquire() ! def fail2_mid2():=20 ! nrl.acquire() ! nrl.release() =20 ! for test in [fail1_top, fail2_top]:=20 ! try:=20 ! print "\nTest: %s" % test.func_name ! test() ! except ReleaseFailure, e:=20 ! msg, frame, t =3D e ! print "%s caught; lock was acquired in frame id %d at time %s" % ( ! msg, id(frame), time.ctime(t)) ! print_minitrace(frame) =20 --- 313,514 ---- print 'Exception %s: %s' % (e, d) for row in traceback.extract_tb(tb): print repr(row) + print 'Dict:'=20 + for key in self.__dict__.keys():=20 + print ' %s =3D %s' % (key, repr(self.__dict__[key])) =20 def _run(self):=20 ! print "%s: LockWhiner %d started for %s; thread %d (%s) waiting" % = (\ time.ctime(self.whineTime),=20 ! id(self),=20 ! self.dorothy.name,=20 ! id(self.whineThread), ! self.whineThread.getName()) waits =3D 0 + louds =3D int(self.whineLoudlyEvery/self.whineEvery) while self.active:=20 time.sleep(self.whineEvery) + if not self.active:=20 + break waits =3D waits + 1 now =3D time.time() ! print "%s: LockWhiner %d has been waiting for %.2fs" % ( time.ctime(), + id(self),=20 now - self.whineTime) ! if not (waits-1) % louds:=20 ! msg =3D [] ! add =3D msg.append ! add("-"*70) ! add("Just in case this is a deadlock, here's some additional = information") ! add("for debugging purposes:\n") ! owner, lockCount, topContext, topTime =3D = self.dorothy.lockDetails() ! if topTime is None:=20 ! timeRep =3D "(unknown)" else:=20 ! timeRep =3D time.ctime(topTime) ! add("Lock most recently obtained at %s by thread id %d, name %s" % = (timeRep, id(owner), owner.getName())) ! if not owner.isAlive():=20 ! add("**THREAD IS DEAD**") ! add("\nLock holding stack:") ! if topContext is None:=20 ! add("(unknown)") ! else: ! msg.extend(format_minitrace(topContext[-1])) ! if waits=3D=3D1:=20 ! add("\nWaiting thread: id %d, name %s" % (id(self.whineThread), = self.whineThread.getName())) ! add("\nWaiting stack:") ! msg.extend(format_minitrace(self.whineContext[-1])) ! add("-"*70) ! print '\n'.join(msg) =20 if __name__ =3D=3D '__main__':=20 ! import unittest ! import Queue =20 ! class BasicTests(unittest.TestCase):=20 ! "Basic tests." =20 ! def test_creation(self):=20 ! "Creation" ! dorothy =3D DorothyRLock() ! =09 ! def test_verbose_creation(self):=20 ! "Verbose creation" ! dorothy =3D DorothyRLock(verbose=3DTrue) ! =09 ! def test_named_creation(self):=20 ! "Named creation" ! dorothy =3D DorothyRLock(name=3D'lockName') ! =09 ! def test_verbose_named_creation(self):=20 ! "Verbose, named creation" ! dorothy =3D DorothyRLock(verbose=3DTrue, name=3D'lockName') ! =09 ! def test_acquire_release(self):=20 ! "Basic acquisition and release" ! dorothy =3D DorothyRLock() ! dorothy.acquire() ! dorothy.release() ! =09 ! def test_mistaken_release_of_unacquired_lock(self):=20 ! "React properly to someone releasing an un-acquired lock" ! def mistaken_release_of_unacquired_lock():=20 ! dorothy =3D DorothyRLock() ! dorothy.release() ! self.assertRaises(ReleaseOfUnAcquiredLock, = mistaken_release_of_unacquired_lock) !=20 ! class ReleaseFailureTests(unittest.TestCase): ! "Tests of the release failure detection mechanism." !=20 ! def acquire_with_no_release(self, dorothy):=20 ! "Acquire, but don't release, `dorothy`. Used by many tests." ! dorothy.acquire() ! # didn't call dorothy.release() -- oops! ! =09 ! def test_boxed_pairs(self):=20 ! "Permit subframes to re-acquire" !=20 ! def reacquire(dorothy):=20 ! dorothy.acquire() ! dorothy.release() !=20 ! dorothy =3D DorothyRLock() ! dorothy.acquire() ! reacquire(dorothy) ! dorothy.release() !=20 ! def test_release_from_calling_frame(self):=20 ! "Detect release failure when a calling frame releases" !=20 ! def release_from_calling_frame():=20 ! dorothy =3D DorothyRLock() ! dorothy.acquire() ! self.acquire_with_no_release(dorothy) ! dorothy.release() ! =09 ! self.assertRaises(ReleaseFromForeignFrame, = release_from_calling_frame) !=20 ! def test_reacquire_from_calling_frame(self):=20 ! "Detect release failure when a calling frame re-acquires" !=20 ! def acquire_with_no_release(dorothy):=20 ! dorothy.acquire() ! # didn't call dorothy.release() -- oops! ! =09 ! def reacquire_from_calling_frame():=20 ! dorothy =3D DorothyRLock() ! self.acquire_with_no_release(dorothy) ! dorothy.acquire() ! =09 ! self.assertRaises(ReAcquireFromCallingFrame, = reacquire_from_calling_frame) ! =09 ! def test_reacquire_from_foreign_frame(self):=20 ! "Detect release failure when the lock is re-acquired from = elsewhere" !=20 ! def reacquire_from_foreign_frame(): ! dorothy =3D DorothyRLock() ! self.acquire_with_no_release(dorothy) ! # the second time around, this is a different *frame*=20 ! # despite it being the same callable object ! self.acquire_with_no_release(dorothy) !=20 ! self.assertRaises(ReAcquireFromForeignFrame, = reacquire_from_foreign_frame) ! =09 ! def test_reacquire_from_same_frame(self):=20 ! "Detect release failure when the lock is re-acquired from the same = frame" !=20 ! def reacquire_from_same_frame(): ! dorothy =3D DorothyRLock() ! dorothy.acquire() ! dorothy.acquire() !=20 ! self.assertRaises(ReAcquireFromSameFrame, = reacquire_from_same_frame) ! =09 ! class LockSittingThread(threading.Thread):=20 ! "Thread that sits on a lock. Automatically starts itself" !=20 ! def __init__(self, lock):=20 ! "Initialise and start the `LockSittingThread`." ! self.lock =3D lock ! self.acquired =3D 0 ! self.keepGoing =3D 1 ! threading.Thread.__init__(self) !=20 ! # start, and wait for acquisition ! self.start() ! while not self.acquired:=20 ! time.sleep(0.1) ! =09 ! def run(self):=20 ! "In the thread: acquire the lock, wait, and release when told." ! self.lock.acquire() ! self.acquired =3D 1 ! while self.keepGoing:=20 ! time.sleep(0.1) ! self.lock.release() !=20 ! def stop(self):=20 ! "Ask the thread to stop and release, and wait for it to obey." ! self.keepGoing =3D 0 ! self.join() ! =09 ! class MultiThreadedTests(unittest.TestCase):=20 ! "Tests of multi-threaded locking behaviour." !=20 ! def test_nonblocking_acquire_of_acquired_lock(self):=20 ! "Fail non-blocking acquires of a lock acquired by another thread" ! dorothy =3D DorothyRLock() ! sitter =3D LockSittingThread(dorothy) ! self.assert_(not dorothy.acquire(0)) ! sitter.stop() !=20 ! def test_foreign_release(self):=20 ! "Catch one thread releasing a lock acquired by another thread" ! dorothy =3D DorothyRLock() ! sitter =3D LockSittingThread(dorothy) ! self.assertRaises(ReleaseFromForeignThread, dorothy.release) ! sitter.stop() =20 + # Run the tests. + unittest.main() ------=_NextPart_000_0013_01C4A16D.6CD28660--