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
| 454 | |
| 455 | |
| 456 | class AddThreadSelectorEventLoop(asyncio.AbstractEventLoop): |
| 457 | """Wrap an event loop to add implementations of the ``add_reader`` method family. |
| 458 | |
| 459 | Instances of this class start a second thread to run a selector. |
| 460 | This thread is completely hidden from the user; all callbacks are |
| 461 | run on the wrapped event loop's thread. |
| 462 | |
| 463 | This class is used automatically by Tornado; applications should not need |
| 464 | to refer to it directly. |
| 465 | |
| 466 | It is safe to wrap any event loop with this class, although it only makes sense |
| 467 | for event loops that do not implement the ``add_reader`` family of methods |
| 468 | themselves (i.e. ``WindowsProactorEventLoop``) |
| 469 | |
| 470 | Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop. |
| 471 | |
| 472 | """ |
| 473 | |
| 474 | # This class is a __getattribute__-based proxy. All attributes other than those |
| 475 | # in this set are proxied through to the underlying loop. |
| 476 | MY_ATTRIBUTES = { |
| 477 | "_consume_waker", |
| 478 | "_select_cond", |
| 479 | "_select_args", |
| 480 | "_closing_selector", |
| 481 | "_thread", |
| 482 | "_handle_event", |
| 483 | "_readers", |
| 484 | "_real_loop", |
| 485 | "_start_select", |
| 486 | "_run_select", |
| 487 | "_handle_select", |
| 488 | "_wake_selector", |
| 489 | "_waker_r", |
| 490 | "_waker_w", |
| 491 | "_writers", |
| 492 | "add_reader", |
| 493 | "add_writer", |
| 494 | "close", |
| 495 | "remove_reader", |
| 496 | "remove_writer", |
| 497 | } |
| 498 | |
| 499 | def __getattribute__(self, name: str) -> Any: |
| 500 | if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES: |
| 501 | return super().__getattribute__(name) |
| 502 | return getattr(self._real_loop, name) |
| 503 | |
| 504 | def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None: |
| 505 | self._real_loop = real_loop |
| 506 | |
| 507 | # Create a thread to run the select system call. We manage this thread |
| 508 | # manually so we can trigger a clean shutdown from an atexit hook. Note |
| 509 | # that due to the order of operations at shutdown, only daemon threads |
| 510 | # can be shut down in this way (non-daemon threads would require the |
| 511 | # introduction of a new hook: https://bugs.python.org/issue41962) |
| 512 | self._select_cond = threading.Condition() |
| 513 | self._select_args = ( |