@@ -425,7 +425,15 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
425
425
int in_loop = 0 ;
426
426
427
427
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
+ */
429
437
while (1 ) {
430
438
PyCStackObject * csfirst = slp_cstack_chain , * cs ;
431
439
PyTaskletObject * t ;
@@ -442,59 +450,95 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
442
450
if (cs -> tstate != cts )
443
451
continue ;
444
452
445
- if ( cs -> task == NULL ) {
446
- cs -> tstate = NULL ;
453
+ /* here we are looking for tasks only */
454
+ if ( cs -> task == NULL )
447
455
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 ) {
452
476
continue ;
453
477
}
478
+
454
479
break ;
455
480
}
456
- in_loop = 0 ;
457
481
t = cs -> task ;
458
482
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
+ }
488
532
}
489
-
490
- /* must clear the tstate */
491
- t -> cstate -> tstate = NULL ;
492
533
Py_DECREF (t );
534
+ in_loop = 0 ;
493
535
} /* while(1) */
494
536
} /* if(...) */
495
537
496
538
other_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.
498
542
* Since foreign tasklets are scheduled in their own good time,
499
543
* there is no guarantee that they are actually dead when we
500
544
* exit this function. Therefore, we also can't clear their thread
@@ -539,7 +583,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
539
583
csfirst = slp_cstack_chain ;
540
584
if (csfirst == NULL ) {
541
585
Py_XDECREF (sleepfunc );
542
- return ;
586
+ goto current_main ;
543
587
}
544
588
545
589
count = 0 ;
@@ -554,7 +598,7 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
554
598
Py_DECREF (t );
555
599
continue ; /* not the current cstate of the tasklet */
556
600
}
557
- if (cs -> tstate == cts ) {
601
+ if (cs -> tstate == NULL || cs -> tstate == cts ) {
558
602
Py_DECREF (t );
559
603
continue ; /* already handled */
560
604
}
@@ -583,6 +627,33 @@ void slp_kill_tasks_with_stacks(PyThreadState *target_ts)
583
627
}
584
628
Py_XDECREF (sleepfunc );
585
629
}
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(...) */
586
657
}
587
658
588
659
void PyStackless_kill_tasks_with_stacks (int allthreads )
0 commit comments