Skip to content

Commit 66f017b

Browse files
author
Anselm Kruis
committed
Issue python#120: Correctly grab the head_mutex
Stackless now correctly grabs and releases the head_mutex, if it iterates over the linked list of Py_ThreadState structures. https://bitbucket.org/stackless-dev/stackless/issues/120 (grafted from 1fb75d58a98083b2547746adb21fa238bbf8135a)
1 parent ade1fae commit 66f017b

7 files changed

Lines changed: 121 additions & 22 deletions

File tree

Python/pystate.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ static int autoTLSkey = 0;
4848
#define HEAD_UNLOCK() /* Nothing */
4949
#endif
5050

51+
#ifdef STACKLESS
52+
#ifdef WITH_THREAD
53+
void
54+
slp_head_lock(void) {
55+
HEAD_LOCK();
56+
}
57+
58+
void
59+
slp_head_unlock(void) {
60+
HEAD_UNLOCK();
61+
}
62+
#endif
63+
#endif
64+
5165
static PyInterpreterState *interp_head = NULL;
5266

5367
/* Assuming the current thread holds the GIL, this is the

Stackless/changelog.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ What's New in Stackless 3.X.X?
1414
weakref-callback runs Python code, Python used to leak a reference to a
1515
C-stack object.
1616

17+
- https://bitbucket.org/stackless-dev/stackless/issues/120
18+
Stackless now correctly grabs the head_mutex, when it iterates over the list
19+
of thread states.
20+
1721
- https://bitbucket.org/stackless-dev/stackless/issues/119
1822
Fix a rare bug in the stack unwinding mechanism, that caused a SystemError
1923
exception or an assertion violation, if a __del__()-method or a weakref

Stackless/core/stackless_impl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,17 @@ PyObject * slp_get_channel_callback(void);
788788
(task)->flags.ignore_nesting || \
789789
(ts->st.runflags & PY_WATCHDOG_IGNORE_NESTING))
790790

791+
/* Interpreter shutdown and thread state access */
792+
PyObject * slp_getthreads(PyObject *self);
793+
#ifdef WITH_THREAD
794+
void slp_head_lock(void);
795+
void slp_head_unlock(void);
796+
#define SLP_HEAD_LOCK() slp_head_lock()
797+
#define SLP_HEAD_UNLOCK() slp_head_unlock()
798+
#else
799+
#define SLP_HEAD_LOCK() assert(1)
800+
#define SLP_HEAD_UNLOCK() assert(1)
801+
#endif
791802

792803
#include "stackless_api.h"
793804

Stackless/core/stacklesseval.c

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -380,44 +380,61 @@ slp_eval_frame(PyFrameObject *f)
380380
}
381381

382382
static void
383-
kill_pending_current_main_and_watchdogs(PyThreadState *ts)
383+
get_current_main_and_watchdogs(PyThreadState *ts, PyObject *list)
384384
{
385385
PyTaskletObject *t;
386386

387387
assert(ts != PyThreadState_GET()); /* don't kill ourself */
388+
assert(PyList_CheckExact(list));
388389

389390
/* kill watchdogs */
390391
if (ts->st.watchdogs && PyList_CheckExact(ts->st.watchdogs)) {
391392
Py_ssize_t i;
392393
/* we don't kill the "intterupt" slot, number 0 */
393394
for(i = PyList_GET_SIZE(ts->st.watchdogs) - 1; i > 0; i--) {
394-
PyObject * item = PyList_GET_ITEM(ts->st.watchdogs, i);
395+
PyObject *item = PyList_GET_ITEM(ts->st.watchdogs, i);
395396
assert(item && PyTasklet_Check(item));
396-
t = (PyTaskletObject *) item;
397-
Py_INCREF(t); /* it is a borrowed ref */
398-
PyTasklet_KillEx(t, 1);
397+
Py_INCREF(item); /* it is a borrowed ref */
398+
PyList_Append(list, item);
399399
PyErr_Clear();
400-
Py_DECREF(t);
400+
Py_DECREF(item);
401401
}
402402
}
403403
/* kill main */
404404
t = ts->st.main;
405405
if (t != NULL) {
406406
Py_INCREF(t); /* it is a borrowed ref */
407-
PyTasklet_KillEx(t, 1);
407+
PyList_Append(list, (PyObject *)t);
408408
PyErr_Clear();
409409
Py_DECREF(t);
410410
}
411411
/* kill current */
412412
t = ts->st.current;
413413
if (t != NULL) {
414414
Py_INCREF(t); /* it is a borrowed ref */
415-
PyTasklet_KillEx(t, 1);
415+
PyList_Append(list, (PyObject *)t);
416416
PyErr_Clear();
417417
Py_DECREF(t);
418418
}
419419
}
420420

421+
static void
422+
kill_pending(PyObject *list)
423+
{
424+
Py_ssize_t i, len;
425+
426+
assert(list && PyList_CheckExact(list));
427+
428+
len = PyList_GET_SIZE(list);
429+
for (i=0; i < len; i++) {
430+
PyTaskletObject *t = (PyTaskletObject *) PyList_GET_ITEM(list, i);
431+
assert(PyTasklet_Check(t));
432+
PyTasklet_KillEx(t, 1);
433+
PyErr_Clear();
434+
assert(len == PyList_GET_SIZE(list));
435+
}
436+
}
437+
421438
static void
422439
run_other_threads(PyObject **sleep, Py_ssize_t count)
423440
{
@@ -580,7 +597,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
580597
other_threads:
581598
/* Step II of III
582599
*
583-
* A separate simple loop to kill tasklets on foreign threads.
600+
* Kill tasklets on foreign threads:.
584601
* Since foreign tasklets are scheduled in their own good time,
585602
* there is no guarantee that they are actually dead when we
586603
* exit this function. Therefore, we also can't clear their thread
@@ -591,20 +608,57 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
591608
PyTaskletObject *t;
592609
PyObject *sleepfunc = NULL;
593610
Py_ssize_t count;
611+
PyObject *tasklet_list = PyList_New(0);
612+
if (tasklet_list == NULL) {
613+
PyErr_Clear();
614+
}
594615

595-
/* other threads, first pass: kill (pending) current, main and watchdog tasklets */
596-
if (target_ts == NULL) {
616+
/* Other threads, first pass: kill (pending) current, main and watchdog tasklets
617+
* Iterating over the threads requires the HEAD lock. In order to prevent dead locks,
618+
* we try to avoid interpreter recursions (= no Py_DECREF), while we hold the lock.
619+
*/
620+
if (target_ts == NULL && tasklet_list) {
597621
PyThreadState *ts;
622+
PyObject *threadid_list = NULL;
598623
count = 0;
624+
625+
/* build a list of tasklets to be killed */
626+
SLP_HEAD_LOCK();
599627
for (ts = cts->interp->tstate_head; ts != NULL; ts = ts->next) {
600628
if (ts != cts) {
601629
/* Inactivate thread ts. In case the thread is active,
602630
* it will be killed. If the thread is sleping, it
603631
* continues to sleep.
604632
*/
605633
count++;
606-
kill_pending_current_main_and_watchdogs(ts);
634+
get_current_main_and_watchdogs(ts, tasklet_list);
635+
}
636+
}
637+
SLP_HEAD_UNLOCK();
638+
639+
/* get the list of thread ids */
640+
if (PyExc_TaskletExit)
641+
threadid_list = slp_getthreads(Py_None); /* requires the HEAD lock */
642+
643+
/* kill the tasklets */
644+
kill_pending(tasklet_list);
645+
/* kill the threads */
646+
if (threadid_list != NULL) {
647+
Py_ssize_t i, len;
648+
assert(PyList_CheckExact(threadid_list));
649+
len = PyList_GET_SIZE(threadid_list);
650+
for (i=0; i < len; i++) {
651+
long thread_id;
652+
PyObject *item = PyList_GET_ITEM(threadid_list, i);
653+
assert(PyLong_CheckExact(item));
654+
thread_id = PyLong_AsLong(item);
655+
if (thread_id != cts->thread_id) {
656+
/* requires the HEAD lock */
657+
PyThreadState_SetAsyncExc(thread_id, PyExc_TaskletExit);
658+
PyErr_Clear();
659+
}
607660
}
661+
Py_DECREF(threadid_list);
608662
}
609663
/* We must not release the GIL while we might hold the HEAD-lock.
610664
* Otherwise another thread (usually the thread of the killed tasklet)
@@ -615,10 +669,12 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
615669
/* The other threads might have modified the thread state chain, but fortunately we
616670
* are done with it.
617671
*/
618-
} else if (target_ts != cts) {
619-
kill_pending_current_main_and_watchdogs(target_ts);
672+
} else if (target_ts != cts && tasklet_list) {
673+
get_current_main_and_watchdogs(target_ts, tasklet_list);
674+
kill_pending(tasklet_list);
620675
/* Here it is not safe to release the GIL. */
621676
}
677+
Py_XDECREF(tasklet_list);
622678

623679
/* other threads, second pass: kill tasklets with nesting-level > 0 and
624680
* clear tstate if target_ts != NULL && target_ts != cts. */

Stackless/module/scheduling.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,10 +687,14 @@ check_for_deadlock(void)
687687
PyInterpreterState *interp = ts->interp;
688688

689689
/* see if anybody else will be able to run */
690-
691-
for (ts = interp->tstate_head; ts != NULL; ts = ts->next)
692-
if (is_thread_runnable(ts))
690+
SLP_HEAD_LOCK();
691+
for (ts = interp->tstate_head; ts != NULL; ts = ts->next) {
692+
if (is_thread_runnable(ts)) {
693+
SLP_HEAD_UNLOCK();
693694
return 0;
695+
}
696+
}
697+
SLP_HEAD_UNLOCK();
694698
return 1;
695699
}
696700

Stackless/module/stacklessmodule.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -732,10 +732,12 @@ get_thread_info(PyObject *self, PyObject *args)
732732
if (!PyArg_ParseTuple(args, "|lk:get_thread_info", &id, &flags))
733733
return NULL;
734734
if (id != -1) {
735+
SLP_HEAD_LOCK();
735736
for (ts = interp->tstate_head; id && ts != NULL; ts = ts->next) {
736737
if (ts->thread_id == id)
737738
break;
738739
}
740+
SLP_HEAD_UNLOCK();
739741
if (ts == NULL)
740742
RUNTIME_ERROR("Thread id not found", NULL);
741743
}
@@ -1529,8 +1531,8 @@ These might need to be killed manually in order to free memory,\n\
15291531
since their C stack might prevent garbage collection.\n\
15301532
Note that a tasklet is reported for every C stacks it has.");
15311533

1532-
static PyObject *
1533-
slpmodule_getthreads(PyObject *self)
1534+
PyObject *
1535+
slp_getthreads(PyObject *self)
15341536
{
15351537
PyObject *lis = PyList_New(0);
15361538
PyThreadState *ts = PyThreadState_GET();
@@ -1539,20 +1541,27 @@ slpmodule_getthreads(PyObject *self)
15391541
if (lis == NULL)
15401542
return NULL;
15411543

1544+
SLP_HEAD_LOCK();
15421545
for (ts = interp->tstate_head; ts != NULL; ts = ts->next) {
15431546
PyObject *id = PyLong_FromLong(ts->thread_id);
15441547
if (id == NULL) {
1548+
SLP_HEAD_UNLOCK();
15451549
Py_DECREF(lis);
15461550
return NULL;
15471551
}
15481552
if (PyList_Append(lis, id)) {
1553+
SLP_HEAD_UNLOCK();
15491554
Py_DECREF(lis);
15501555
Py_DECREF(id);
15511556
return NULL;
15521557
}
15531558
Py_DECREF(id);
15541559
}
1555-
PyList_Reverse(lis);
1560+
SLP_HEAD_UNLOCK();
1561+
if(PyList_Reverse(lis)) {
1562+
Py_DECREF(lis);
1563+
return NULL;
1564+
}
15561565
return lis;
15571566
}
15581567

@@ -1625,7 +1634,7 @@ static PyMethodDef stackless_methods[] = {
16251634
slpmodule_getdebug__doc__},
16261635
{"getuncollectables", (PCF)slpmodule_getuncollectables, METH_NOARGS,
16271636
slpmodule_getuncollectables__doc__},
1628-
{"getthreads", (PCF)slpmodule_getthreads, METH_NOARGS,
1637+
{"getthreads", (PCF)slp_getthreads, METH_NOARGS,
16291638
slpmodule_getthreads__doc__},
16301639
{NULL, NULL} /* sentinel */
16311640
};

Stackless/unittests/test_shutdown.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def test_other_thread_Py_Exit(self):
360360
361361
def exit():
362362
# print("Calling Py_Exit(42)")
363+
time.sleep(0.1)
363364
sys.stdout.flush()
364365
Py_Exit(42)
365366
@@ -545,7 +546,7 @@ def __init__(self, out, checks, tasklets):
545546
self.checks = checks
546547
self.tasklets = tasklets
547548

548-
# In Py_Finalize() the PyImport_Cleanup() runs shortly after
549+
# In Py_Finalize() the PyImport_Cleanup() runs shortly after
549550
# slp_kill_tasks_with_stacks(NULL).
550551
# As very first action of PyImport_Cleanup() the Python
551552
# interpreter sets builtins._ to None.

0 commit comments

Comments
 (0)