Merged revisions 75958 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r75958 | antoine.pitrou | 2009-10-30 18:07:08 +0100 (ven., 30 oct. 2009) | 7 lines

  Issue #7222: Make thread "reaping" more reliable so that reference
  leak-chasing test runs give sensible results. The previous method of
  reaping threads could return successfully while some Thread objects were
  still referenced. This also introduces a new private function:
  :func:hread._count().
........
diff --git a/Lib/test/support.py b/Lib/test/support.py
index 0f3e2ee..a7cac4a 100644
--- a/Lib/test/support.py
+++ b/Lib/test/support.py
@@ -947,24 +947,29 @@
 #=======================================================================
 # Threading support to prevent reporting refleaks when running regrtest.py -R
 
-def threading_setup():
-    import threading
-    return len(threading._active), len(threading._limbo)
+# NOTE: we use thread._count() rather than threading.enumerate() (or the
+# moral equivalent thereof) because a threading.Thread object is still alive
+# until its __bootstrap() method has returned, even after it has been
+# unregistered from the threading module.
+# thread._count(), on the other hand, only gets decremented *after* the
+# __bootstrap() method has returned, which gives us reliable reference counts
+# at the end of a test run.
 
-def threading_cleanup(num_active, num_limbo):
-    import threading
+def threading_setup():
+    import _thread
+    return _thread._count(),
+
+def threading_cleanup(nb_threads):
+    import _thread
     import time
 
     _MAX_COUNT = 10
-    count = 0
-    while len(threading._active) != num_active and count < _MAX_COUNT:
-        count += 1
+    for count in range(_MAX_COUNT):
+        n = _thread._count()
+        if n == nb_threads:
+            break
         time.sleep(0.1)
-
-    count = 0
-    while len(threading._limbo) != num_limbo and count < _MAX_COUNT:
-        count += 1
-        time.sleep(0.1)
+    # XXX print a warning in case of failure?
 
 def reap_threads(func):
     @functools.wraps(func)
diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py
index 73d87b8..c25668d 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -4,6 +4,7 @@
 from test import support
 import _thread as thread
 import time
+import weakref
 
 
 NUMTASKS = 10
@@ -99,6 +100,32 @@
 
             thread.stack_size(0)
 
+    def test__count(self):
+        # Test the _count() function.
+        orig = thread._count()
+        mut = thread.allocate_lock()
+        mut.acquire()
+        started = []
+        def task():
+            started.append(None)
+            mut.acquire()
+            mut.release()
+        thread.start_new_thread(task, ())
+        while not started:
+            time.sleep(0.01)
+        self.assertEquals(thread._count(), orig + 1)
+        # Allow the task to finish.
+        mut.release()
+        # The only reliable way to be sure that the thread ended from the
+        # interpreter's point of view is to wait for the function object to be
+        # destroyed.
+        done = []
+        wr = weakref.ref(task, lambda _: done.append(None))
+        del task
+        while not done:
+            time.sleep(0.01)
+        self.assertEquals(thread._count(), orig)
+
 
 class Barrier:
     def __init__(self, num_threads):