Commit 59b96c10 authored by Tim Peters's avatar Tim Peters

Try to repair at least one segfault on the Mac buildbot,

as diagnosed by Nick Coghlan.

test_capi.py:  A test module should never spawn a thread as
a side effect of being imported.  Because this one did, the
segfault one of its thread tests caused didn't occur until
a few tests after test_regrtest.py thought test_capi was
finished.  Repair that.  Also join() the thread spawned
at the end, so that test_capi is truly finished when
regrtest reports that it's done.

_testcapimodule.c test_thread_state():  this spawns a
couple of non-threading.py threads, passing them a PyObject*
argument, but did nothing to ensure that those threads
finished before returning.  As a result, the PyObject*
_could_ (although this was unlikely) get decref'ed out of
existence before the threads got around to using it.
Added explicit synchronization (via a Python mutex) so
that test_thread_state can reliably wait for its spawned
threads to finish.
parent 66760f87
...@@ -5,7 +5,9 @@ import sys ...@@ -5,7 +5,9 @@ import sys
from test import test_support from test import test_support
import _testcapi import _testcapi
for name in dir(_testcapi): def test_main():
for name in dir(_testcapi):
if name.startswith('test_'): if name.startswith('test_'):
test = getattr(_testcapi, name) test = getattr(_testcapi, name)
if test_support.verbose: if test_support.verbose:
...@@ -15,8 +17,8 @@ for name in dir(_testcapi): ...@@ -15,8 +17,8 @@ for name in dir(_testcapi):
except _testcapi.error: except _testcapi.error:
raise test_support.TestFailed, sys.exc_info()[1] raise test_support.TestFailed, sys.exc_info()[1]
# some extra thread-state tests driven via _testcapi # some extra thread-state tests driven via _testcapi
def TestThreadState(): def TestThreadState():
import thread import thread
import time import time
...@@ -29,20 +31,25 @@ def TestThreadState(): ...@@ -29,20 +31,25 @@ def TestThreadState():
idents.append(thread.get_ident()) idents.append(thread.get_ident())
_testcapi._test_thread_state(callback) _testcapi._test_thread_state(callback)
a = b = callback
time.sleep(1) time.sleep(1)
# Check our main thread is in the list exactly 3 times. # Check our main thread is in the list exactly 3 times.
if idents.count(thread.get_ident()) != 3: if idents.count(thread.get_ident()) != 3:
raise test_support.TestFailed, \ raise test_support.TestFailed, \
"Couldn't find main thread correctly in the list" "Couldn't find main thread correctly in the list"
try: try:
_testcapi._test_thread_state _testcapi._test_thread_state
have_thread_state = True have_thread_state = True
except AttributeError: except AttributeError:
have_thread_state = False have_thread_state = False
if have_thread_state: if have_thread_state:
TestThreadState() TestThreadState()
import threading import threading
t=threading.Thread(target=TestThreadState) t=threading.Thread(target=TestThreadState)
t.start() t.start()
t.join()
if __name__ == "__main__":
test_main()
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
#ifdef WITH_THREAD #ifdef WITH_THREAD
#include "pythread.h" #include "pythread.h"
#endif /* WITH_THREAD */ #endif /* WITH_THREAD */
static PyObject *TestError; /* set to exception object in init */ static PyObject *TestError; /* set to exception object in init */
/* Raise TestError with test_name + ": " + msg, and return NULL. */ /* Raise TestError with test_name + ": " + msg, and return NULL. */
...@@ -583,7 +582,17 @@ raise_exception(PyObject *self, PyObject *args) ...@@ -583,7 +582,17 @@ raise_exception(PyObject *self, PyObject *args)
#ifdef WITH_THREAD #ifdef WITH_THREAD
void _make_call(void *callable) /* test_thread_state spawns a thread of its own, and that thread releases
* `thread_done` when it's finished. The driver code has to know when the
* thread finishes, because the thread uses a PyObject (the callable) that
* may go away when the driver finishes. The former lack of this explicit
* synchronization caused rare segfaults, so rare that they were seen only
* on a Mac buildbot (although they were possible on any box).
*/
static PyThread_type_lock thread_done = NULL;
static void
_make_call(void *callable)
{ {
PyObject *rc; PyObject *rc;
PyGILState_STATE s = PyGILState_Ensure(); PyGILState_STATE s = PyGILState_Ensure();
...@@ -592,32 +601,53 @@ void _make_call(void *callable) ...@@ -592,32 +601,53 @@ void _make_call(void *callable)
PyGILState_Release(s); PyGILState_Release(s);
} }
/* Same thing, but releases `thread_done` when it returns. This variant
* should be called only from threads spawned by test_thread_state().
*/
static void
_make_call_from_thread(void *callable)
{
_make_call(callable);
PyThread_release_lock(thread_done);
}
static PyObject * static PyObject *
test_thread_state(PyObject *self, PyObject *args) test_thread_state(PyObject *self, PyObject *args)
{ {
PyObject *fn; PyObject *fn;
if (!PyArg_ParseTuple(args, "O:test_thread_state", &fn)) if (!PyArg_ParseTuple(args, "O:test_thread_state", &fn))
return NULL; return NULL;
/* Ensure Python is setup for threading */
/* Ensure Python is set up for threading */
PyEval_InitThreads(); PyEval_InitThreads();
/* Start a new thread for our callback. */ thread_done = PyThread_allocate_lock();
PyThread_start_new_thread( _make_call, fn); if (thread_done == NULL)
return PyErr_NoMemory();
PyThread_acquire_lock(thread_done, 1);
/* Start a new thread with our callback. */
PyThread_start_new_thread(_make_call_from_thread, fn);
/* Make the callback with the thread lock held by this thread */ /* Make the callback with the thread lock held by this thread */
_make_call(fn); _make_call(fn);
/* Do it all again, but this time with the thread-lock released */ /* Do it all again, but this time with the thread-lock released */
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
_make_call(fn); _make_call(fn);
PyThread_acquire_lock(thread_done, 1); /* wait for thread to finish */
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
/* And once more with and without a thread /* And once more with and without a thread
XXX - should use a lock and work out exactly what we are trying XXX - should use a lock and work out exactly what we are trying
to test <wink> to test <wink>
*/ */
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
PyThread_start_new_thread( _make_call, fn); PyThread_start_new_thread(_make_call_from_thread, fn);
_make_call(fn); _make_call(fn);
PyThread_acquire_lock(thread_done, 1); /* wait for thread to finish */
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
Py_INCREF(Py_None);
return Py_None; PyThread_free_lock(thread_done);
Py_RETURN_NONE;
} }
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment