Assertion violation in tasklet_dealloc during PyThreadState_Clear #89

Description
Originally reported by: Anselm Kruis (Bitbucket: akruis, GitHub: akruis)
Stackless 2.7.9-slp and 2.7-slp current (commit 782a1b292460), Win-7, VS 2008 debug build
This script
#!python
from __future__ import print_function, absolute_import
import threading
import stackless
import sys
def other_thread_main():
print("other thread started")
assert stackless.main is stackless.current
tasklet2 = stackless.tasklet(apply)(stackless.main.run, ())
print("OT Main:", stackless.main)
print("OT tasklet2:", tasklet2)
tasklet2.run()
return
assert stackless.main is stackless.current
print("Main Thread:", stackless.main)
t = threading.Thread(target=other_thread_main, name="other thread")
t.start()
t.join()
print("OK")
triggers Assertion failed: t->cstate->task != t || t->cstate->ob_size == 0, file ..\Stackless\module\taskletobject.c, line 207
in thread "other thread".
C-Stacktrace
msvcr90d.dll!_NMSG_WRITE(int rterrnum=10) Line 198 C
msvcr90d.dll!abort() Line 59 + 0x7 bytes C
msvcr90d.dll!_wassert(const wchar_t * expr=0x1e2e9f88, const wchar_t * filename=0x1e2e9e48, unsigned int lineno=207) Line 346 C
python27_d.dll!tasklet_dealloc(_tasklet * t=0x0261db90) Line 207 + 0x2f bytes C
python27_d.dll!_Py_Dealloc(_object * op=0x0261db90) Line 2262 + 0x7 bytes C
python27_d.dll!slp_kill_tasks_with_stacks(_ts * target_ts=0x025e8780) Line 406 + 0x51 bytes C
> python27_d.dll!PyThreadState_Clear(_ts * tstate=0x025e8780) Line 251 + 0x9 bytes C
python27_d.dll!t_bootstrap(void * boot_raw=0x007dc2f8) Line 642 + 0x9 bytes C
python27_d.dll!bootstrap(void * call=0x004b1990) Line 122 + 0x7 bytes C
msvcr90d.dll!_callthreadstartex() Line 348 + 0xf bytes C
msvcr90d.dll!_threadstartex(void * ptd=0x025a12c8) Line 331 C
kernel32.dll!7679338a()
[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]
ntdll.dll!76ec9902()
ntdll.dll!76ec98d5()
Tasklet 0x0261db90 (argument of tasklet_dealloc() is tasklet2.
Analysis
The problem is in slp_kill_tasks_with_stacks(). The relevant C-code is
#!C
PyTasklet_Kill(t);
PyErr_Clear();
/* must clear the tstate */
t->cstate->tstate = NULL;
Py_DECREF(t);
The assertion violation happens in Py_DECREF(t). t is tasklet2. It looks like PyTasklet_Kill(t) didn't "remove" the C-state from the tasklet. Findings:
- tstate->st.main == NULL
- tstate->st.current == tasklet2
- tstate->frame == NULL.
- tasklet2->f.cframe != NULL
This is caused by tasklet_end(main tasklet of other thread). I think, this state is not OK. It signals, that tasklet2 is dead, but that is wrong. As a consequence, PyTasklet_Kill(tasklet2) returns without killing tasklet2.
Questions
-
tasklet_end(a main tasklet) does not care about other scheduled tasklets. Perhaps this is the root cause of the problem?
-
Because this assertion violation happens when the interpreter is fully operational, it must be fixed correctly. Unfortunately I don't understand the exact purpose of the failed assertion yet.
@ctismer or @krisvale, any help is highly appreciated.
Test case is commit 2cc41427a347 for 2.7-slp