@@ -425,7 +425,15 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
425425 int in_loop = 0 ;
426426
427427 if (target_ts == NULL || target_ts == cts ) {
428- /* a loop to kill tasklets on the local thread */
428+ /* Step I of III
429+ * A loop to kill tasklets on the current thread.
430+ *
431+ * Plan:
432+ * - loop over all cstacks
433+ * - if a cstack belongs to the current thread and is the
434+ * cstack of a tasklet, eventually kill the tasklet. Then remove the
435+ * tstate of all cstacks, which still belong to the killed tasklet.
436+ */
429437 while (1 ) {
430438 PyCStackObject * csfirst = slp_cstack_chain , * cs ;
431439 PyTaskletObject * t ;
@@ -442,59 +450,95 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
442450 if (cs -> tstate != cts )
443451 continue ;
444452
445- if ( cs -> task == NULL ) {
446- cs -> tstate = NULL ;
453+ /* here we are looking for tasks only */
454+ if ( cs -> task == NULL )
447455 continue ;
448- }
449- /* is it already dead? */
450- if (cs -> task -> f .frame == NULL ) {
451- cs -> tstate = NULL ;
456+
457+ /* Do not damage the initial stub */
458+ assert (cs != cts -> st .initial_stub );
459+
460+ /* is it the current cstack of the tasklet */
461+ if (cs -> task -> cstate != cs )
462+ continue ;
463+
464+ /* Do not damage the current tasklet of the current thread.
465+ * Otherwise we fail to kill other tasklets.
466+ * Unfortunately cts->st.current is only valid, if
467+ * cts->st.main != NULL.
468+ *
469+ * Why? When the main tasklet ends, the function
470+ * tasklet_end(PyObject *retval) calls slp_current_remove()
471+ * for the main tasklet. This call sets tstate->st.current to
472+ * the next scheduled tasklet. Then tasklet_end() cleans up
473+ * the main tasklet and returns.
474+ */
475+ if (cts -> st .main != NULL && cs -> task == cts -> st .current ) {
452476 continue ;
453477 }
478+
454479 break ;
455480 }
456- in_loop = 0 ;
457481 t = cs -> task ;
458482 Py_INCREF (t ); /* cs->task is a borrowed ref */
459- assert (t -> cstate == cs );
460-
461- /* If a thread ends, the thread no longer has a main tasklet and
462- * the thread is not in a valid state. tstate->st.current is
463- * undefined. It may point to a tasklet, but the other fields in
464- * tstate have wrong values.
465- *
466- * Therefore we need to ensure, that t is not tstate->st.current.
467- * Convert t into a free floating tasklet. PyTasklet_Kill works
468- * for floating tasklets too.
469- */
470- if (t -> next && !t -> flags .blocked ) {
471- assert (t -> prev );
472- slp_current_remove_tasklet (t );
473- assert (Py_REFCNT (t ) > 1 );
474- Py_DECREF (t );
475- assert (t -> next == NULL );
476- assert (t -> prev == NULL );
477- }
478- assert (t != cs -> tstate -> st .current );
479-
480- /* has the tasklet nesting_level > 0? The Stackles documentation
481- * specifies: "When a thread dies, only tasklets with a C-state are actively killed.
482- * Soft-switched tasklets simply stop."
483- */
484- if ((cts -> st .current == cs -> task ? cts -> st .nesting_level : cs -> nesting_level ) > 0 ) {
485- /* Is is hard switched. */
486- PyTasklet_Kill (t );
487- PyErr_Clear ();
483+ assert (cs == t -> cstate );
484+
485+ /* Is tasklet t already dead? */
486+ if (t -> f .frame != NULL ) {
487+ /* If a thread ends, the thread no longer has a main tasklet and
488+ * the thread is not in a valid state. tstate->st.current is
489+ * undefined. It may point to a tasklet, but the other fields in
490+ * tstate have wrong values.
491+ *
492+ * Therefore we need to ensure, that t is not tstate->st.current.
493+ * Convert t into a free floating tasklet. PyTasklet_Kill works
494+ * for floating tasklets too.
495+ */
496+ if (t -> next && !t -> flags .blocked ) {
497+ assert (t -> prev );
498+ slp_current_remove_tasklet (t );
499+ assert (Py_REFCNT (t ) > 1 );
500+ Py_DECREF (t );
501+ assert (t -> next == NULL );
502+ assert (t -> prev == NULL );
503+ }
504+ assert (t != cs -> tstate -> st .current );
505+
506+ /* has the tasklet nesting_level > 0? The Stackles documentation
507+ * specifies: "When a thread dies, only tasklets with a C-state are actively killed.
508+ * Soft-switched tasklets simply stop."
509+ */
510+ if ((cts -> st .current == cs -> task ? cts -> st .nesting_level : cs -> nesting_level ) > 0 ) {
511+ /* Is is hard switched. */
512+ PyTasklet_Kill (t );
513+ PyErr_Clear ();
514+ }
515+ } /* already dead? */
516+
517+ /* Now remove the tstate from all cstacks of tasklet t */
518+ csfirst = slp_cstack_chain ;
519+ if (csfirst != NULL ) {
520+ in_loop = 0 ;
521+ for (cs = csfirst ; ; cs = cs -> next ) {
522+ if (in_loop && cs == csfirst ) {
523+ /* nothing found */
524+ break ;
525+ }
526+ in_loop = 1 ;
527+ if (cs -> task == t ) {
528+ assert (cs -> tstate == cts );
529+ cs -> tstate = NULL ;
530+ }
531+ }
488532 }
489-
490- /* must clear the tstate */
491- t -> cstate -> tstate = NULL ;
492533 Py_DECREF (t );
534+ in_loop = 0 ;
493535 } /* while(1) */
494536 } /* if(...) */
495537
496538other_threads :
497- /* and a separate simple loop to kill tasklets on foreign threads.
539+ /* Step II of III
540+ *
541+ * A separate simple loop to kill tasklets on foreign threads.
498542 * Since foreign tasklets are scheduled in their own good time,
499543 * there is no guarantee that they are actually dead when we
500544 * exit this function. Therefore, we also can't clear their thread
@@ -539,7 +583,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
539583 csfirst = slp_cstack_chain ;
540584 if (csfirst == NULL ) {
541585 Py_XDECREF (sleepfunc );
542- return ;
586+ goto current_main ;
543587 }
544588
545589 count = 0 ;
@@ -554,7 +598,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
554598 Py_DECREF (t );
555599 continue ; /* not the current cstate of the tasklet */
556600 }
557- if (cs -> tstate == cts ) {
601+ if (cs -> tstate == NULL || cs -> tstate == cts ) {
558602 Py_DECREF (t );
559603 continue ; /* already handled */
560604 }
@@ -583,6 +627,33 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
583627 }
584628 Py_XDECREF (sleepfunc );
585629 }
630+
631+ current_main :
632+ /* Step III of III
633+ *
634+ * Finally remove the thread state from all remaining cstacks.
635+ * In theory only cstacks of the main tasklet and the initial stub
636+ * should be left.
637+ */
638+ if (target_ts == NULL || target_ts == cts ) {
639+ /* a loop to kill tasklets on the local thread */
640+ PyCStackObject * csfirst = slp_cstack_chain , * cs ;
641+
642+ if (csfirst == NULL )
643+ return ;
644+ in_loop = 0 ;
645+ for (cs = csfirst ; ; cs = cs -> next ) {
646+ if (in_loop && cs == csfirst ) {
647+ /* nothing found */
648+ break ;
649+ }
650+ in_loop = 1 ;
651+ /* has tstate already been cleared or is it a foreign thread? */
652+ if (target_ts == NULL || cs -> tstate == cts ) {
653+ cs -> tstate = NULL ;
654+ }
655+ } /* for(...) */
656+ } /* if(...) */
586657}
587658
588659void PyStackless_kill_tasks_with_stacks (int allthreads )
0 commit comments