-
-
Notifications
You must be signed in to change notification settings - Fork 361
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
Kernel subshells (JEP91) implementation #1249
base: main
Are you sure you want to change the base?
Conversation
651299f
to
9139669
Compare
The kernel subshells JEP has been accepted and as this is the reference implementation for that I am asking for someone to review this. |
ipykernel/subshell_manager.py
Outdated
while True: | ||
for socket, _ in self._poller.poll(0): | ||
msg = await socket.recv_multipart(copy=False) | ||
self._shell_socket.send_multipart(msg) | ||
|
||
# Yield to other tasks. | ||
await sleep(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop leads to 100% CPU usage, we need to find another way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. On my macOS dev machine this is fine, but I've confirmed that it is a problem on Linux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the implementation in 97e3e91. I've removed the sleep(0)
and poll(0)
calls and now use an async zmq Poller
in an anyio
task. When a subshell is created or deleted this task is cancelled using an anyio.Event
and rescheduled with the updated list of subshells. With this I no longer see 100% CPU load on Linux or macOS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the implementation of this piece of code again. My use of await poller.poll()
turned out to cause problems on python < 3.10 and on Windows. Now I am avoiding use of zmq.Poller
and instead using a separate anyio task for each subshell to listen for reply messages and send them out to the client via the shell channel. This seems to be more robust on older python and Windows. I've also replaced the use of an anyio.Event
with a memory object stream instead (essentially an anyio async queue).
for more information, see https://pre-commit.ci
Local tests are showing occasional errors in |
pyproject.toml
Outdated
@@ -188,6 +188,7 @@ filterwarnings= [ | |||
# https://github.com/python-trio/trio/issues/3053 | |||
"ignore:The `hash` argument is deprecated in favor of `unsafe_hash` and will be removed in or after August 2025.", | |||
] | |||
asyncio_default_fixture_loop_scope = "function" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comes from pytest-asyncio
, but don't we use AnyIO's pytest plugin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does come from pytest-asyncio
, and evidently it is used via some means as there are warnings before this change and not after. But I am going to revert this change as this option is not available for the minimum dependencies run, and I can tolerate a warning.
I had missed a direct use of the |
This is the implementation of the kernel subshells JEP (jupyter/enhancement-proposals#91). It follows the latest commit (1f1ad3d) with the addition of a
%subshell
magic command that is useful for debugging. To try this out I have a JupyterLab branch that talks to this branch and is most easily tried out using https://mybinder.org/v2/gh/ianthomas23/jupyterlab/jep91_demo?urlpath=lab; once the mybinder instance has started, open thesubshell_demo_notebook.ipynb
and follow the instructions therein.The idea is that this is mergeable as it is now, it is backward compatible in that it does not break any existing use of
ipykernel
(subject to CI confirmation). There are some ramifications of the protocol additions (outlined below) that will need addressing eventually, but I consider these future work that can be in separate PRs.Outline of changes
subshell_id
from the message and passes it on to the correct subshell.SubshellManager
in the shell channel thread is responsible for subshell lifetimes.Example scenario
Here is an example of the communication between threads when running a long task in the parent subshell (main thread) and whilst this is running a child subshell is created, used, and deleted.
Future work
ipykernel
the wholesome of the message to get thesubshell_id
. Ideally it would only deserialise the header. May need changes in Jupyter Client.threading.Event
following the existinganyio
implementation which requires an extra thread perEvent
. It would be nice if this could be changed so a subshell is a single thread not two.input()
on more than one subshell at the same time run but do not store correctly.JupyterLab
The JupyterLab branch I am using to demo this isn't really intended to be merged. But if it was, it needs:
kernel_info
to see if subshells are supported.ConsolePanel
.(Edited for clarity)