-
-
Notifications
You must be signed in to change notification settings - Fork 373
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
Support subshells #955
Support subshells #955
Conversation
for more information, see https://pre-commit.ci
ipykernel/kernelbase.py
Outdated
@@ -396,28 +403,33 @@ async def dispatch_shell(self, msg): | |||
self.log.warning("Unknown message type: %r", msg_type) | |||
else: | |||
self.log.debug("%s: %s", msg_type, msg) | |||
asyncio.run_coroutine_threadsafe(self.call_handler(shell_id, handler, idents, msg), self.shell_threads[shell_id].io_loop.asyncio_loop) |
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 cannot work because you will send replies on the same socket from two different threads, and ZMQ sockets are NOT thread safe (unless PyZQM adds some synchronization mechanism in the bindings, I need to check). Potentially same issue if you authorize stdin in the subshell.
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.
Yes, but this can be solved by adding locks when sending messages.
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.
The whole point of ZeroMQ is to avoid to share sockets (or anything actually) among threads, do not use multithreading primitives to synchronize threads, they should be synchronized via interprocess sockets. Even with a lock, you have no guarantee that the socket will handle both threads correclty.
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.
If that is true (I could not find any documentation confirming what you said), then it means that we should use a new ZMQ socket per subshell.
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.
Here is a link to the specific section of ZeroMQ guide for more detail about my last statement.
EDIT: sorry I missed you answer above, thus my partial answer here
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.
Thanks, that is quite clear indeed 😄
"To make utterly perfect MT programs (and I mean that literally), we don’t need mutexes, locks, or any other form of inter-thread communication except messages sent across ZeroMQ sockets."
So, do we agree that we should let down the approach consisting of one ZMQ channel for all subshells, and instead have one ZMQ channel per subshell?
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.
Indeed I find the second approach cleaner since we delegate the routing to ZeroMQ (although it requires additional changes in the server). However, for the sake of completeness (and if you want to still experiment with the first approach), the way to implement it would be:
- one thread polling the shell sockets and additional PAIR sockets for communication between the poller and the execution threads.
- one thread for the main shell and one per subshell.
When a message arrives on the shell, the polling thread parses it and sends it to the corresponding thread via the pair socket. The executing thread should push the answer on that pair socket instead of sending it on the shell socket.
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.
Yes I'm currently implementing it but:
one thread polling the shell sockets and additional PAIR sockets for interthread communication.
There is only one shell socket and I'm using queues for inter-thread communication instead of sockets.
one thread for the main shell and one per subshell.
The main shell is in the main thread, each subshell has its own thread.
In 0bde221 the processing of the main shell requests is done in the main thread, and the shell messages are received in a separate thread. |
I added a test in a64cf72 which checks that execution in a subshell is done in parallel with execution in the main shell. |
Regarding the implementation with a new zmq channel, the advantages would be:
|
66b038d
to
6de6783
Compare
@davidbrochart I think this another PR that we can close as superseded by #1249. |
This PR aims at implementing potential solutions to support "subshells" (see jupyter/jupyter_client#806).
In this first commit, the main shell runs in its own thread (which is not the main thread). A subshell can be created through a
subshell_request
received on theshell
channel. This creates a new thread for this subshell, identified by a subshell ID. A request targeting this subshell must specify itssubshell_id
in themetadata
(if not specified, then the request is targeting the main shell).Shell messages are received in the main thread and handled in the thread corresponding to the shell ID (the ID of the main shell is
main
, the ID of a subshell is a UUID). Thus, it is always possible to receive shell messages, even if a shell request is being handled.This approach modifies the behavior of ipykernel in the following ways:
await
), then they can be executed concurrently, similarly to what akernel does.stdout
orstderr
) currently makes the assumption that shell requests are received and handled one at a time, which is not the case anymore: the next shell request can be received while the previous one is being handled.