From: Ben Darnell Date: Mon, 15 May 2023 02:02:44 +0000 (-0400) Subject: Merge branch 'master' into pr/minrk/3029 X-Git-Tag: v6.4.0b1~32^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91bddc05c06310cd500b52be8ae3e447cba3f937;p=thirdparty%2Ftornado.git Merge branch 'master' into pr/minrk/3029 --- 91bddc05c06310cd500b52be8ae3e447cba3f937 diff --cc tornado/platform/asyncio.py index 3863e78f2,5248c1f95..2e95b801a --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@@ -573,70 -646,18 +613,78 @@@ class SelectorThread self._writers[fd] = functools.partial(callback, *args) self._wake_selector() - def remove_reader(self, fd: "_FileDescriptorLike") -> None: - del self._readers[fd] + def remove_reader(self, fd: "_FileDescriptorLike") -> bool: + try: + del self._readers[fd] + except KeyError: + return False self._wake_selector() + return True - def remove_writer(self, fd: "_FileDescriptorLike") -> None: - del self._writers[fd] + def remove_writer(self, fd: "_FileDescriptorLike") -> bool: + try: + del self._writers[fd] + except KeyError: + return False self._wake_selector() + return True + + +class AddThreadSelectorEventLoop(asyncio.AbstractEventLoop): + """Wrap an event loop to add implementations of the ``add_reader`` method family. + + Instances of this class start a second thread to run a selector. + This thread is completely hidden from the user; all callbacks are + run on the wrapped event loop's thread. + + This class is used automatically by Tornado; applications should not need + to refer to it directly. + + It is safe to wrap any event loop with this class, although it only makes sense + for event loops that do not implement the ``add_reader`` family of methods + themselves (i.e. ``WindowsProactorEventLoop``) + + Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop. + + """ + + # This class is a __getattribute__-based proxy. All attributes other than those + # in this set are proxied through to the underlying loop. + MY_ATTRIBUTES = { + "_real_loop", + "_selector", + "add_reader", + "add_writer", + "close", + "remove_reader", + "remove_writer", + } + + def __getattribute__(self, name: str) -> Any: + if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES: + return super().__getattribute__(name) + return getattr(self._real_loop, name) + + def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None: + self._real_loop = real_loop + self._selector = SelectorThread(real_loop) + + def close(self) -> None: + self._selector.close() + self._real_loop.close() + + def add_reader( + self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any + ) -> None: + return self._selector.add_reader(fd, callback, *args) + + def add_writer( + self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any + ) -> None: + return self._selector.add_writer(fd, callback, *args) + - def remove_reader(self, fd: "_FileDescriptorLike") -> None: ++ def remove_reader(self, fd: "_FileDescriptorLike") -> bool: + return self._selector.remove_reader(fd) + - def remove_writer(self, fd: "_FileDescriptorLike") -> None: ++ def remove_writer(self, fd: "_FileDescriptorLike") -> bool: + return self._selector.remove_writer(fd) diff --cc tornado/test/asyncio_test.py index 59825e8f2,348c0cebb..f6b719bb4 --- a/tornado/test/asyncio_test.py +++ b/tornado/test/asyncio_test.py @@@ -108,11 -105,6 +106,11 @@@ class AsyncIOLoopTest(AsyncTestCase) 42, ) + def test_add_thread_close_idempotent(self): - loop = AddThreadSelectorEventLoop(asyncio.get_event_loop()) ++ loop = AddThreadSelectorEventLoop(asyncio.get_event_loop()) # type: ignore + loop.close() + loop.close() + class LeakTest(unittest.TestCase): def setUp(self):