From 65c9c6426b719d885f12938a2c5ab938558406ab Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 30 Oct 2009 17:25:12 +0000 Subject: 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(). ........ --- Doc/library/_thread.rst | 13 +++++++++++++ Lib/test/support.py | 29 +++++++++++++++++------------ Lib/test/test_thread.py | 27 +++++++++++++++++++++++++++ Misc/NEWS | 6 ++++++ Modules/_threadmodule.c | 20 +++++++++++++++++++- 5 files changed, 82 insertions(+), 13 deletions(-) diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index cb62407..373b1b0 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -103,6 +103,19 @@ It defines the following constant and functions: Availability: Windows, systems with POSIX threads. +.. function:: _count() + + Return the number of currently running Python threads, excluding the main + thread. The returned number comprises all threads created through + :func:`start_new_thread` as well as :class:`threading.Thread`, and not + yet finished. + + This function is meant for internal and specialized purposes only. In + most applications :func:`threading.enumerate()` should be used instead. + + .. versionadded:: 3.2 + + Lock objects have the following methods: 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 @@ def run_doctest(module, verbosity=None): #======================================================================= # Threading support to prevent reporting refleaks when running regrtest.py -R +# 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_setup(): - import threading - return len(threading._active), len(threading._limbo) + import _thread + return _thread._count(), -def threading_cleanup(num_active, num_limbo): - import threading +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 - time.sleep(0.1) - - count = 0 - while len(threading._limbo) != num_limbo and count < _MAX_COUNT: - count += 1 + for count in range(_MAX_COUNT): + n = _thread._count() + if n == nb_threads: + break 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 @@ import random from test import support import _thread as thread import time +import weakref NUMTASKS = 10 @@ -99,6 +100,32 @@ class ThreadRunningTests(BasicThreadTest): 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): diff --git a/Misc/NEWS b/Misc/NEWS index e7384dc..8dc674a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -334,6 +334,12 @@ Documentation Tests ----- +- 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:`_thread._count()`. + - Issue #7151: fixed regrtest -j so that output to stderr from a test no longer runs the risk of causing the worker thread to fail. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 8dc2d00..81e8984 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -14,7 +14,7 @@ #include "pythread.h" static PyObject *ThreadError; - +static long nb_threads = 0; /* Lock objects */ @@ -439,6 +439,7 @@ t_bootstrap(void *boot_raw) tstate = PyThreadState_New(boot->interp); PyEval_AcquireThread(tstate); + nb_threads++; res = PyEval_CallObjectWithKeywords( boot->func, boot->args, boot->keyw); if (res == NULL) { @@ -463,6 +464,7 @@ t_bootstrap(void *boot_raw) Py_DECREF(boot->args); Py_XDECREF(boot->keyw); PyMem_DEL(boot_raw); + nb_threads--; PyThreadState_Clear(tstate); PyThreadState_DeleteCurrent(); PyThread_exit_thread(); @@ -606,6 +608,18 @@ be relied upon, and the number should be seen purely as a magic cookie.\n\ A thread's identity may be reused for another thread after it exits."); static PyObject * +thread__count(PyObject *self) +{ + return PyLong_FromLong(nb_threads); +} + +PyDoc_STRVAR(_count_doc, +"_count() -> integer\n\ +\n\ +Return the number of currently running (sub)threads.\n\ +This excludes the main thread."); + +static PyObject * thread_stack_size(PyObject *self, PyObject *args) { size_t old_size; @@ -678,6 +692,8 @@ static PyMethodDef thread_methods[] = { METH_NOARGS, interrupt_doc}, {"get_ident", (PyCFunction)thread_get_ident, METH_NOARGS, get_ident_doc}, + {"_count", (PyCFunction)thread__count, + METH_NOARGS, _count_doc}, {"stack_size", (PyCFunction)thread_stack_size, METH_VARARGS, stack_size_doc}, @@ -748,6 +764,8 @@ PyInit__thread(void) if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0) return NULL; + nb_threads = 0; + /* Initialize the C thread library */ PyThread_init_thread(); return m; -- cgit v0.12