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