Skip to content

Commit

Permalink
Stackless issue python#87: assertion failures during shutdown
Browse files Browse the repository at this point in the history
Never call inter-thread scheduler for a tasklet switch with prev==next.
This fixes the first part of issue python#87.
Fix an assert, that is no longer valid. Fixes part 2 of issue python#87.
Add a test case for two assertion failures during interpreter shutdown.

https://bitbucket.org/stackless-dev/stackless/issues/87
(grafted from a6e5950cb45fc1d2d101866c20aea7eb5f005db0, 73f4ac31aa6a and
9f4606055d9c)
  • Loading branch information
Anselm Kruis committed Sep 6, 2016
1 parent 79a08c8 commit 5a06d86
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 4 deletions.
6 changes: 4 additions & 2 deletions Stackless/core/stackless_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ slp_resurrect_and_kill(PyObject *self, void(*killer)(PyObject *))

PyObject *error_type, *error_value, *error_traceback;

/* this is different from typeobject.c's slot_tp_del: our callers already
called PyObject_GC_UnTrack(self) */
assert(PyType_IS_GC(Py_TYPE(self)) && _Py_AS_GC(self)->gc.gc_refs == _PyGC_REFS_UNTRACKED);

/* Temporarily resurrect the object. */
assert(Py_REFCNT(self) == 0);
Py_REFCNT(self) = 1;
Expand Down Expand Up @@ -149,8 +153,6 @@ slp_resurrect_and_kill(PyObject *self, void(*killer)(PyObject *))
_Py_NewReference(self);
Py_REFCNT(self) = refcnt;
}
assert(!PyType_IS_GC(Py_TYPE(self)) ||
_Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
* we need to undo that. */
_Py_DEC_REFTOTAL;
Expand Down
7 changes: 5 additions & 2 deletions Stackless/module/scheduling.c
Original file line number Diff line number Diff line change
Expand Up @@ -974,8 +974,11 @@ slp_schedule_task(PyObject **result, PyTaskletObject *prev, PyTaskletObject *nex
return schedule_task_block(result, prev, stackless, did_switch);

#ifdef WITH_THREAD
/* note that next->cstate is undefined if it is ourself */
if (next->cstate != NULL && next->cstate->tstate != ts) {
/* note that next->cstate is undefined if it is ourself.
Also note, that prev->cstate->tstate == NULL during Py_Finalize() */
assert(prev->cstate == NULL || prev->cstate->tstate == NULL || prev->cstate->tstate == ts);
/* The last condition is required during shutdown when next->cstate->tstate == NULL */
if (next->cstate != NULL && next->cstate->tstate != ts && next != prev) {
return schedule_task_interthread(result, prev, next, stackless, did_switch);
}
#endif
Expand Down
60 changes: 60 additions & 0 deletions Stackless/unittests/test_defects.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,66 @@ def func():
"""])
self.assertEqual(rc, 42)

def test_interthread_kill(self):
# test for issue #87 https://bitbucket.org/stackless-dev/stackless/issues/87/
import subprocess
rc = subprocess.call([sys.executable, "-S", "-E", "-c", """from __future__ import print_function, absolute_import\nif 1:
import sys
import _thread as thread
import stackless
import os
import time
from stackless import _test_nostacklesscall as apply
# This lock is used as a simple event variable.
ready = thread.allocate_lock()
ready.acquire()
if False: # change to True to enable debug messages
sys.stdout = sys.stderr # C-assert messages go to STDERR
else:
def print(*args):
pass
# Module globals are cleared before __del__ is run
# So we save functions, objects, ... in a class dict.
class C(object):
time_sleep = time.sleep
def other_thread_main(self):
print("other thread started")
assert stackless.main.nesting_level == 0
self.main = stackless.main
assert stackless.main is stackless.current
t1 = stackless.tasklet(apply)(self.main.switch)
t1.run()
assert t1.paused
assert t1.nesting_level == 1
print("OT-Main:", self.main)
print("OT-t1:", t1)
try:
ready.release()
while True:
self.main.run()
except TaskletExit:
self.time_sleep(999999)
print("Main-Thread:", stackless.current)
x = C()
thread.start_new_thread(x.other_thread_main, ())
ready.acquire() # Be sure the other thread is waiting.
x.main.kill()
time.sleep(0.5) # give other thread time to run
# state is now: other thread has 2 tasklets:
# - main tasklet is stackless and blocked in sleep(999999)
# - t1 has a C-state and is paused
print("at end")
sys.stdout.flush()
sys.exit(42)
"""])
self.assertEqual(rc, 42)


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

0 comments on commit 5a06d86

Please sign in to comment.