@@ -335,100 +335,251 @@ slp_eval_frame(PyFrameObject *f)
335
335
return slp_frame_dispatch (f , fprev , 0 , Py_None );
336
336
}
337
337
338
- /* a thread (or threads) is exiting. After this call, no tasklet may
339
- * refer to the current thread's tstate
340
- */
341
- void slp_kill_tasks_with_stacks (PyThreadState * target_ts )
338
+ static void
339
+ kill_pending_current_main_and_watchdogs (PyThreadState * ts )
342
340
{
343
- PyThreadState * ts = PyThreadState_GET ();
344
- int count = 0 ;
341
+ PyTaskletObject * t ;
342
+
343
+ assert (ts != PyThreadState_GET ()); /* don't kill ourself */
344
+
345
+ /* kill watchdogs */
346
+ if (ts -> st .watchdogs && PyList_CheckExact (ts -> st .watchdogs )) {
347
+ Py_ssize_t i ;
348
+ /* we don't kill the "intterupt" slot, number 0 */
349
+ for (i = PyList_GET_SIZE (ts -> st .watchdogs ) - 1 ; i > 0 ; i -- ) {
350
+ PyObject * item = PyList_GET_ITEM (ts -> st .watchdogs , i );
351
+ assert (item && PyTasklet_Check (item ));
352
+ t = (PyTaskletObject * ) item ;
353
+ Py_INCREF (t ); /* it is a borrowed ref */
354
+ PyTasklet_KillEx (t , 1 );
355
+ PyErr_Clear ();
356
+ Py_DECREF (t );
357
+ }
358
+ }
359
+ /* kill main */
360
+ t = ts -> st .main ;
361
+ if (t != NULL ) {
362
+ Py_INCREF (t ); /* it is a borrowed ref */
363
+ PyTasklet_KillEx (t , 1 );
364
+ PyErr_Clear ();
365
+ Py_DECREF (t );
366
+ }
367
+ /* kill current */
368
+ t = ts -> st .current ;
369
+ if (t != NULL ) {
370
+ Py_INCREF (t ); /* it is a borrowed ref */
371
+ PyTasklet_KillEx (t , 1 );
372
+ PyErr_Clear ();
373
+ Py_DECREF (t );
374
+ }
375
+ }
345
376
346
- /* a loop to kill tasklets on the local thread */
347
- while (1 ) {
348
- PyCStackObject * csfirst = slp_cstack_chain , * cs ;
349
- PyTaskletObject * t ;
377
+ static void
378
+ run_other_threads (PyObject * * sleep , int count )
379
+ {
380
+ if (count == 0 ) {
381
+ /* shortcut */
382
+ return ;
383
+ }
350
384
351
- if (csfirst == NULL )
352
- break ;
353
- for (cs = csfirst ; ; cs = cs -> next ) {
354
- if (count && cs == csfirst ) {
355
- /* nothing found */
356
- return ;
385
+ assert (sleep != NULL );
386
+ if (* sleep == NULL ) {
387
+ /* get a reference to time.sleep() */
388
+ PyObject * mod_time ;
389
+ assert (Py_IsInitialized ());
390
+ mod_time = PyImport_ImportModule ("time" );
391
+ if (mod_time != NULL ) {
392
+ * sleep = PyObject_GetAttrString (mod_time , "sleep" );
393
+ Py_DECREF (mod_time );
394
+ }
395
+ if (* sleep == NULL ) {
396
+ PyErr_Clear ();
397
+ }
398
+ }
399
+ while (count -- > 0 ) {
400
+ if (* sleep == NULL ) {
401
+ Py_BEGIN_ALLOW_THREADS
402
+ Py_END_ALLOW_THREADS
403
+ } else {
404
+ PyObject * res = PyObject_CallFunction (* sleep , "(f)" , (float )0.001 );
405
+ if (res != NULL ) {
406
+ Py_DECREF (res );
407
+ } else {
408
+ PyErr_Clear ();
357
409
}
358
- ++ count ;
359
- /* has tstate already been cleared or is it a foreign thread? */
360
- if (cs -> tstate != ts )
361
- continue ;
410
+ }
411
+ }
412
+ }
362
413
363
- if (cs -> task == NULL ) {
364
- cs -> tstate = NULL ;
365
- continue ;
366
- }
367
- /* is it already dead? */
368
- if (cs -> task -> f .frame == NULL ) {
369
- cs -> tstate = NULL ;
370
- continue ;
414
+ /* a thread (or threads) is exiting. After this call, no tasklet may
415
+ * refer to target_ts, if target_ts != NULL.
416
+ * Also inactivate all other threads during interpreter shut down (target_ts == NULL).
417
+ * Later in the shutdown sequence Python clears the tstate structure. This
418
+ * causes access violations, if another thread is still active.
419
+ */
420
+ void slp_kill_tasks_with_stacks (PyThreadState * target_ts )
421
+ {
422
+ PyThreadState * cts = PyThreadState_GET ();
423
+ int in_loop = 0 ;
424
+
425
+ if (target_ts == NULL || target_ts == cts ) {
426
+ /* a loop to kill tasklets on the local thread */
427
+ while (1 ) {
428
+ PyCStackObject * csfirst = slp_cstack_chain , * cs ;
429
+ PyTaskletObject * t ;
430
+
431
+ if (csfirst == NULL )
432
+ goto other_threads ;
433
+ for (cs = csfirst ; ; cs = cs -> next ) {
434
+ if (in_loop && cs == csfirst ) {
435
+ /* nothing found */
436
+ goto other_threads ;
437
+ }
438
+ in_loop = 1 ;
439
+ /* has tstate already been cleared or is it a foreign thread? */
440
+ if (cs -> tstate != cts )
441
+ continue ;
442
+
443
+ if (cs -> task == NULL ) {
444
+ cs -> tstate = NULL ;
445
+ continue ;
446
+ }
447
+ /* is it already dead? */
448
+ if (cs -> task -> f .frame == NULL ) {
449
+ cs -> tstate = NULL ;
450
+ continue ;
451
+ }
452
+ break ;
371
453
}
372
- break ;
373
- }
374
- count = 0 ;
375
- t = cs -> task ;
376
- Py_INCREF (t ); /* cs->task is a borrowed ref */
377
-
378
- /* We need to ensure that the tasklet 't' is in the scheduler
379
- * tasklet chain before this one (our main). This ensures
380
- * that this one is directly switched back to after 't' is
381
- * killed. The reason we do this this is because if another
382
- * tasklet is switched to, this is of course it being scheduled
383
- * and run. Why we do not need to do this for tasklets blocked
384
- * on channels is that when they are scheduled to be run and
385
- * killed, they will be implicitly placed before this one,
386
- * leaving it to run next.
387
- */
388
- if (!t -> flags .blocked && t != cs -> tstate -> st .current ) {
389
- /* unlink from runnable queue if it wasn't previously remove()'d */
390
- if (t -> next ) {
454
+ in_loop = 0 ;
455
+ t = cs -> task ;
456
+ Py_INCREF (t ); /* cs->task is a borrowed ref */
457
+ assert (t -> cstate == cs );
458
+
459
+ /* If a thread ends, the thread no longer has a main tasklet and
460
+ * the thread is not in a valid state. tstate->st.current is
461
+ * undefined. It may point to a tasklet, but the other fields in
462
+ * tstate have wrong values.
463
+ *
464
+ * Therefore we need to ensure, that t is not tstate->st.current.
465
+ * Convert t into a free floating tasklet. PyTasklet_Kill works
466
+ * for floating tasklets too.
467
+ */
468
+ if (t -> next && !t -> flags .blocked ) {
391
469
assert (t -> prev );
392
470
slp_current_remove_tasklet (t );
393
- assert (t -> cstate -> tstate == ts );
394
- } else
395
- Py_INCREF (t ); /* a new reference for the runnable queue */
396
- /* insert into the 'current' chain without modifying 'current' */
397
- slp_current_insert (t );
398
- }
399
-
400
- PyTasklet_Kill (t );
401
- PyErr_Clear ();
471
+ assert (Py_REFCNT (t ) > 1 );
472
+ Py_DECREF (t );
473
+ assert (t -> next == NULL );
474
+ assert (t -> prev == NULL );
475
+ }
476
+ assert (t != cs -> tstate -> st .current );
477
+
478
+ /* has the tasklet nesting_level > 0? The Stackles documentation
479
+ * specifies: "When a thread dies, only tasklets with a C-state are actively killed.
480
+ * Soft-switched tasklets simply stop."
481
+ */
482
+ if ((cts -> st .current == cs -> task ? cts -> st .nesting_level : cs -> nesting_level ) > 0 ) {
483
+ /* Is is hard switched. */
484
+ PyTasklet_Kill (t );
485
+ PyErr_Clear ();
486
+ }
402
487
403
- /* must clear the tstate */
404
- t -> cstate -> tstate = NULL ;
405
- Py_DECREF (t );
406
- }
488
+ /* must clear the tstate */
489
+ t -> cstate -> tstate = NULL ;
490
+ Py_DECREF (t );
491
+ } /* while(1) */
492
+ } /* if(...) */
407
493
494
+ other_threads :
408
495
/* and a separate simple loop to kill tasklets on foreign threads.
409
496
* Since foreign tasklets are scheduled in their own good time,
410
497
* there is no guarantee that they are actually dead when we
411
498
* exit this function. Therefore, we also can't clear their thread
412
499
* states. That will hopefully happen when their threads exit.
413
500
*/
414
501
{
415
- PyCStackObject * csfirst = slp_cstack_chain , * cs ;
502
+ PyCStackObject * csfirst , * cs ;
416
503
PyTaskletObject * t ;
417
-
418
- if (csfirst == NULL )
504
+ PyObject * sleepfunc = NULL ;
505
+ int count ;
506
+
507
+ /* other threads, first pass: kill (pending) current, main and watchdog tasklets */
508
+ if (target_ts == NULL ) {
509
+ PyThreadState * ts ;
510
+ count = 0 ;
511
+ for (ts = cts -> interp -> tstate_head ; ts != NULL ; ts = ts -> next ) {
512
+ if (ts != cts ) {
513
+ /* Inactivate thread ts. In case the thread is active,
514
+ * it will be killed. If the thread is sleping, it
515
+ * continues to sleep.
516
+ */
517
+ count ++ ;
518
+ kill_pending_current_main_and_watchdogs (ts );
519
+ /* It helps to inactivate threads reliably */
520
+ if (PyExc_TaskletExit )
521
+ PyThreadState_SetAsyncExc (ts -> thread_id , PyExc_TaskletExit );
522
+ }
523
+ }
524
+ /* We must not release the GIL while we might hold the HEAD-lock.
525
+ * Otherwise another thread (usually the thread of the killed tasklet)
526
+ * could try to get the HEAD lock. The result would be a wonderful dead lock.
527
+ * If target_ts is NULL, we know for sure, that we don't hold the HEAD-lock.
528
+ */
529
+ run_other_threads (& sleepfunc , count );
530
+ /* The other threads might have modified the thread state chain, but fortunately we
531
+ * are done with it.
532
+ */
533
+ } else if (target_ts != cts ) {
534
+ kill_pending_current_main_and_watchdogs (target_ts );
535
+ /* Here it is not safe to release the GIL. */
536
+ }
537
+
538
+ /* other threads, second pass: kill tasklets with nesting-level > 0 and
539
+ * clear tstate if target_ts != NULL && target_ts != cts. */
540
+ csfirst = slp_cstack_chain ;
541
+ if (csfirst == NULL ) {
542
+ Py_XDECREF (sleepfunc );
419
543
return ;
544
+ }
545
+
420
546
count = 0 ;
421
- for (cs = csfirst ; ; cs = cs -> next ) {
422
- if (count && cs == csfirst ) {
423
- return ;
424
- }
425
- count ++ ;
547
+ in_loop = 0 ;
548
+ for (cs = csfirst ; !(in_loop && cs == csfirst ); cs = cs -> next ) {
549
+ in_loop = 1 ;
426
550
t = cs -> task ;
427
- Py_INCREF (t ); /* cs->task is a borrowed ref */
428
- PyTasklet_Kill (t );
429
- PyErr_Clear ();
551
+ if (t == NULL )
552
+ continue ;
553
+ Py_INCREF (t ); /* cs->task is a borrowed ref */
554
+ assert (t -> cstate == cs );
555
+ if (cs -> tstate == cts ) {
556
+ Py_DECREF (t );
557
+ continue ; /* already handled */
558
+ }
559
+ if (target_ts != NULL && cs -> tstate != target_ts ) {
560
+ Py_DECREF (t );
561
+ continue ; /* we are not interested in this thread */
562
+ }
563
+ if (((cs -> tstate && cs -> tstate -> st .current == t ) ? cs -> tstate -> st .nesting_level : cs -> nesting_level ) > 0 ) {
564
+ /* Kill only tasklets with nesting level > 0 */
565
+ count ++ ;
566
+ PyTasklet_Kill (t );
567
+ PyErr_Clear ();
568
+ }
430
569
Py_DECREF (t );
570
+ if (target_ts != NULL ) {
571
+ cs -> tstate = NULL ;
572
+ }
573
+ }
574
+ if (target_ts == NULL ) {
575
+ /* We must not release the GIL while we might hold the HEAD-lock.
576
+ * Otherwise another thread (usually the thread of the killed tasklet)
577
+ * could try to get the HEAD lock. The result would be a wonderful dead lock.
578
+ * If target_ts is NULL, we know for sure, that we don't hold the HEAD-lock.
579
+ */
580
+ run_other_threads (& sleepfunc , count );
431
581
}
582
+ Py_XDECREF (sleepfunc );
432
583
}
433
584
}
434
585
0 commit comments