Skip to content

Commit b6af5e9

Browse files
author
Anselm Kruis
committed
Stackless issue python#91: unbinding a tasklet does not reset the recursion depth
Reset the recursion depth in PyTasklet_BindEx and add a few asserts. This fixes issue python#91. Add a test case for the recursion depth and as test case for an assertion failure caused by this bug. https://bitbucket.org/stackless-dev/stackless/issues/91 (grafted from 1584a89fca366f66dadf3cf04f0306dd45b1372f and 380b70b1933d)
1 parent 5c4a74b commit b6af5e9

File tree

5 files changed

+85
-2
lines changed

5 files changed

+85
-2
lines changed

Stackless/changelog.txt

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

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

13+
- https://bitbucket.org/stackless-dev/stackless/issues/91
14+
Stackless now resets the recursion depth, if you re-bind
15+
a tasklet to another callable.
16+
1317
- https://bitbucket.org/stackless-dev/stackless/issues/90
1418
Stackless now raises a RuntimeError, if you try to unbind (tlet.bind(None))
1519
a main-tasklet.

Stackless/module/scheduling.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ schedule_task_destruct(PyObject **retval, PyTaskletObject *prev, PyTaskletObject
12851285
tasklet is initialized in the middle of an existing indeterminate call
12861286
stack. Therefore it is not guaranteed that there is not a pre-existing
12871287
recursion depth from before its initialization. So, assert that this
1288-
is zero, or that we are the main tasklet being destroyed (see tasklet_end)
1288+
is zero, or that we are the main tasklet being destroyed (see tasklet_end).
12891289
*/
12901290
assert(ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next));
12911291
prev->recursion_depth = 0;

Stackless/module/taskletobject.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ PyTasklet_BindEx(PyTaskletObject *task, PyObject *func, PyObject *args, PyObject
256256
}
257257

258258
tasklet_clear_frames(task);
259+
task->recursion_depth = 0;
260+
assert(task->flags.autoschedule == 0); /* probably unused */
261+
assert(task->flags.blocked == 0);
259262
assert(task->f.frame == NULL);
260263

261264
/* cstate is set by bind_tasklet_to_frame() later on */
@@ -996,6 +999,12 @@ impl_tasklet_setup(PyTaskletObject *task, PyObject *args, PyObject *kwds, int in
996999
assert(PyTasklet_Check(task));
9971000
if (ts->st.main == NULL) return PyTasklet_Setup_M(task, args, kwds);
9981001

1002+
assert(task->recursion_depth == 0);
1003+
assert(task->flags.is_zombie == 0);
1004+
assert(task->flags.autoschedule == 0); /* probably unused */
1005+
assert(task->flags.blocked == 0);
1006+
assert(task->f.frame == NULL);
1007+
9991008
func = task->tempval;
10001009
if (func == NULL || func == Py_None)
10011010
RUNTIME_ERROR("the tasklet was not bound to a function", -1);

Stackless/unittests/test_defects.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import print_function, absolute_import, division
12
import unittest
23
import stackless
34
import gc
@@ -335,6 +336,52 @@ def other_thread_main(self):
335336
"""])
336337
self.assertEqual(rc, 42)
337338

339+
@unittest.skip("test triggers an assertion violation, issue #89")
340+
def test_tasklet_end_with_wrong_recursion_level(self):
341+
# test for issue #91 https://bitbucket.org/stackless-dev/stackless/issues/91/
342+
"""A test for issue #91, wrong recursion level after tasklet re-binding
343+
344+
Assertion failed: ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next), file ..\Stackless\module\scheduling.c, line 1291
345+
The assertion fails with ts->recursion_depth > 0
346+
347+
It is in function
348+
static int schedule_task_destruct(PyObject **retval, PyTaskletObject *prev, PyTaskletObject *next):
349+
350+
assert(ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next));
351+
352+
During thread shutdown in slp_kill_tasks_with_stacks() kills tasklet tlet after the main
353+
tasklet of other thread ended. To do so, it creates a new temporary main tasklet. The
354+
assertion failure happens during the end of the killed tasklet.
355+
"""
356+
if True:
357+
def print(*args):
358+
pass
359+
360+
def tlet_inner():
361+
assert stackless.current.recursion_depth == 2
362+
stackless.main.switch()
363+
364+
def tlet_outer():
365+
tlet_inner()
366+
367+
def other_thread_main():
368+
self.tlet = stackless.tasklet(tlet_outer)()
369+
self.assertEqual(self.tlet.recursion_depth, 0)
370+
print("Other thread main", stackless.main)
371+
print("Other thread paused", self.tlet)
372+
self.tlet.run()
373+
self.assertEqual(self.tlet.recursion_depth, 2)
374+
self.tlet.bind(lambda: None, ())
375+
self.assertEqual(self.tlet.recursion_depth, 0)
376+
# before issue #91 got fixed, the assertion violatition occurred here
377+
378+
print("Main thread", stackless.current)
379+
t = threading.Thread(target=other_thread_main, name="other thread")
380+
t.start()
381+
print("OK")
382+
t.join()
383+
print("Done")
384+
338385

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

Stackless/unittests/test_miscell.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ def other_thread():
902902

903903
def test_rebind_main(self):
904904
# rebind the main tasklet of a thread. This is highly discouraged,
905-
# because it will deadlock, if the thread is a threading.Thread.
905+
# because it will deadlock, if the thread is a non daemon threading.Thread.
906906
self.skipUnlessSoftswitching()
907907

908908
ready = thread.allocate_lock()
@@ -929,6 +929,29 @@ def other_thread_main():
929929
self.assertTrue(self.target_called)
930930
self.assertFalse(self.main_returned)
931931

932+
def test_rebind_recursion_depth(self):
933+
self.skipUnlessSoftswitching()
934+
self.recursion_depth_in_test = None
935+
936+
def tasklet_outer():
937+
tasklet_inner()
938+
939+
def tasklet_inner():
940+
stackless.main.switch()
941+
942+
def test():
943+
self.recursion_depth_in_test = stackless.current.recursion_depth
944+
945+
tlet = stackless.tasklet(tasklet_outer)()
946+
self.assertEqual(tlet.recursion_depth, 0)
947+
tlet.run()
948+
self.assertEqual(tlet.recursion_depth, 2)
949+
tlet.bind(test, ())
950+
self.assertEqual(tlet.recursion_depth, 0)
951+
tlet.run()
952+
self.assertEqual(tlet.recursion_depth, 0)
953+
self.assertEqual(self.recursion_depth_in_test, 1)
954+
932955

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

0 commit comments

Comments
 (0)