From b167433b99d2c0cdc54b1b66ae776b7b70f6d9ab Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 20 May 2018 11:28:03 -0400 Subject: [PATCH] asyncio: Fix a race between close and initialize The close method of one IOLoop could race with the initialize method of another one, leading to KeyErrors raised in close(). Fixes #2367 --- tornado/platform/asyncio.py | 7 ++++++- tornado/test/ioloop_test.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index b6a490afa..e0042e1d9 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -62,8 +62,13 @@ class BaseAsyncIOLoop(IOLoop): self.remove_handler(fd) if all_fds: self.close_fd(fileobj) - self.asyncio_loop.close() + # Remove the mapping before closing the asyncio loop. If this + # happened in the other order, we could race against another + # initialize() call which would see the closed asyncio loop, + # assume it was closed from the asyncio side, and do this + # cleanup for us, leading to a KeyError. del IOLoop._ioloop_for_asyncio[self.asyncio_loop] + self.asyncio_loop.close() def add_handler(self, fd, handler, events): fd, fileobj = self.split_fd(fd) diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 646a1d431..b38d0ba32 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -458,6 +458,16 @@ class TestIOLoop(AsyncTestCase): client.close() server.close() + @gen_test + def test_init_close_race(self): + # Regression test for #2367 + def f(): + for i in range(10): + loop = IOLoop() + loop.close() + + yield gen.multi([self.io_loop.run_in_executor(None, f) for i in range(2)]) + # Deliberately not a subclass of AsyncTestCase so the IOLoop isn't # automatically set as current. -- 2.47.2