[Python-bugs-list] Bug with Threaded exceptions? (PR#151)

jam@camros.com jam@camros.com
Tue, 7 Dec 1999 00:13:55 -0500 (EST)


Full_Name: Jeffrey A. Marshall
Version: 1.5.2
OS: solaris 2.6, linux (rh 6.0)
Submission from: phantasm.paragon-software.com (204.91.29.130)


We've run into a possible bug when using exceptions within a threaded
python program. It seems as though objects on the python execution stack
are "garbage" collected in a different thread than where the were created. 

This isn't a real problem, except we're trying to make our developer's life
easier by defining classes which have certain (thread-related) semantics  
associated with their (instance's) life-times.

Maybe this problem only shows up due to the fact that we're not catching an
exception within our top-level thread function.

I've included a script demonstrating the problem below, along with the
workaround we're currently using:

thanks,
  -jeff

----- python code follows -------------
import threading

# this is the class we've developed to help with
# making sure that a given mutex (lock) gets
# released, even when exceptions are raised
#
# The intent is that creating an instance of "BlockLock"
# acquires the given lock object
#
# And that when the BlockLock goes out of scope (or
# gets deleted) it automagically releases the lock
#
class BlockLock:
  def __init__ (self, lock):
    self.__lock = lock
    self.__thread = threading.currentThread ()
    lock.acquire ()
    print "%s.__init__: self=%s thread=%s" % (self.__class__.__name__,
                                              self,
                                              threading.currentThread ())
  def __del__ (self):
    thr = threading.currentThread ()
    if thr is not self.__thread:
      print "WARNING: releasing lock in diff. thread (%s)" % (self.__thread,)
    print "%s.__del__: self=%s thread=%s" % (self.__class__.__name__,
                                             self,
                                             thr)
    self.__lock.release ()

the_lock = threading.Lock ()

def my_buggy_thread_fun ():
  # buggy function, showing that the __del__ method in block lock
  # is 
  a = BlockLock (the_lock)
  assert 0

def my_fixed_thread_fun ():
  # Same as above, our workaround is to always place
  # a `try'/`finally' block around all the code... Doing
  # this almost defeats the whole purpose of having
  # the BlockLock class...
  a = BlockLock (the_lock)
  try:
    assert 0
  finally:
    del a


def show_bug ():
  thr = threading.Thread (target=my_buggy_thread_fun)
  print "starting bad thread: ", `thr`
  thr.start ()
  thr.join ()
  print "bad thread exited"

  thr = threading.Thread (target=my_fixed_thread_fun)
  print "starting fixed thread: ", `thr`
  thr.start ()
  thr.join ()
  print "fixed thread exited"


if __name__ == '__main__':
  show_bug ()