enthought/traits — pull request #66 by mdickinson (2013-07-04 08:12:58+00:00)
Title: Fix race condition in TraitChangeNotifyWrapper.listener_deleted

This PR fixes a minor issue from race condition in `TraitChangeNotifyWrapper`. That class has a `dispose` method and a `listener_deleted` method. In normal use, only one of those methods gets called: `dispose` if the traits listener is explicitly removed, and `listener_deleted` (as a weakref callback) if the handler object's reference count reaches zero. However, in multithreaded situations, it's possible for both of these methods to be called.

The code below demonstrates the issue. When executed, it sends the following output to `sys.stderr`:

```
Exception ValueError: 'list.remove(x): x not in list' in > ignored
```

Note that no actual exception is raised in either thread: because the exception occurs during garbage collection, Python ignores it and just sends the above warning message to stderr.

```
import threading
import time

from traits.api import Event, HasStrictTraits

class Handler(HasStrictTraits):
def event_handler(self):
time.sleep(2.0)

class Target(HasStrictTraits):
foo = Event

def worker_thread(obj, start):
start.wait()
obj.foo = True

def main_thread(obj, start):
handler = Handler()
obj.on_trait_change(handler.event_handler, 'foo')
start.set()
time.sleep(1.0)
obj.on_trait_change(handler.event_handler, 'foo', remove=True)

def main():
start = threading.Event()
target = Target()
t = threading.Thread(target=worker_thread, args=(target, start))
t.start()
main_thread(target, start)
t.join()

if __name__ == '__main__':
main()
```