summaryrefslogtreecommitdiffstats
path: root/Modules/_testcapimodule.c
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2006-03-21 03:58:41 (GMT)
committerTim Peters <tim.peters@gmail.com>2006-03-21 03:58:41 (GMT)
commit59b96c1029290822b7069634fce4628b19b2d4ca (patch)
treeddc6d1734285e2c68e80e9d849cf62f529b88cab /Modules/_testcapimodule.c
parent66760f87b51662d95a0d13226712d83a7ab049f8 (diff)
downloadcpython-59b96c1029290822b7069634fce4628b19b2d4ca.zip
cpython-59b96c1029290822b7069634fce4628b19b2d4ca.tar.gz
cpython-59b96c1029290822b7069634fce4628b19b2d4ca.tar.bz2
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.
Diffstat (limited to 'Modules/_testcapimodule.c')
-rw-r--r--Modules/_testcapimodule.c54
1 files changed, 42 insertions, 12 deletions
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 263c61e..60c71d7 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -10,7 +10,6 @@
#ifdef WITH_THREAD
#include "pythread.h"
#endif /* WITH_THREAD */
-
static PyObject *TestError; /* set to exception object in init */
/* Raise TestError with test_name + ": " + msg, and return NULL. */
@@ -482,7 +481,7 @@ static
PyObject *codec_incrementalencoder(PyObject *self, PyObject *args)
{
const char *encoding, *errors = NULL;
- if (!PyArg_ParseTuple(args, "s|s:test_incrementalencoder",
+ if (!PyArg_ParseTuple(args, "s|s:test_incrementalencoder",
&encoding, &errors))
return NULL;
return PyCodec_IncrementalEncoder(encoding, errors);
@@ -492,7 +491,7 @@ static
PyObject *codec_incrementaldecoder(PyObject *self, PyObject *args)
{
const char *encoding, *errors = NULL;
- if (!PyArg_ParseTuple(args, "s|s:test_incrementaldecoder",
+ if (!PyArg_ParseTuple(args, "s|s:test_incrementaldecoder",
&encoding, &errors))
return NULL;
return PyCodec_IncrementalDecoder(encoding, errors);
@@ -583,7 +582,17 @@ raise_exception(PyObject *self, PyObject *args)
#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;
PyGILState_STATE s = PyGILState_Ensure();
@@ -592,32 +601,53 @@ void _make_call(void *callable)
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 *
test_thread_state(PyObject *self, PyObject *args)
{
PyObject *fn;
+
if (!PyArg_ParseTuple(args, "O:test_thread_state", &fn))
return NULL;
- /* Ensure Python is setup for threading */
+
+ /* Ensure Python is set up for threading */
PyEval_InitThreads();
- /* Start a new thread for our callback. */
- PyThread_start_new_thread( _make_call, fn);
+ thread_done = PyThread_allocate_lock();
+ 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_call(fn);
/* Do it all again, but this time with the thread-lock released */
Py_BEGIN_ALLOW_THREADS
_make_call(fn);
+ PyThread_acquire_lock(thread_done, 1); /* wait for thread to finish */
Py_END_ALLOW_THREADS
+
/* And once more with and without a thread
- XXX - should use a lock and work out exactly what we are trying
- to test <wink>
+ XXX - should use a lock and work out exactly what we are trying
+ to test <wink>
*/
Py_BEGIN_ALLOW_THREADS
- PyThread_start_new_thread( _make_call, fn);
+ PyThread_start_new_thread(_make_call_from_thread, fn);
_make_call(fn);
+ PyThread_acquire_lock(thread_done, 1); /* wait for thread to finish */
Py_END_ALLOW_THREADS
- Py_INCREF(Py_None);
- return Py_None;
+
+ PyThread_free_lock(thread_done);
+ Py_RETURN_NONE;
}
#endif