Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trio loop #479

Merged
merged 3 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions ipykernel/kernelapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
{'IPKernelApp' : {'pylab' : 'auto'}},
"""Pre-load matplotlib and numpy for interactive use with
the default matplotlib backend."""),
'trio-loop' : (
{'InteractiveShell' : {'trio_loop' : False}},
'Enable Trio as main event loop.'
),
})

# inherit flags&aliases for any IPython shell apps
Expand Down Expand Up @@ -147,6 +151,7 @@ def abs_connection_file(self):
# streams, etc.
no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True)
no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True)
trio_loop = Bool(False, help="Set main event loop.").tag(config=True)
quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True)
outstream_class = DottedObjectName('ipykernel.iostream.OutStream',
help="The importstring for the OutStream factory").tag(config=True)
Expand Down Expand Up @@ -579,10 +584,19 @@ def start(self):
self.poller.start()
self.kernel.start()
self.io_loop = ioloop.IOLoop.current()
try:
self.io_loop.start()
except KeyboardInterrupt:
pass
if self.trio_loop:
from ipykernel.trio_runner import TrioRunner
tr = TrioRunner()
tr.initialize(self.kernel, self.io_loop)
try:
tr.run()
except KeyboardInterrupt:
pass
else:
try:
self.io_loop.start()
except KeyboardInterrupt:
pass


launch_new_instance = IPKernelApp.launch_instance
Expand Down
60 changes: 60 additions & 0 deletions ipykernel/trio_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import builtins
import logging
import signal
import threading
import traceback
import warnings

import trio


class TrioRunner:
def __init__(self):
self._cell_cancel_scope = None
self._trio_token = None

def initialize(self, kernel, io_loop):
kernel.shell.set_trio_runner(self)
kernel.shell.run_line_magic('autoawait', 'trio')
kernel.shell.magics_manager.magics['line']['autoawait'] = \
lambda _: warnings.warn("Autoawait isn't allowed in Trio "
"background loop mode.")
bg_thread = threading.Thread(target=io_loop.start, daemon=True,
name='TornadoBackground')
bg_thread.start()

def interrupt(self, signum, frame):
if self._cell_cancel_scope:
self._cell_cancel_scope.cancel()
else:
raise Exception('Kernel interrupted but no cell is running')

def run(self):
old_sig = signal.signal(signal.SIGINT, self.interrupt)

def log_nursery_exc(exc):
exc = '\n'.join(traceback.format_exception(type(exc), exc,
exc.__traceback__))
logging.error('An exception occurred in a global nursery task.\n%s',
exc)

async def trio_main():
self._trio_token = trio.hazmat.current_trio_token()
async with trio.open_nursery() as nursery:
# TODO This hack prevents the nursery from cancelling all child
# tasks when an uncaught exception occurs, but it's ugly.
nursery._add_exc = log_nursery_exc
builtins.GLOBAL_NURSERY = nursery
await trio.sleep_forever()

trio.run(trio_main)
signal.signal(signal.SIGINT, old_sig)

def __call__(self, async_fn):
async def loc(coro):
self._cell_cancel_scope = trio.CancelScope()
with self._cell_cancel_scope:
return await coro
self._cell_cancel_scope = None

return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token)