Skip to content

Commit

Permalink
Stackless issue python#91: unbinding a tasklet does not reset the rec…
Browse files Browse the repository at this point in the history
…ursion 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)
  • Loading branch information
Anselm Kruis committed Sep 10, 2016
1 parent 5c4a74b commit b6af5e9
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Stackless/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ What's New in Stackless 3.X.X?

*Release date: 20XX-XX-XX*

- https://bitbucket.org/stackless-dev/stackless/issues/91
Stackless now resets the recursion depth, if you re-bind
a tasklet to another callable.

- https://bitbucket.org/stackless-dev/stackless/issues/90
Stackless now raises a RuntimeError, if you try to unbind (tlet.bind(None))
a main-tasklet.
Expand Down
2 changes: 1 addition & 1 deletion Stackless/module/scheduling.c
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ schedule_task_destruct(PyObject **retval, PyTaskletObject *prev, PyTaskletObject
tasklet is initialized in the middle of an existing indeterminate call
stack. Therefore it is not guaranteed that there is not a pre-existing
recursion depth from before its initialization. So, assert that this
is zero, or that we are the main tasklet being destroyed (see tasklet_end)
is zero, or that we are the main tasklet being destroyed (see tasklet_end).
*/
assert(ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next));
prev->recursion_depth = 0;
Expand Down
9 changes: 9 additions & 0 deletions Stackless/module/taskletobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ PyTasklet_BindEx(PyTaskletObject *task, PyObject *func, PyObject *args, PyObject
}

tasklet_clear_frames(task);
task->recursion_depth = 0;
assert(task->flags.autoschedule == 0); /* probably unused */
assert(task->flags.blocked == 0);
assert(task->f.frame == NULL);

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

assert(task->recursion_depth == 0);
assert(task->flags.is_zombie == 0);
assert(task->flags.autoschedule == 0); /* probably unused */
assert(task->flags.blocked == 0);
assert(task->f.frame == NULL);

func = task->tempval;
if (func == NULL || func == Py_None)
RUNTIME_ERROR("the tasklet was not bound to a function", -1);
Expand Down
47 changes: 47 additions & 0 deletions Stackless/unittests/test_defects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import print_function, absolute_import, division
import unittest
import stackless
import gc
Expand Down Expand Up @@ -335,6 +336,52 @@ def other_thread_main(self):
"""])
self.assertEqual(rc, 42)

@unittest.skip("test triggers an assertion violation, issue #89")
def test_tasklet_end_with_wrong_recursion_level(self):
# test for issue #91 https://bitbucket.org/stackless-dev/stackless/issues/91/
"""A test for issue #91, wrong recursion level after tasklet re-binding
Assertion failed: ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next), file ..\Stackless\module\scheduling.c, line 1291
The assertion fails with ts->recursion_depth > 0
It is in function
static int schedule_task_destruct(PyObject **retval, PyTaskletObject *prev, PyTaskletObject *next):
assert(ts->recursion_depth == 0 || (ts->st.main == NULL && prev == next));
During thread shutdown in slp_kill_tasks_with_stacks() kills tasklet tlet after the main
tasklet of other thread ended. To do so, it creates a new temporary main tasklet. The
assertion failure happens during the end of the killed tasklet.
"""
if True:
def print(*args):
pass

def tlet_inner():
assert stackless.current.recursion_depth == 2
stackless.main.switch()

def tlet_outer():
tlet_inner()

def other_thread_main():
self.tlet = stackless.tasklet(tlet_outer)()
self.assertEqual(self.tlet.recursion_depth, 0)
print("Other thread main", stackless.main)
print("Other thread paused", self.tlet)
self.tlet.run()
self.assertEqual(self.tlet.recursion_depth, 2)
self.tlet.bind(lambda: None, ())
self.assertEqual(self.tlet.recursion_depth, 0)
# before issue #91 got fixed, the assertion violatition occurred here

print("Main thread", stackless.current)
t = threading.Thread(target=other_thread_main, name="other thread")
t.start()
print("OK")
t.join()
print("Done")


class TestStacklessProtokoll(StacklessTestCase):
"""Various tests for violations of the STACKLESS_GETARG() STACKLESS_ASSERT() protocol
Expand Down
25 changes: 24 additions & 1 deletion Stackless/unittests/test_miscell.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ def other_thread():

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

ready = thread.allocate_lock()
Expand All @@ -929,6 +929,29 @@ def other_thread_main():
self.assertTrue(self.target_called)
self.assertFalse(self.main_returned)

def test_rebind_recursion_depth(self):
self.skipUnlessSoftswitching()
self.recursion_depth_in_test = None

def tasklet_outer():
tasklet_inner()

def tasklet_inner():
stackless.main.switch()

def test():
self.recursion_depth_in_test = stackless.current.recursion_depth

tlet = stackless.tasklet(tasklet_outer)()
self.assertEqual(tlet.recursion_depth, 0)
tlet.run()
self.assertEqual(tlet.recursion_depth, 2)
tlet.bind(test, ())
self.assertEqual(tlet.recursion_depth, 0)
tlet.run()
self.assertEqual(tlet.recursion_depth, 0)
self.assertEqual(self.recursion_depth_in_test, 1)


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

0 comments on commit b6af5e9

Please sign in to comment.