Skip to content

Commit 19b7b19

Browse files
author
Anselm Kruis
committed
merge 3.4-slp (Stackless python#103)
2 parents b5d9f15 + 126fa99 commit 19b7b19

File tree

4 files changed

+367
-78
lines changed

4 files changed

+367
-78
lines changed

Stackless/changelog.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ What's New in Stackless 3.X.X?
1313
Initialise the global variable slp_initial_tstate early before it is used.
1414

1515
- https://bitbucket.org/stackless-dev/stackless/issues/103
16-
Fix an invalid assertion during interpreter shutdown.
16+
Improve the interpreter shutdown, if deeply nested
17+
tasklets are present. Previously you some assertions could fail.
1718

1819
- https://bitbucket.org/stackless-dev/stackless/issues/101
1920
Enhance Tools/gdb/libpython.py to support debugging Stackless Python.

Stackless/core/stacklesseval.c

+114-43
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,15 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
425425
int in_loop = 0;
426426

427427
if (target_ts == NULL || target_ts == cts) {
428-
/* a loop to kill tasklets on the local thread */
428+
/* Step I of III
429+
* A loop to kill tasklets on the current thread.
430+
*
431+
* Plan:
432+
* - loop over all cstacks
433+
* - if a cstack belongs to the current thread and is the
434+
* cstack of a tasklet, eventually kill the tasklet. Then remove the
435+
* tstate of all cstacks, which still belong to the killed tasklet.
436+
*/
429437
while (1) {
430438
PyCStackObject *csfirst = slp_cstack_chain, *cs;
431439
PyTaskletObject *t;
@@ -442,59 +450,95 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
442450
if (cs->tstate != cts)
443451
continue;
444452

445-
if (cs->task == NULL) {
446-
cs->tstate = NULL;
453+
/* here we are looking for tasks only */
454+
if (cs->task == NULL)
447455
continue;
448-
}
449-
/* is it already dead? */
450-
if (cs->task->f.frame == NULL) {
451-
cs->tstate = NULL;
456+
457+
/* Do not damage the initial stub */
458+
assert(cs != cts->st.initial_stub);
459+
460+
/* is it the current cstack of the tasklet */
461+
if (cs->task->cstate != cs)
462+
continue;
463+
464+
/* Do not damage the current tasklet of the current thread.
465+
* Otherwise we fail to kill other tasklets.
466+
* Unfortunately cts->st.current is only valid, if
467+
* cts->st.main != NULL.
468+
*
469+
* Why? When the main tasklet ends, the function
470+
* tasklet_end(PyObject *retval) calls slp_current_remove()
471+
* for the main tasklet. This call sets tstate->st.current to
472+
* the next scheduled tasklet. Then tasklet_end() cleans up
473+
* the main tasklet and returns.
474+
*/
475+
if (cts->st.main != NULL && cs->task == cts->st.current) {
452476
continue;
453477
}
478+
454479
break;
455480
}
456-
in_loop = 0;
457481
t = cs->task;
458482
Py_INCREF(t); /* cs->task is a borrowed ref */
459-
assert(t->cstate == cs);
460-
461-
/* If a thread ends, the thread no longer has a main tasklet and
462-
* the thread is not in a valid state. tstate->st.current is
463-
* undefined. It may point to a tasklet, but the other fields in
464-
* tstate have wrong values.
465-
*
466-
* Therefore we need to ensure, that t is not tstate->st.current.
467-
* Convert t into a free floating tasklet. PyTasklet_Kill works
468-
* for floating tasklets too.
469-
*/
470-
if (t->next && !t->flags.blocked) {
471-
assert(t->prev);
472-
slp_current_remove_tasklet(t);
473-
assert(Py_REFCNT(t) > 1);
474-
Py_DECREF(t);
475-
assert(t->next == NULL);
476-
assert(t->prev == NULL);
477-
}
478-
assert(t != cs->tstate->st.current);
479-
480-
/* has the tasklet nesting_level > 0? The Stackles documentation
481-
* specifies: "When a thread dies, only tasklets with a C-state are actively killed.
482-
* Soft-switched tasklets simply stop."
483-
*/
484-
if ((cts->st.current == cs->task ? cts->st.nesting_level : cs->nesting_level) > 0) {
485-
/* Is is hard switched. */
486-
PyTasklet_Kill(t);
487-
PyErr_Clear();
483+
assert(cs == t->cstate);
484+
485+
/* Is tasklet t already dead? */
486+
if (t->f.frame != NULL) {
487+
/* If a thread ends, the thread no longer has a main tasklet and
488+
* the thread is not in a valid state. tstate->st.current is
489+
* undefined. It may point to a tasklet, but the other fields in
490+
* tstate have wrong values.
491+
*
492+
* Therefore we need to ensure, that t is not tstate->st.current.
493+
* Convert t into a free floating tasklet. PyTasklet_Kill works
494+
* for floating tasklets too.
495+
*/
496+
if (t->next && !t->flags.blocked) {
497+
assert(t->prev);
498+
slp_current_remove_tasklet(t);
499+
assert(Py_REFCNT(t) > 1);
500+
Py_DECREF(t);
501+
assert(t->next == NULL);
502+
assert(t->prev == NULL);
503+
}
504+
assert(t != cs->tstate->st.current);
505+
506+
/* has the tasklet nesting_level > 0? The Stackles documentation
507+
* specifies: "When a thread dies, only tasklets with a C-state are actively killed.
508+
* Soft-switched tasklets simply stop."
509+
*/
510+
if ((cts->st.current == cs->task ? cts->st.nesting_level : cs->nesting_level) > 0) {
511+
/* Is is hard switched. */
512+
PyTasklet_Kill(t);
513+
PyErr_Clear();
514+
}
515+
} /* already dead? */
516+
517+
/* Now remove the tstate from all cstacks of tasklet t */
518+
csfirst = slp_cstack_chain;
519+
if (csfirst != NULL) {
520+
in_loop = 0;
521+
for (cs = csfirst; ; cs = cs->next) {
522+
if (in_loop && cs == csfirst) {
523+
/* nothing found */
524+
break;
525+
}
526+
in_loop = 1;
527+
if (cs->task == t) {
528+
assert(cs->tstate == cts);
529+
cs->tstate = NULL;
530+
}
531+
}
488532
}
489-
490-
/* must clear the tstate */
491-
t->cstate->tstate = NULL;
492533
Py_DECREF(t);
534+
in_loop = 0;
493535
} /* while(1) */
494536
} /* if(...) */
495537

496538
other_threads:
497-
/* and a separate simple loop to kill tasklets on foreign threads.
539+
/* Step II of III
540+
*
541+
* A separate simple loop to kill tasklets on foreign threads.
498542
* Since foreign tasklets are scheduled in their own good time,
499543
* there is no guarantee that they are actually dead when we
500544
* exit this function. Therefore, we also can't clear their thread
@@ -539,7 +583,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
539583
csfirst = slp_cstack_chain;
540584
if (csfirst == NULL) {
541585
Py_XDECREF(sleepfunc);
542-
return;
586+
goto current_main;
543587
}
544588

545589
count = 0;
@@ -554,7 +598,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
554598
Py_DECREF(t);
555599
continue; /* not the current cstate of the tasklet */
556600
}
557-
if (cs->tstate == cts) {
601+
if (cs->tstate == NULL || cs->tstate == cts) {
558602
Py_DECREF(t);
559603
continue; /* already handled */
560604
}
@@ -583,6 +627,33 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
583627
}
584628
Py_XDECREF(sleepfunc);
585629
}
630+
631+
current_main:
632+
/* Step III of III
633+
*
634+
* Finally remove the thread state from all remaining cstacks.
635+
* In theory only cstacks of the main tasklet and the initial stub
636+
* should be left.
637+
*/
638+
if (target_ts == NULL || target_ts == cts) {
639+
/* a loop to kill tasklets on the local thread */
640+
PyCStackObject *csfirst = slp_cstack_chain, *cs;
641+
642+
if (csfirst == NULL)
643+
return;
644+
in_loop = 0;
645+
for (cs = csfirst; ; cs = cs->next) {
646+
if (in_loop && cs == csfirst) {
647+
/* nothing found */
648+
break;
649+
}
650+
in_loop = 1;
651+
/* has tstate already been cleared or is it a foreign thread? */
652+
if (target_ts == NULL || cs->tstate == cts) {
653+
cs->tstate = NULL;
654+
}
655+
} /* for(...) */
656+
} /* if(...) */
586657
}
587658

588659
void PyStackless_kill_tasks_with_stacks(int allthreads)

Stackless/unittests/test_defects.py

+2-34
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class TestShutdown(StacklessTestCase):
289289
def test_cstack_new(self):
290290
# test for issue #80 https://bitbucket.org/stackless-dev/stackless/issues/80/
291291
import subprocess
292-
rc = subprocess.call([sys.executable, "-S", "-E", "-c", """if 1:
292+
rc = subprocess.call([sys.executable, "-s", "-S", "-E", "-c", """if 1:
293293
import stackless, sys
294294
from stackless import _test_nostacklesscall as apply_not_stackless
295295
@@ -310,7 +310,7 @@ def func():
310310
def test_interthread_kill(self):
311311
# test for issue #87 https://bitbucket.org/stackless-dev/stackless/issues/87/
312312
import subprocess
313-
rc = subprocess.call([sys.executable, "-S", "-E", "-c", """from __future__ import print_function, absolute_import\nif 1:
313+
rc = subprocess.call([sys.executable, "-s", "-S", "-E", "-c", """from __future__ import print_function, absolute_import\nif 1:
314314
import sys
315315
import _thread as thread
316316
import stackless
@@ -414,38 +414,6 @@ def other_thread_main():
414414
t.join()
415415
print("Done")
416416

417-
@unittest.skipUnless(withThreads, "requires thread support")
418-
def test_deep_thread(self):
419-
# test for issue #103 https://bitbucket.org/stackless-dev/stackless/issues/103/
420-
import subprocess
421-
rc = subprocess.call([sys.executable, "-S", "-E", "-c", """from __future__ import print_function, absolute_import\nif 1:
422-
import threading
423-
import stackless
424-
import time
425-
import sys
426-
from stackless import _test_nostacklesscall as apply
427-
428-
RECURSION_DEPTH = 200
429-
430-
event = threading.Event()
431-
432-
def recurse():
433-
if stackless.current.nesting_level < RECURSION_DEPTH:
434-
apply(recurse)
435-
else:
436-
event.set()
437-
time.sleep(10)
438-
439-
t = threading.Thread(target=recurse, name="other_thread")
440-
t.daemon = True
441-
t.start()
442-
event.wait(10)
443-
# print("end")
444-
sys.stdout.flush()
445-
sys.exit(42)
446-
"""])
447-
self.assertEqual(rc, 42)
448-
449417

450418
class TestStacklessProtokoll(StacklessTestCase):
451419
"""Various tests for violations of the STACKLESS_GETARG() STACKLESS_ASSERT() protocol

0 commit comments

Comments
 (0)