23
23
"""
24
24
25
25
import asyncio
26
+ import atexit
26
27
import concurrent .futures
27
28
import errno
28
29
import functools
@@ -59,6 +60,31 @@ def fileno(self) -> int:
59
60
_T = TypeVar ("_T" )
60
61
61
62
63
+ # Collection of selector thread event loops to shut down on exit.
64
+ _selector_loops : Set ["SelectorThread" ] = set ()
65
+
66
+
67
+ def _atexit_callback () -> None :
68
+ for loop in _selector_loops :
69
+ with loop ._select_cond :
70
+ loop ._closing_selector = True
71
+ loop ._select_cond .notify ()
72
+ try :
73
+ loop ._waker_w .send (b"a" )
74
+ except BlockingIOError :
75
+ pass
76
+ if loop ._thread is not None :
77
+ # If we don't join our (daemon) thread here, we may get a deadlock
78
+ # during interpreter shutdown. I don't really understand why. This
79
+ # deadlock happens every time in CI (both travis and appveyor) but
80
+ # I've never been able to reproduce locally.
81
+ loop ._thread .join ()
82
+ _selector_loops .clear ()
83
+
84
+
85
+ atexit .register (_atexit_callback )
86
+
87
+
62
88
class BaseAsyncIOLoop (IOLoop ):
63
89
def initialize ( # type: ignore
64
90
self , asyncio_loop : asyncio .AbstractEventLoop , ** kwargs : Any
@@ -453,6 +479,7 @@ async def thread_manager_anext() -> None:
453
479
self ._waker_r , self ._waker_w = socket .socketpair ()
454
480
self ._waker_r .setblocking (False )
455
481
self ._waker_w .setblocking (False )
482
+ _selector_loops .add (self )
456
483
self .add_reader (self ._waker_r , self ._consume_waker )
457
484
458
485
def close (self ) -> None :
@@ -464,6 +491,7 @@ def close(self) -> None:
464
491
self ._wake_selector ()
465
492
if self ._thread is not None :
466
493
self ._thread .join ()
494
+ _selector_loops .discard (self )
467
495
self .remove_reader (self ._waker_r )
468
496
self ._waker_r .close ()
469
497
self ._waker_w .close ()
0 commit comments