mirror of https://github.com/python/cpython
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:
parent
93c2171494
commit
59c44f36e0
|
@ -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:
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue