Skip to content

Commit b531101

Browse files
author
Anselm Kruis
committed
merge 3.3-slp (Stackless python#120)
2 parents a740da9 + 66f017b commit b531101

File tree

7 files changed

+121
-22
lines changed

7 files changed

+121
-22
lines changed

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
@@ -19,6 +19,10 @@ What's New in Stackless 3.X.X?
1919
weakref-callback runs Python code, Python used to leak a reference to a
2020
C-stack object.
2121

22+
- https://bitbucket.org/stackless-dev/stackless/issues/120
23+
Stackless now correctly grabs the head_mutex, when it iterates over the list
24+
of thread states.
25+
2226
- https://bitbucket.org/stackless-dev/stackless/issues/119
2327
Fix a rare bug in the stack unwinding mechanism, that caused a SystemError
2428
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
@@ -384,44 +384,61 @@ slp_eval_frame(PyFrameObject *f)
384384
}
385385

386386
static void
387-
kill_pending_current_main_and_watchdogs(PyThreadState *ts)
387+
get_current_main_and_watchdogs(PyThreadState *ts, PyObject *list)
388388
{
389389
PyTaskletObject *t;
390390

391391
assert(ts != PyThreadState_GET()); /* don't kill ourself */
392+
assert(PyList_CheckExact(list));
392393

393394
/* kill watchdogs */
394395
if (ts->st.watchdogs && PyList_CheckExact(ts->st.watchdogs)) {
395396
Py_ssize_t i;
396397
/* we don't kill the "intterupt" slot, number 0 */
397398
for(i = PyList_GET_SIZE(ts->st.watchdogs) - 1; i > 0; i--) {
398-
PyObject * item = PyList_GET_ITEM(ts->st.watchdogs, i);
399+
PyObject *item = PyList_GET_ITEM(ts->st.watchdogs, i);
399400
assert(item && PyTasklet_Check(item));
400-
t = (PyTaskletObject *) item;
401-
Py_INCREF(t); /* it is a borrowed ref */
402-
PyTasklet_KillEx(t, 1);
401+
Py_INCREF(item); /* it is a borrowed ref */
402+
PyList_Append(list, item);
403403
PyErr_Clear();
404-
Py_DECREF(t);
404+
Py_DECREF(item);
405405
}
406406
}
407407
/* kill main */
408408
t = ts->st.main;
409409
if (t != NULL) {
410410
Py_INCREF(t); /* it is a borrowed ref */
411-
PyTasklet_KillEx(t, 1);
411+
PyList_Append(list, (PyObject *)t);
412412
PyErr_Clear();
413413
Py_DECREF(t);
414414
}
415415
/* kill current */
416416
t = ts->st.current;
417417
if (t != NULL) {
418418
Py_INCREF(t); /* it is a borrowed ref */
419-
PyTasklet_KillEx(t, 1);
419+
PyList_Append(list, (PyObject *)t);
420420
PyErr_Clear();
421421
Py_DECREF(t);
422422
}
423423
}
424424

425+
static void
426+
kill_pending(PyObject *list)
427+
{
428+
Py_ssize_t i, len;
429+
430+
assert(list && PyList_CheckExact(list));
431+
432+
len = PyList_GET_SIZE(list);
433+
for (i=0; i < len; i++) {
434+
PyTaskletObject *t = (PyTaskletObject *) PyList_GET_ITEM(list, i);
435+
assert(PyTasklet_Check(t));
436+
PyTasklet_KillEx(t, 1);
437+
PyErr_Clear();
438+
assert(len == PyList_GET_SIZE(list));
439+
}
440+
}
441+
425442
static void
426443
run_other_threads(PyObject **sleep, Py_ssize_t count)
427444
{
@@ -584,7 +601,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
584601
other_threads:
585602
/* Step II of III
586603
*
587-
* A separate simple loop to kill tasklets on foreign threads.
604+
* Kill tasklets on foreign threads:.
588605
* Since foreign tasklets are scheduled in their own good time,
589606
* there is no guarantee that they are actually dead when we
590607
* exit this function. Therefore, we also can't clear their thread
@@ -595,20 +612,57 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
595612
PyTaskletObject *t;
596613
PyObject *sleepfunc = NULL;
597614
Py_ssize_t count;
615+
PyObject *tasklet_list = PyList_New(0);
616+
if (tasklet_list == NULL) {
617+
PyErr_Clear();
618+
}
598619

599-
/* other threads, first pass: kill (pending) current, main and watchdog tasklets */
600-
if (target_ts == NULL) {
620+
/* Other threads, first pass: kill (pending) current, main and watchdog tasklets
621+
* Iterating over the threads requires the HEAD lock. In order to prevent dead locks,
622+
* we try to avoid interpreter recursions (= no Py_DECREF), while we hold the lock.
623+
*/
624+
if (target_ts == NULL && tasklet_list) {
601625
PyThreadState *ts;
626+
PyObject *threadid_list = NULL;
602627
count = 0;
628+
629+
/* build a list of tasklets to be killed */
630+
SLP_HEAD_LOCK();
603631
for (ts = cts->interp->tstate_head; ts != NULL; ts = ts->next) {
604632
if (ts != cts) {
605633
/* Inactivate thread ts. In case the thread is active,
606634
* it will be killed. If the thread is sleping, it
607635
* continues to sleep.
608636
*/
609637
count++;
610-
kill_pending_current_main_and_watchdogs(ts);
638+
get_current_main_and_watchdogs(ts, tasklet_list);
639+
}
640+
}
641+
SLP_HEAD_UNLOCK();
642+
643+
/* get the list of thread ids */
644+
if (PyExc_TaskletExit)
645+
threadid_list = slp_getthreads(Py_None); /* requires the HEAD lock */
646+
647+
/* kill the tasklets */
648+
kill_pending(tasklet_list);
649+
/* kill the threads */
650+
if (threadid_list != NULL) {
651+
Py_ssize_t i, len;
652+
assert(PyList_CheckExact(threadid_list));
653+
len = PyList_GET_SIZE(threadid_list);
654+
for (i=0; i < len; i++) {
655+
long thread_id;
656+
PyObject *item = PyList_GET_ITEM(threadid_list, i);
657+
assert(PyLong_CheckExact(item));
658+
thread_id = PyLong_AsLong(item);
659+
if (thread_id != cts->thread_id) {
660+
/* requires the HEAD lock */
661+
PyThreadState_SetAsyncExc(thread_id, PyExc_TaskletExit);
662+
PyErr_Clear();
663+
}
611664
}
665+
Py_DECREF(threadid_list);
612666
}
613667
/* We must not release the GIL while we might hold the HEAD-lock.
614668
* Otherwise another thread (usually the thread of the killed tasklet)
@@ -619,10 +673,12 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
619673
/* The other threads might have modified the thread state chain, but fortunately we
620674
* are done with it.
621675
*/
622-
} else if (target_ts != cts) {
623-
kill_pending_current_main_and_watchdogs(target_ts);
676+
} else if (target_ts != cts && tasklet_list) {
677+
get_current_main_and_watchdogs(target_ts, tasklet_list);
678+
kill_pending(tasklet_list);
624679
/* Here it is not safe to release the GIL. */
625680
}
681+
Py_XDECREF(tasklet_list);
626682

627683
/* other threads, second pass: kill tasklets with nesting-level > 0 and
628684
* 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)