Skip to content

Commit 5e93864

Browse files
author
Anselm Kruis
committed
merge 3.4-slp (Stackless python#92)
2 parents 5befe91 + 39d4ca1 commit 5e93864

File tree

6 files changed

+94
-10
lines changed

6 files changed

+94
-10
lines changed

Doc/library/stackless/threads.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ will report as ``-1``. This also includes soft-switched tasklets,
3838
which share a C-state.
3939

4040
The reason Stackless kills tasklets with C-state is that not doing so
41-
can cause serious leaks when a C-state is not unwound. Stackless cannot
42-
kill soft-switched tasklets, because there is no central list of them.
41+
can cause serious leaks when a C-state is not unwound. If Stackless runs
42+
in verbose mode (command line option :option:`-v` or :envvar:`PYTHONVERBOSE`),
43+
Stackless prints a warning message, if it deallocates a tasklet
44+
with a C-state. Stackless cannot
45+
kill soft-switched tasklets, because there is no central list of them.
4346
Stackless only knows about the hard-switched ones.
4447

4548
Threads that end really should make sure that they finish whatever worker

Python/pystate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ PyThreadState_Clear(PyThreadState *tstate)
384384
#endif
385385
if (Py_VerboseFlag && tstate->frame != NULL)
386386
fprintf(stderr,
387-
"PyThreadState_Clear: warning: thread still has a frame\n");
387+
"# PyThreadState_Clear: warning: thread still has a frame\n");
388388

389389
Py_CLEAR(tstate->frame);
390390

Stackless/changelog.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ What's New in Stackless 3.X.X?
1010

1111
*Release date: 20XX-XX-XX*
1212

13+
- https://bitbucket.org/stackless-dev/stackless/issues/92
14+
If you run Stackless with the option '-v' (or set the environment variable
15+
PYTHONVERBOSE), Stackless prints a warning message, if it deallocates a
16+
tasklet, that has C-state.
17+
Additionally, the methods tasklet.bind() and tasklet.bind_thread() now check
18+
correctly, if the tasklet has C-state.
19+
1320
- https://bitbucket.org/stackless-dev/stackless/issues/91
1421
Stackless now resets the recursion depth, if you re-bind
1522
a tasklet to another callable.

Stackless/module/taskletobject.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,12 @@ slp_current_unremove(PyTaskletObject* task)
9494
* problems elsewhere (why didn't the tasklet die when instructed to?)
9595
*/
9696
static int
97-
tasklet_has_c_stack(PyTaskletObject *t)
97+
tasklet_has_c_stack_and_thread(PyTaskletObject *t)
9898
{
99-
return t->f.frame && t->cstate && t->cstate->tstate && t->cstate->nesting_level != 0 ;
99+
/* The GC may call this function for a current tasklet.
100+
* Therefore we need the complete check. */
101+
return t->f.frame && t->cstate && t->cstate->tstate &&
102+
(t->cstate->tstate->st.current == t ? t->cstate->tstate->st.nesting_level : t->cstate->nesting_level) != 0;
100103
}
101104

102105
static int
@@ -107,7 +110,7 @@ tasklet_traverse(PyTaskletObject *t, visitproc visit, void *arg)
107110
/* tasklets that need to be switched to for the kill, can't be collected.
108111
* Only trivial decrefs are allowed during GC collect
109112
*/
110-
if (tasklet_has_c_stack(t))
113+
if (tasklet_has_c_stack_and_thread(t))
111114
PyObject_GC_Collectable((PyObject *)t, visit, arg, 0);
112115

113116
/* we own the "execute reference" of all the frames */
@@ -186,7 +189,7 @@ static void
186189
tasklet_dealloc(PyTaskletObject *t)
187190
{
188191
PyObject_GC_UnTrack(t);
189-
if (tasklet_has_c_stack(t)) {
192+
if (tasklet_has_c_stack_and_thread(t)) {
190193
/*
191194
* we want to cleanly kill the tasklet in the case it
192195
* was forgotten. One way would be to resurrect it,
@@ -205,7 +208,13 @@ tasklet_dealloc(PyTaskletObject *t)
205208
if (t->tsk_weakreflist != NULL)
206209
PyObject_ClearWeakRefs((PyObject *)t);
207210
if (t->cstate != NULL) {
208-
assert(t->cstate->task != t || Py_SIZE(t->cstate) == 0);
211+
assert(t->cstate->task != t || Py_SIZE(t->cstate) == 0 || t->cstate->tstate == NULL);
212+
if (t->cstate->task == t) {
213+
t->cstate->task = NULL;
214+
if (Py_VerboseFlag && Py_SIZE(t->cstate) != 0) {
215+
PySys_WriteStderr("# tasklet_dealloc: warning: tasklet %p has a non zero C-stack. \n", (void*)t);
216+
}
217+
}
209218
Py_DECREF(t->cstate);
210219
}
211220
Py_DECREF(t->tempval);
@@ -248,7 +257,7 @@ PyTasklet_BindEx(PyTaskletObject *task, PyObject *func, PyObject *args, PyObject
248257
if (PyTasklet_Scheduled(task)) {
249258
RUNTIME_ERROR("tasklet is scheduled", -1);
250259
}
251-
if (tasklet_has_c_stack(task)) {
260+
if (PyTasklet_GetNestingLevel(task)) {
252261
RUNTIME_ERROR("tasklet has C state on its stack", -1);
253262
}
254263
if (ts && task == ts->st.main && args == NULL && kwargs == NULL) {
@@ -569,7 +578,7 @@ tasklet_bind_thread(PyObject *self, PyObject *args)
569578
if (PyTasklet_Scheduled(task) && !task->flags.blocked) {
570579
RUNTIME_ERROR("can't (re)bind a runnable tasklet", NULL);
571580
}
572-
if (tasklet_has_c_stack(task)) {
581+
if (PyTasklet_GetNestingLevel(task)) {
573582
RUNTIME_ERROR("tasklet has C state on its stack", NULL);
574583
}
575584
if (target_tid != -1) {

Stackless/unittests/test_miscell.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import weakref
1010
import types
1111
import _thread as thread
12+
import time
13+
from stackless import _test_nostacklesscall as apply
1214

1315
from support import StacklessTestCase, AsTaskletTestCase
1416
try:
@@ -952,6 +954,34 @@ def test():
952954
self.assertEqual(tlet.recursion_depth, 0)
953955
self.assertEqual(self.recursion_depth_in_test, 1)
954956

957+
def test_unbind_fail_cstate_no_thread(self):
958+
# https://bitbucket.org/stackless-dev/stackless/issues/92
959+
loop = True
960+
961+
def task():
962+
while loop:
963+
try:
964+
stackless.main.switch()
965+
except TaskletExit:
966+
pass
967+
968+
def other_thread_main():
969+
tlet.bind_thread()
970+
tlet.run()
971+
972+
tlet = stackless.tasklet().bind(apply, (task,))
973+
t = threading.Thread(target=other_thread_main, name="other thread")
974+
t.start()
975+
t.join()
976+
time.sleep(0.05) # other_thread needs some time to be destroyed
977+
978+
loop = False
979+
self.assertEqual(tlet.thread_id, -1)
980+
self.assertFalse(tlet.alive)
981+
self.assertFalse(tlet.restorable)
982+
self.assertGreater(tlet.nesting_level, 0)
983+
self.assertRaisesRegex(RuntimeError, "tasklet has C state on its stack", tlet.bind, None)
984+
955985

956986
class TestSwitch(StacklessTestCase):
957987
"""Test the new tasklet.switch() method, which allows

Stackless/unittests/test_thread.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import stackless
55
import sys
66
import time
7+
from stackless import _test_nostacklesscall as apply
78

89
from support import StacklessTestCase, AsTaskletTestCase
910
try:
@@ -391,6 +392,40 @@ def test_rebind_from_dead(self):
391392
t.bind_thread()
392393
self.assertEqual(t.thread_id, stackless.getcurrent().thread_id)
393394

395+
def test_rebind_from_dead_fail_cstate(self):
396+
# A test for https://bitbucket.org/stackless-dev/stackless/issues/92
397+
loop = True
398+
399+
def task():
400+
while loop:
401+
try:
402+
stackless.main.switch()
403+
except TaskletExit:
404+
pass
405+
406+
def other_thread_main():
407+
tlet.bind_thread()
408+
tlet.run()
409+
410+
tlet = stackless.tasklet().bind(apply, (task,))
411+
t = threading.Thread(target=other_thread_main, name="other thread")
412+
t.start()
413+
t.join()
414+
time.sleep(0.1) # other_thread needs some time to be destroyed
415+
416+
self.assertEqual(tlet.thread_id, -1)
417+
self.assertFalse(tlet.alive)
418+
self.assertFalse(tlet.restorable)
419+
self.assertGreater(tlet.nesting_level, 0)
420+
loop = False
421+
try:
422+
self.assertRaisesRegex(RuntimeError, "tasklet has C state on its stack", tlet.bind_thread)
423+
except AssertionError:
424+
tlet.kill() # causes an assertion error in debug builds of 2.7.9-slp
425+
raise
426+
# the tasklet has no thread
427+
self.assertEqual(tlet.thread_id, -1)
428+
394429
def test_methods_on_dead(self):
395430
"""test that tasklet methods on a dead tasklet behave well"""
396431
class MyException(Exception):

0 commit comments

Comments
 (0)