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

Trio loop #479

merged 3 commits into from
May 20, 2020

Conversation

mehaase
Copy link
Contributor

@mehaase mehaase commented Jan 24, 2020

This PR adds support for running a long-lived Trio event loop on the foreground thread and pushes the ZMQ/other kernel stuff into a background thread. When Trio runs on the main thread, all cells are executed as tasks inside a global nursery. The global nursery is also injected into builtins as GLOBAL_NURSERY so that notebook users can run tasks in the background even after a cell finishes executing. Exceptions in background tasks are caught and displayed in the current cell.

This requires a matching PR on IPython itself!!

The loop is activated by passing a kernel flag, so by default this PR doesn't affect any current users—not even existing %autoawait trio users. It's completely opt-in. The following example configuration shows how to start a kernel in Trio loop mode:

{
 "argv": [
  "python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}",
  "--IPKernelApp.trio_loop=True"
 ],
 "display_name": "Python 3 Trio",
 "language": "python"
}

(Save this file as .../share/jupyter/python-trio/kernel.json. The leading ... part will vary depending on whether you're installing system-wide, per-user, or within a Python venv.)

In Trio loop mode, the kernel implicitly runs %autoawait trio at startup. It also disables the %autoawait magic to prevent users from trying to switch loops, which would certainly fail.

Here's an example notebooks session running in this mode:

notebook running in trio loop mode

We know the code isn't pretty, and we're seeking feedback on some of the implementation decisions, including (but not limited to):

  • How to make a global nursery available to notebook users? Injecting into builtins is pretty hacky, as is hard-coding the name GLOBAL_NURSERY.
  • How to inject the TrioRunner created in kernelapp.py into the shell instance, where it is needed to run cells on the main Trio loop.
  • Whether to disable %autoawait and how best to do so. If disabled, what's the best way to warn the user?

mehaase and others added 2 commits January 24, 2020 15:38
This flag causes Trio to run on the main thread, and puts ZMQ and other
stuff on a background thread. When trio runs on the main thread, async
cells are executed as tasks inside a global nursery. A global nursery
named GLOBAL_NURSERY is exported in builtins so that background tasks
can continue to run even after a cell finishes executing. Exceptions in
background tasks are caught and displayed in the current cell.

This implicitly runs `%autoawait trio` when the kernel starts and then
disables the %autoawait magic so that users can't switch to a different
loop while in Trio loop mode.

Co-authored-by: Brian Mackintosh <bcmackintosh@gmail.com>
This minimizes the amount of change in kernelapp.py.

Co-authored-by: Brian Mackintosh <bcmackintosh@gmail.com>
The trio runner didn't support kernel interrupts: it would just continue
to hang. I'm not sure why, but kernelapp.py ignores SIGINT. So in this
commit, the Trio runner registers a new SIGINT handler that uses Trio
cancel scopes to cancel the currently executing cell.

The downside to this approach is that if a cell hangs in a loop that
never reaches a Trio checkpoint, then the cancellation will have no
effect. We could have sent SIGINT to the main thread (i.e. SIG_DFL), but
that would have the effect of potentially raising SIGINT on one of the
background tasks, which would likely force you to restart the kernel.
@Carreau
Copy link
Member

Carreau commented Feb 2, 2020

Matching PR is in IPython and has been released as IPython 7.12.

@Carreau Carreau added this to the 5.2 milestone Feb 2, 2020
@auphofBSF
Copy link

auphofBSF commented Feb 25, 2020

Firstly thank you for this work, I am trying to use trio and derivative classes in a ipython/ipykernel running within Hydrogen in Atom IDE. (version details below) Your Untitled17 example above works but I am getting some issues. I will write the 2nd issue in a followup comment. Important to note the issue does not appear when I run in Jupyter notebook

1)In AtomIDE with Hydrogen on the first run of a kernel previously stopped. Executing import trio produces the following error

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\Anaconda3\envs\BSFE20W07PY37dataAnalysis\lib\site-packages\trio\_threads.py in _run_fn_as_system_task(cb, fn, trio_token, *args)
    324         try:
--> 325             trio_token = TOKEN_LOCAL.token
    326         except AttributeError:

AttributeError: '_thread._local' object has no attribute 'token'

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
c:\users\bsfau\cloudstation\bsfsoftdev\axternal\ipykernel\ipykernel\trio_runner.py in __call__(self, async_fn)
     58             self._cell_cancel_scope = None
     59 
---> 60         return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token)

~\Anaconda3\envs\BSFE20W07PY37dataAnalysis\lib\site-packages\trio\_threads.py in from_thread_run(afn, trio_token, *args)
    389         trio.hazmat.spawn_system_task(await_in_trio_thread_task, name=afn)
    390 
--> 391     return _run_fn_as_system_task(callback, afn, *args, trio_token=trio_token)
    392 
    393 

~\Anaconda3\envs\BSFE20W07PY37dataAnalysis\lib\site-packages\trio\_threads.py in _run_fn_as_system_task(cb, fn, trio_token, *args)
    326         except AttributeError:
    327             raise RuntimeError(
--> 328                 "this thread wasn't created by Trio, pass kwarg trio_token=..."
    329             )
    330 

RuntimeError: this thread wasn't created by Trio, pass kwarg trio_token=...

Retrying the import trio executes successfully and then all lines as per your example execute successfully. If I restart the kernel there is no error, only after shutdown of kernel does it appear
image

Versions:

ipykernel : origin https://github.com/HyperionGray/ipykernel.git -branch: trio-loop commit 3bb1bf5 31/1/2020
ipython 7.12.0
trio 0.13.0
python 3.7.6
atom 1.43.0
electron 4.2.7
chrome 69.0.3497.128
node v10.11.0
hydrogen 2.13.1
windows 10
conda version : 4.7.10
conda-build version : 3.18.9
python version : 3.6.9.final.0

@auphofBSF
Copy link

Warnings from %autoawait ??? are only shown on 1st execution, see below
image

This is a 2nd Issue to #479 (comment) ,same version details apply

@blink1073 blink1073 modified the milestones: 5.2, 6.0 Mar 21, 2020
@blink1073 blink1073 modified the milestones: 6.0, 5.3 May 20, 2020
Copy link
Contributor

@blink1073 blink1073 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@blink1073 blink1073 merged commit e524565 into ipython:master May 20, 2020
@mehaase
Copy link
Contributor Author

mehaase commented Apr 1, 2021

For people who read this thread in the future, I made a new package trio-jupyter that installs a Jupyter kernelspec with Trio-mode enabled. This simplifies the process of setting it up. https://github.com/mehaase/trio-jupyter

@blink1073
Copy link
Contributor

Nice, thanks for sharing @mehaase!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants