Skip to content

Commit ec59fa0

Browse files
authored
Merge pull request #3332 from bdarnell/selector-thread-atexit
Revert "asyncio: Remove atexit hook"
2 parents f5df43f + 3340c39 commit ec59fa0

File tree

2 files changed

+41
-13
lines changed

2 files changed

+41
-13
lines changed

Diff for: tornado/platform/asyncio.py

+28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"""
2424

2525
import asyncio
26+
import atexit
2627
import concurrent.futures
2728
import errno
2829
import functools
@@ -59,6 +60,31 @@ def fileno(self) -> int:
5960
_T = TypeVar("_T")
6061

6162

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+
6288
class BaseAsyncIOLoop(IOLoop):
6389
def initialize( # type: ignore
6490
self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any
@@ -453,6 +479,7 @@ async def thread_manager_anext() -> None:
453479
self._waker_r, self._waker_w = socket.socketpair()
454480
self._waker_r.setblocking(False)
455481
self._waker_w.setblocking(False)
482+
_selector_loops.add(self)
456483
self.add_reader(self._waker_r, self._consume_waker)
457484

458485
def close(self) -> None:
@@ -464,6 +491,7 @@ def close(self) -> None:
464491
self._wake_selector()
465492
if self._thread is not None:
466493
self._thread.join()
494+
_selector_loops.discard(self)
467495
self.remove_reader(self._waker_r)
468496
self._waker_r.close()
469497
self._waker_w.close()

Diff for: tornado/test/circlerefs_test.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,21 @@ def test_run_on_executor(self):
197197
# and tornado.concurrent.chain_future.
198198
import concurrent.futures
199199

200-
thread_pool = concurrent.futures.ThreadPoolExecutor(1)
200+
with concurrent.futures.ThreadPoolExecutor(1) as thread_pool:
201201

202-
class Factory(object):
203-
executor = thread_pool
202+
class Factory(object):
203+
executor = thread_pool
204204

205-
@tornado.concurrent.run_on_executor
206-
def run(self):
207-
return None
205+
@tornado.concurrent.run_on_executor
206+
def run(self):
207+
return None
208208

209-
factory = Factory()
209+
factory = Factory()
210210

211-
async def main():
212-
# The cycle is not reported on the first call. It's not clear why.
213-
for i in range(2):
214-
await factory.run()
211+
async def main():
212+
# The cycle is not reported on the first call. It's not clear why.
213+
for i in range(2):
214+
await factory.run()
215215

216-
with assert_no_cycle_garbage():
217-
asyncio.run(main())
216+
with assert_no_cycle_garbage():
217+
asyncio.run(main())

0 commit comments

Comments
 (0)