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().
This commit is contained in:
Antoine Pitrou 2009-10-30 17:07:08 +00:00
parent 93c2171494
commit 59c44f36e0
5 changed files with 84 additions and 14 deletions

View File

@ -112,6 +112,20 @@ It defines the following constant and functions:
.. versionadded:: 2.5 .. versionadded:: 2.5
.. 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:: 2.7
Lock objects have the following methods: Lock objects have the following methods:

View File

@ -952,24 +952,29 @@ def run_doctest(module, verbosity=None):
#======================================================================= #=======================================================================
# Threading support to prevent reporting refleaks when running regrtest.py -R # Threading support to prevent reporting refleaks when running regrtest.py -R
def threading_setup(): # NOTE: we use thread._count() rather than threading.enumerate() (or the
import threading # moral equivalent thereof) because a threading.Thread object is still alive
return len(threading._active), len(threading._limbo) # 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): def threading_setup():
import threading import thread
return thread._count(),
def threading_cleanup(nb_threads):
import thread
import time import time
_MAX_COUNT = 10 _MAX_COUNT = 10
count = 0 for count in range(_MAX_COUNT):
while len(threading._active) != num_active and count < _MAX_COUNT: n = thread._count()
count += 1 if n == nb_threads:
time.sleep(0.1) break
count = 0
while len(threading._limbo) != num_limbo and count < _MAX_COUNT:
count += 1
time.sleep(0.1) time.sleep(0.1)
# XXX print a warning in case of failure?
def reap_threads(func): def reap_threads(func):
@functools.wraps(func) @functools.wraps(func)

View File

@ -4,6 +4,7 @@ import random
from test import test_support from test import test_support
import thread import thread
import time import time
import weakref
NUMTASKS = 10 NUMTASKS = 10
@ -101,6 +102,32 @@ class ThreadRunningTests(BasicThreadTest):
thread.stack_size(0) 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: class Barrier:
def __init__(self, num_threads): def __init__(self, num_threads):

View File

@ -1525,6 +1525,12 @@ Extension Modules
Tests 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 - 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. longer runs the risk of causing the worker thread to fail.

View File

@ -14,7 +14,7 @@
#include "pythread.h" #include "pythread.h"
static PyObject *ThreadError; static PyObject *ThreadError;
static long nb_threads = 0;
/* Lock objects */ /* Lock objects */
@ -439,6 +439,7 @@ t_bootstrap(void *boot_raw)
tstate = PyThreadState_New(boot->interp); tstate = PyThreadState_New(boot->interp);
PyEval_AcquireThread(tstate); PyEval_AcquireThread(tstate);
nb_threads++;
res = PyEval_CallObjectWithKeywords( res = PyEval_CallObjectWithKeywords(
boot->func, boot->args, boot->keyw); boot->func, boot->args, boot->keyw);
if (res == NULL) { if (res == NULL) {
@ -463,6 +464,7 @@ t_bootstrap(void *boot_raw)
Py_DECREF(boot->args); Py_DECREF(boot->args);
Py_XDECREF(boot->keyw); Py_XDECREF(boot->keyw);
PyMem_DEL(boot_raw); PyMem_DEL(boot_raw);
nb_threads--;
PyThreadState_Clear(tstate); PyThreadState_Clear(tstate);
PyThreadState_DeleteCurrent(); PyThreadState_DeleteCurrent();
PyThread_exit_thread(); PyThread_exit_thread();
@ -605,6 +607,18 @@ allocated consecutive numbers starting at 1, this behavior should not\n\
be relied upon, and the number should be seen purely as a magic cookie.\n\ 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."); A thread's identity may be reused for another thread after it exits.");
static PyObject *
thread__count(PyObject *self)
{
return PyInt_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 * static PyObject *
thread_stack_size(PyObject *self, PyObject *args) thread_stack_size(PyObject *self, PyObject *args)
{ {
@ -678,6 +692,8 @@ static PyMethodDef thread_methods[] = {
METH_NOARGS, interrupt_doc}, METH_NOARGS, interrupt_doc},
{"get_ident", (PyCFunction)thread_get_ident, {"get_ident", (PyCFunction)thread_get_ident,
METH_NOARGS, get_ident_doc}, METH_NOARGS, get_ident_doc},
{"_count", (PyCFunction)thread__count,
METH_NOARGS, _count_doc},
{"stack_size", (PyCFunction)thread_stack_size, {"stack_size", (PyCFunction)thread_stack_size,
METH_VARARGS, METH_VARARGS,
stack_size_doc}, stack_size_doc},
@ -735,6 +751,8 @@ initthread(void)
if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0) if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0)
return; return;
nb_threads = 0;
/* Initialize the C thread library */ /* Initialize the C thread library */
PyThread_init_thread(); PyThread_init_thread();
} }