asyncio.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. """Bridges between the `asyncio` module and Tornado IOLoop.
  2. .. versionadded:: 3.2
  3. This module integrates Tornado with the ``asyncio`` module introduced
  4. in Python 3.4. This makes it possible to combine the two libraries on
  5. the same event loop.
  6. .. deprecated:: 5.0
  7. While the code in this module is still used, it is now enabled
  8. automatically when `asyncio` is available, so applications should
  9. no longer need to refer to this module directly.
  10. .. note::
  11. Tornado is designed to use a selector-based event loop. On Windows,
  12. where a proactor-based event loop has been the default since Python 3.8,
  13. a selector event loop is emulated by running ``select`` on a separate thread.
  14. Configuring ``asyncio`` to use a selector event loop may improve performance
  15. of Tornado (but may reduce performance of other ``asyncio``-based libraries
  16. in the same process).
  17. """
  18. import asyncio
  19. import atexit
  20. import concurrent.futures
  21. import contextvars
  22. import errno
  23. import functools
  24. import select
  25. import socket
  26. import sys
  27. import threading
  28. import typing
  29. import warnings
  30. from tornado.gen import convert_yielded
  31. from tornado.ioloop import IOLoop, _Selectable
  32. from typing import (
  33. Any,
  34. Callable,
  35. Dict,
  36. List,
  37. Optional,
  38. Protocol,
  39. Set,
  40. Tuple,
  41. TypeVar,
  42. Union,
  43. )
  44. if typing.TYPE_CHECKING:
  45. from typing_extensions import TypeVarTuple, Unpack
  46. class _HasFileno(Protocol):
  47. def fileno(self) -> int:
  48. pass
  49. _FileDescriptorLike = Union[int, _HasFileno]
  50. _T = TypeVar("_T")
  51. if typing.TYPE_CHECKING:
  52. _Ts = TypeVarTuple("_Ts")
  53. # Collection of selector thread event loops to shut down on exit.
  54. _selector_loops: Set["SelectorThread"] = set()
  55. def _atexit_callback() -> None:
  56. for loop in _selector_loops:
  57. with loop._select_cond:
  58. loop._closing_selector = True
  59. loop._select_cond.notify()
  60. try:
  61. loop._waker_w.send(b"a")
  62. except BlockingIOError:
  63. pass
  64. if loop._thread is not None:
  65. # If we don't join our (daemon) thread here, we may get a deadlock
  66. # during interpreter shutdown. I don't really understand why. This
  67. # deadlock happens every time in CI (both travis and appveyor) but
  68. # I've never been able to reproduce locally.
  69. loop._thread.join()
  70. _selector_loops.clear()
  71. atexit.register(_atexit_callback)
  72. class BaseAsyncIOLoop(IOLoop):
  73. def initialize( # type: ignore
  74. self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any
  75. ) -> None:
  76. # asyncio_loop is always the real underlying IOLoop. This is used in
  77. # ioloop.py to maintain the asyncio-to-ioloop mappings.
  78. self.asyncio_loop = asyncio_loop
  79. # selector_loop is an event loop that implements the add_reader family of
  80. # methods. Usually the same as asyncio_loop but differs on platforms such
  81. # as windows where the default event loop does not implement these methods.
  82. self.selector_loop = asyncio_loop
  83. if hasattr(asyncio, "ProactorEventLoop") and isinstance(
  84. asyncio_loop, asyncio.ProactorEventLoop
  85. ):
  86. # Ignore this line for mypy because the abstract method checker
  87. # doesn't understand dynamic proxies.
  88. self.selector_loop = AddThreadSelectorEventLoop(asyncio_loop) # type: ignore
  89. # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler)
  90. self.handlers: Dict[int, Tuple[Union[int, _Selectable], Callable]] = {}
  91. # Set of fds listening for reads/writes
  92. self.readers: Set[int] = set()
  93. self.writers: Set[int] = set()
  94. self.closing = False
  95. # If an asyncio loop was closed through an asyncio interface
  96. # instead of IOLoop.close(), we'd never hear about it and may
  97. # have left a dangling reference in our map. In case an
  98. # application (or, more likely, a test suite) creates and
  99. # destroys a lot of event loops in this way, check here to
  100. # ensure that we don't have a lot of dead loops building up in
  101. # the map.
  102. #
  103. # TODO(bdarnell): consider making self.asyncio_loop a weakref
  104. # for AsyncIOMainLoop and make _ioloop_for_asyncio a
  105. # WeakKeyDictionary.
  106. for loop in IOLoop._ioloop_for_asyncio.copy():
  107. if loop.is_closed():
  108. try:
  109. del IOLoop._ioloop_for_asyncio[loop]
  110. except KeyError:
  111. pass
  112. # Make sure we don't already have an IOLoop for this asyncio loop
  113. existing_loop = IOLoop._ioloop_for_asyncio.setdefault(asyncio_loop, self)
  114. if existing_loop is not self:
  115. raise RuntimeError(
  116. f"IOLoop {existing_loop} already associated with asyncio loop {asyncio_loop}"
  117. )
  118. super().initialize(**kwargs)
  119. def close(self, all_fds: bool = False) -> None:
  120. self.closing = True
  121. for fd in list(self.handlers):
  122. fileobj, handler_func = self.handlers[fd]
  123. self.remove_handler(fd)
  124. if all_fds:
  125. self.close_fd(fileobj)
  126. # Remove the mapping before closing the asyncio loop. If this
  127. # happened in the other order, we could race against another
  128. # initialize() call which would see the closed asyncio loop,
  129. # assume it was closed from the asyncio side, and do this
  130. # cleanup for us, leading to a KeyError.
  131. del IOLoop._ioloop_for_asyncio[self.asyncio_loop]
  132. if self.selector_loop is not self.asyncio_loop:
  133. self.selector_loop.close()
  134. self.asyncio_loop.close()
  135. def add_handler(
  136. self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int
  137. ) -> None:
  138. fd, fileobj = self.split_fd(fd)
  139. if fd in self.handlers:
  140. raise ValueError("fd %s added twice" % fd)
  141. self.handlers[fd] = (fileobj, handler)
  142. if events & IOLoop.READ:
  143. self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)
  144. self.readers.add(fd)
  145. if events & IOLoop.WRITE:
  146. self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)
  147. self.writers.add(fd)
  148. def update_handler(self, fd: Union[int, _Selectable], events: int) -> None:
  149. fd, fileobj = self.split_fd(fd)
  150. if events & IOLoop.READ:
  151. if fd not in self.readers:
  152. self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)
  153. self.readers.add(fd)
  154. else:
  155. if fd in self.readers:
  156. self.selector_loop.remove_reader(fd)
  157. self.readers.remove(fd)
  158. if events & IOLoop.WRITE:
  159. if fd not in self.writers:
  160. self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE)
  161. self.writers.add(fd)
  162. else:
  163. if fd in self.writers:
  164. self.selector_loop.remove_writer(fd)
  165. self.writers.remove(fd)
  166. def remove_handler(self, fd: Union[int, _Selectable]) -> None:
  167. fd, fileobj = self.split_fd(fd)
  168. if fd not in self.handlers:
  169. return
  170. if fd in self.readers:
  171. self.selector_loop.remove_reader(fd)
  172. self.readers.remove(fd)
  173. if fd in self.writers:
  174. self.selector_loop.remove_writer(fd)
  175. self.writers.remove(fd)
  176. del self.handlers[fd]
  177. def _handle_events(self, fd: int, events: int) -> None:
  178. fileobj, handler_func = self.handlers[fd]
  179. handler_func(fileobj, events)
  180. def start(self) -> None:
  181. self.asyncio_loop.run_forever()
  182. def stop(self) -> None:
  183. self.asyncio_loop.stop()
  184. def call_at(
  185. self, when: float, callback: Callable, *args: Any, **kwargs: Any
  186. ) -> object:
  187. # asyncio.call_at supports *args but not **kwargs, so bind them here.
  188. # We do not synchronize self.time and asyncio_loop.time, so
  189. # convert from absolute to relative.
  190. return self.asyncio_loop.call_later(
  191. max(0, when - self.time()),
  192. self._run_callback,
  193. functools.partial(callback, *args, **kwargs),
  194. )
  195. def remove_timeout(self, timeout: object) -> None:
  196. timeout.cancel() # type: ignore
  197. def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
  198. try:
  199. if asyncio.get_running_loop() is self.asyncio_loop:
  200. call_soon = self.asyncio_loop.call_soon
  201. else:
  202. call_soon = self.asyncio_loop.call_soon_threadsafe
  203. except RuntimeError:
  204. call_soon = self.asyncio_loop.call_soon_threadsafe
  205. try:
  206. call_soon(self._run_callback, functools.partial(callback, *args, **kwargs))
  207. except RuntimeError:
  208. # "Event loop is closed". Swallow the exception for
  209. # consistency with PollIOLoop (and logical consistency
  210. # with the fact that we can't guarantee that an
  211. # add_callback that completes without error will
  212. # eventually execute).
  213. pass
  214. except AttributeError:
  215. # ProactorEventLoop may raise this instead of RuntimeError
  216. # if call_soon_threadsafe races with a call to close().
  217. # Swallow it too for consistency.
  218. pass
  219. def add_callback_from_signal(
  220. self, callback: Callable, *args: Any, **kwargs: Any
  221. ) -> None:
  222. warnings.warn("add_callback_from_signal is deprecated", DeprecationWarning)
  223. try:
  224. self.asyncio_loop.call_soon_threadsafe(
  225. self._run_callback, functools.partial(callback, *args, **kwargs)
  226. )
  227. except RuntimeError:
  228. pass
  229. def run_in_executor(
  230. self,
  231. executor: Optional[concurrent.futures.Executor],
  232. func: Callable[..., _T],
  233. *args: Any,
  234. ) -> "asyncio.Future[_T]":
  235. return self.asyncio_loop.run_in_executor(executor, func, *args)
  236. def set_default_executor(self, executor: concurrent.futures.Executor) -> None:
  237. return self.asyncio_loop.set_default_executor(executor)
  238. class AsyncIOMainLoop(BaseAsyncIOLoop):
  239. """``AsyncIOMainLoop`` creates an `.IOLoop` that corresponds to the
  240. current ``asyncio`` event loop (i.e. the one returned by
  241. ``asyncio.get_event_loop()``).
  242. .. deprecated:: 5.0
  243. Now used automatically when appropriate; it is no longer necessary
  244. to refer to this class directly.
  245. .. versionchanged:: 5.0
  246. Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop.
  247. """
  248. def initialize(self, **kwargs: Any) -> None: # type: ignore
  249. super().initialize(asyncio.get_event_loop(), **kwargs)
  250. def _make_current(self) -> None:
  251. # AsyncIOMainLoop already refers to the current asyncio loop so
  252. # nothing to do here.
  253. pass
  254. class AsyncIOLoop(BaseAsyncIOLoop):
  255. """``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop.
  256. This class follows the usual Tornado semantics for creating new
  257. ``IOLoops``; these loops are not necessarily related to the
  258. ``asyncio`` default event loop.
  259. Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object
  260. can be accessed with the ``asyncio_loop`` attribute.
  261. .. versionchanged:: 6.2
  262. Support explicit ``asyncio_loop`` argument
  263. for specifying the asyncio loop to attach to,
  264. rather than always creating a new one with the default policy.
  265. .. versionchanged:: 5.0
  266. When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets
  267. the current `asyncio` event loop.
  268. .. deprecated:: 5.0
  269. Now used automatically when appropriate; it is no longer necessary
  270. to refer to this class directly.
  271. """
  272. def initialize(self, **kwargs: Any) -> None: # type: ignore
  273. self.is_current = False
  274. loop = None
  275. if "asyncio_loop" not in kwargs:
  276. kwargs["asyncio_loop"] = loop = asyncio.new_event_loop()
  277. try:
  278. super().initialize(**kwargs)
  279. except Exception:
  280. # If initialize() does not succeed (taking ownership of the loop),
  281. # we have to close it.
  282. if loop is not None:
  283. loop.close()
  284. raise
  285. def close(self, all_fds: bool = False) -> None:
  286. if self.is_current:
  287. self._clear_current()
  288. super().close(all_fds=all_fds)
  289. def _make_current(self) -> None:
  290. if not self.is_current:
  291. try:
  292. self.old_asyncio = asyncio.get_event_loop()
  293. except (RuntimeError, AssertionError):
  294. self.old_asyncio = None # type: ignore
  295. self.is_current = True
  296. asyncio.set_event_loop(self.asyncio_loop)
  297. def _clear_current_hook(self) -> None:
  298. if self.is_current:
  299. asyncio.set_event_loop(self.old_asyncio)
  300. self.is_current = False
  301. def to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future:
  302. """Convert an `asyncio.Future` to a `tornado.concurrent.Future`.
  303. .. versionadded:: 4.1
  304. .. deprecated:: 5.0
  305. Tornado ``Futures`` have been merged with `asyncio.Future`,
  306. so this method is now a no-op.
  307. """
  308. return asyncio_future
  309. def to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future:
  310. """Convert a Tornado yieldable object to an `asyncio.Future`.
  311. .. versionadded:: 4.1
  312. .. versionchanged:: 4.3
  313. Now accepts any yieldable object, not just
  314. `tornado.concurrent.Future`.
  315. .. deprecated:: 5.0
  316. Tornado ``Futures`` have been merged with `asyncio.Future`,
  317. so this method is now equivalent to `tornado.gen.convert_yielded`.
  318. """
  319. return convert_yielded(tornado_future)
  320. _AnyThreadEventLoopPolicy = None
  321. def __getattr__(name: str) -> typing.Any:
  322. # The event loop policy system is deprecated in Python 3.14; simply accessing
  323. # the name asyncio.DefaultEventLoopPolicy will raise a warning. Lazily create
  324. # the AnyThreadEventLoopPolicy class so that the warning is only raised if
  325. # the policy is used.
  326. if name != "AnyThreadEventLoopPolicy":
  327. raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
  328. global _AnyThreadEventLoopPolicy
  329. if _AnyThreadEventLoopPolicy is None:
  330. if sys.platform == "win32" and hasattr(
  331. asyncio, "WindowsSelectorEventLoopPolicy"
  332. ):
  333. # "Any thread" and "selector" should be orthogonal, but there's not a clean
  334. # interface for composing policies so pick the right base.
  335. _BasePolicy = asyncio.WindowsSelectorEventLoopPolicy # type: ignore
  336. else:
  337. _BasePolicy = asyncio.DefaultEventLoopPolicy
  338. class AnyThreadEventLoopPolicy(_BasePolicy): # type: ignore
  339. """Event loop policy that allows loop creation on any thread.
  340. The default `asyncio` event loop policy only automatically creates
  341. event loops in the main threads. Other threads must create event
  342. loops explicitly or `asyncio.get_event_loop` (and therefore
  343. `.IOLoop.current`) will fail. Installing this policy allows event
  344. loops to be created automatically on any thread, matching the
  345. behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2).
  346. Usage::
  347. asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
  348. .. versionadded:: 5.0
  349. .. deprecated:: 6.2
  350. ``AnyThreadEventLoopPolicy`` affects the implicit creation
  351. of an event loop, which is deprecated in Python 3.10 and
  352. will be removed in a future version of Python. At that time
  353. ``AnyThreadEventLoopPolicy`` will no longer be useful.
  354. If you are relying on it, use `asyncio.new_event_loop`
  355. or `asyncio.run` explicitly in any non-main threads that
  356. need event loops.
  357. """
  358. def __init__(self) -> None:
  359. super().__init__()
  360. warnings.warn(
  361. "AnyThreadEventLoopPolicy is deprecated, use asyncio.run "
  362. "or asyncio.new_event_loop instead",
  363. DeprecationWarning,
  364. stacklevel=2,
  365. )
  366. def get_event_loop(self) -> asyncio.AbstractEventLoop:
  367. try:
  368. return super().get_event_loop()
  369. except RuntimeError:
  370. # "There is no current event loop in thread %r"
  371. loop = self.new_event_loop()
  372. self.set_event_loop(loop)
  373. return loop
  374. _AnyThreadEventLoopPolicy = AnyThreadEventLoopPolicy
  375. return _AnyThreadEventLoopPolicy
  376. class SelectorThread:
  377. """Define ``add_reader`` methods to be called in a background select thread.
  378. Instances of this class start a second thread to run a selector.
  379. This thread is completely hidden from the user;
  380. all callbacks are run on the wrapped event loop's thread.
  381. Typically used via ``AddThreadSelectorEventLoop``,
  382. but can be attached to a running asyncio loop.
  383. """
  384. _closed = False
  385. def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:
  386. self._main_thread_ctx = contextvars.copy_context()
  387. self._real_loop = real_loop
  388. self._select_cond = threading.Condition()
  389. self._select_args: Optional[
  390. Tuple[List[_FileDescriptorLike], List[_FileDescriptorLike]]
  391. ] = None
  392. self._closing_selector = False
  393. self._thread: Optional[threading.Thread] = None
  394. self._thread_manager_handle = self._thread_manager()
  395. async def thread_manager_anext() -> None:
  396. # the anext builtin wasn't added until 3.10. We just need to iterate
  397. # this generator one step.
  398. await self._thread_manager_handle.__anext__()
  399. # When the loop starts, start the thread. Not too soon because we can't
  400. # clean up if we get to this point but the event loop is closed without
  401. # starting.
  402. self._real_loop.call_soon(
  403. lambda: self._real_loop.create_task(thread_manager_anext()),
  404. context=self._main_thread_ctx,
  405. )
  406. self._readers: Dict[_FileDescriptorLike, Callable] = {}
  407. self._writers: Dict[_FileDescriptorLike, Callable] = {}
  408. # Writing to _waker_w will wake up the selector thread, which
  409. # watches for _waker_r to be readable.
  410. self._waker_r, self._waker_w = socket.socketpair()
  411. self._waker_r.setblocking(False)
  412. self._waker_w.setblocking(False)
  413. _selector_loops.add(self)
  414. self.add_reader(self._waker_r, self._consume_waker)
  415. def close(self) -> None:
  416. if self._closed:
  417. return
  418. with self._select_cond:
  419. self._closing_selector = True
  420. self._select_cond.notify()
  421. self._wake_selector()
  422. if self._thread is not None:
  423. self._thread.join()
  424. _selector_loops.discard(self)
  425. self.remove_reader(self._waker_r)
  426. self._waker_r.close()
  427. self._waker_w.close()
  428. self._closed = True
  429. async def _thread_manager(self) -> typing.AsyncGenerator[None, None]:
  430. # Create a thread to run the select system call. We manage this thread
  431. # manually so we can trigger a clean shutdown from an atexit hook. Note
  432. # that due to the order of operations at shutdown, only daemon threads
  433. # can be shut down in this way (non-daemon threads would require the
  434. # introduction of a new hook: https://bugs.python.org/issue41962)
  435. self._thread = threading.Thread(
  436. name="Tornado selector",
  437. daemon=True,
  438. target=self._run_select,
  439. )
  440. self._thread.start()
  441. self._start_select()
  442. try:
  443. # The presense of this yield statement means that this coroutine
  444. # is actually an asynchronous generator, which has a special
  445. # shutdown protocol. We wait at this yield point until the
  446. # event loop's shutdown_asyncgens method is called, at which point
  447. # we will get a GeneratorExit exception and can shut down the
  448. # selector thread.
  449. yield
  450. except GeneratorExit:
  451. self.close()
  452. raise
  453. def _wake_selector(self) -> None:
  454. if self._closed:
  455. return
  456. try:
  457. self._waker_w.send(b"a")
  458. except BlockingIOError:
  459. pass
  460. def _consume_waker(self) -> None:
  461. try:
  462. self._waker_r.recv(1024)
  463. except BlockingIOError:
  464. pass
  465. def _start_select(self) -> None:
  466. # Capture reader and writer sets here in the event loop
  467. # thread to avoid any problems with concurrent
  468. # modification while the select loop uses them.
  469. with self._select_cond:
  470. assert self._select_args is None
  471. self._select_args = (list(self._readers.keys()), list(self._writers.keys()))
  472. self._select_cond.notify()
  473. def _run_select(self) -> None:
  474. while True:
  475. with self._select_cond:
  476. while self._select_args is None and not self._closing_selector:
  477. self._select_cond.wait()
  478. if self._closing_selector:
  479. return
  480. assert self._select_args is not None
  481. to_read, to_write = self._select_args
  482. self._select_args = None
  483. # We use the simpler interface of the select module instead of
  484. # the more stateful interface in the selectors module because
  485. # this class is only intended for use on windows, where
  486. # select.select is the only option. The selector interface
  487. # does not have well-documented thread-safety semantics that
  488. # we can rely on so ensuring proper synchronization would be
  489. # tricky.
  490. try:
  491. # On windows, selecting on a socket for write will not
  492. # return the socket when there is an error (but selecting
  493. # for reads works). Also select for errors when selecting
  494. # for writes, and merge the results.
  495. #
  496. # This pattern is also used in
  497. # https://github.com/python/cpython/blob/v3.8.0/Lib/selectors.py#L312-L317
  498. rs, ws, xs = select.select(to_read, to_write, to_write)
  499. ws = ws + xs
  500. except OSError as e:
  501. # After remove_reader or remove_writer is called, the file
  502. # descriptor may subsequently be closed on the event loop
  503. # thread. It's possible that this select thread hasn't
  504. # gotten into the select system call by the time that
  505. # happens in which case (at least on macOS), select may
  506. # raise a "bad file descriptor" error. If we get that
  507. # error, check and see if we're also being woken up by
  508. # polling the waker alone. If we are, just return to the
  509. # event loop and we'll get the updated set of file
  510. # descriptors on the next iteration. Otherwise, raise the
  511. # original error.
  512. if e.errno == getattr(errno, "WSAENOTSOCK", errno.EBADF):
  513. rs, _, _ = select.select([self._waker_r.fileno()], [], [], 0)
  514. if rs:
  515. ws = []
  516. else:
  517. raise
  518. else:
  519. raise
  520. try:
  521. self._real_loop.call_soon_threadsafe(
  522. self._handle_select, rs, ws, context=self._main_thread_ctx
  523. )
  524. except RuntimeError:
  525. # "Event loop is closed". Swallow the exception for
  526. # consistency with PollIOLoop (and logical consistency
  527. # with the fact that we can't guarantee that an
  528. # add_callback that completes without error will
  529. # eventually execute).
  530. pass
  531. except AttributeError:
  532. # ProactorEventLoop may raise this instead of RuntimeError
  533. # if call_soon_threadsafe races with a call to close().
  534. # Swallow it too for consistency.
  535. pass
  536. def _handle_select(
  537. self, rs: List[_FileDescriptorLike], ws: List[_FileDescriptorLike]
  538. ) -> None:
  539. for r in rs:
  540. self._handle_event(r, self._readers)
  541. for w in ws:
  542. self._handle_event(w, self._writers)
  543. self._start_select()
  544. def _handle_event(
  545. self,
  546. fd: _FileDescriptorLike,
  547. cb_map: Dict[_FileDescriptorLike, Callable],
  548. ) -> None:
  549. try:
  550. callback = cb_map[fd]
  551. except KeyError:
  552. return
  553. callback()
  554. def add_reader(
  555. self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
  556. ) -> None:
  557. self._readers[fd] = functools.partial(callback, *args)
  558. self._wake_selector()
  559. def add_writer(
  560. self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
  561. ) -> None:
  562. self._writers[fd] = functools.partial(callback, *args)
  563. self._wake_selector()
  564. def remove_reader(self, fd: _FileDescriptorLike) -> bool:
  565. try:
  566. del self._readers[fd]
  567. except KeyError:
  568. return False
  569. self._wake_selector()
  570. return True
  571. def remove_writer(self, fd: _FileDescriptorLike) -> bool:
  572. try:
  573. del self._writers[fd]
  574. except KeyError:
  575. return False
  576. self._wake_selector()
  577. return True
  578. class AddThreadSelectorEventLoop(asyncio.AbstractEventLoop):
  579. """Wrap an event loop to add implementations of the ``add_reader`` method family.
  580. Instances of this class start a second thread to run a selector.
  581. This thread is completely hidden from the user; all callbacks are
  582. run on the wrapped event loop's thread.
  583. This class is used automatically by Tornado; applications should not need
  584. to refer to it directly.
  585. It is safe to wrap any event loop with this class, although it only makes sense
  586. for event loops that do not implement the ``add_reader`` family of methods
  587. themselves (i.e. ``WindowsProactorEventLoop``)
  588. Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop.
  589. """
  590. # This class is a __getattribute__-based proxy. All attributes other than those
  591. # in this set are proxied through to the underlying loop.
  592. MY_ATTRIBUTES = {
  593. "_real_loop",
  594. "_selector",
  595. "add_reader",
  596. "add_writer",
  597. "close",
  598. "remove_reader",
  599. "remove_writer",
  600. }
  601. def __getattribute__(self, name: str) -> Any:
  602. if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES:
  603. return super().__getattribute__(name)
  604. return getattr(self._real_loop, name)
  605. def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None:
  606. self._real_loop = real_loop
  607. self._selector = SelectorThread(real_loop)
  608. def close(self) -> None:
  609. self._selector.close()
  610. self._real_loop.close()
  611. def add_reader(
  612. self,
  613. fd: "_FileDescriptorLike",
  614. callback: Callable[..., None],
  615. *args: "Unpack[_Ts]",
  616. ) -> None:
  617. return self._selector.add_reader(fd, callback, *args)
  618. def add_writer(
  619. self,
  620. fd: "_FileDescriptorLike",
  621. callback: Callable[..., None],
  622. *args: "Unpack[_Ts]",
  623. ) -> None:
  624. return self._selector.add_writer(fd, callback, *args)
  625. def remove_reader(self, fd: "_FileDescriptorLike") -> bool:
  626. return self._selector.remove_reader(fd)
  627. def remove_writer(self, fd: "_FileDescriptorLike") -> bool:
  628. return self._selector.remove_writer(fd)