Format: text/x-python
⬇ Download File (2.4 KB)
   1  """A trio loop runner."""
   2  import builtins
   3  import logging
   4  import signal
   5  import threading
   6  import traceback
   7  import warnings
   8  
   9  import trio
  10  
  11  
  12  class TrioRunner:
  13      """A trio loop runner."""
  14  
  15      def __init__(self):
  16          """Initialize the runner."""
  17          self._cell_cancel_scope = None
  18          self._trio_token = None
  19  
  20      def initialize(self, kernel, io_loop):
  21          """Initialize the runner."""
  22          kernel.shell.set_trio_runner(self)
  23          kernel.shell.run_line_magic("autoawait", "trio")
  24          kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn(
  25              "Autoawait isn't allowed in Trio background loop mode.", stacklevel=2
  26          )
  27          self._interrupted = False
  28          bg_thread = threading.Thread(target=io_loop.start, daemon=True, name="TornadoBackground")
  29          bg_thread.start()
  30  
  31      def interrupt(self, signum, frame):
  32          """Interuppt the runner."""
  33          if self._cell_cancel_scope:
  34              self._cell_cancel_scope.cancel()
  35          else:
  36              msg = "Kernel interrupted but no cell is running"
  37              raise Exception(msg)
  38  
  39      def run(self):
  40          """Run the loop."""
  41          old_sig = signal.signal(signal.SIGINT, self.interrupt)
  42  
  43          def log_nursery_exc(exc):
  44              exc = "\n".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
  45              logging.error("An exception occurred in a global nursery task.\n%s", exc)
  46  
  47          async def trio_main():
  48              """Run the main loop."""
  49              self._trio_token = trio.lowlevel.current_trio_token()
  50              async with trio.open_nursery() as nursery:
  51                  # TODO This hack prevents the nursery from cancelling all child
  52                  # tasks when an uncaught exception occurs, but it's ugly.
  53                  nursery._add_exc = log_nursery_exc
  54                  builtins.GLOBAL_NURSERY = nursery  # type:ignore[attr-defined]
  55                  await trio.sleep_forever()
  56  
  57          trio.run(trio_main)
  58          signal.signal(signal.SIGINT, old_sig)
  59  
  60      def __call__(self, async_fn):
  61          """Handle a function call."""
  62  
  63          async def loc(coro):
  64              """A thread runner context."""
  65              self._cell_cancel_scope = trio.CancelScope()
  66              with self._cell_cancel_scope:
  67                  return await coro
  68              self._cell_cancel_scope = None  # type:ignore[unreachable]
  69              return None
  70  
  71          return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token)
  72