[IPython-dev] Kernel-client communication
ellisonbg at gmail.com
Mon Sep 6 13:02:16 EDT 2010
On Fri, Sep 3, 2010 at 1:30 AM, Almar Klein <almar.klein at gmail.com> wrote:
> I've been thinking about your protocol last night. (arg! This whole thing is
> getting to my head too much!)
> I see some advantages of loose messages such as your using in favor of my
> channels approach. Specifically, the order of stdout and stderr messages is
> not preserved, as they are send over a different channel (same socket
> though). On a 1to1 system, this is not much of a problem, but for multiple
> users it definitely will be, specifically if you also want to show the other
> users input (pyin).
> Anyway, I took another good look at your approach and I have a few
> remarks/ideas. Might be useful, might be total BS.
> * You say the prompt is a thing the client decides what it looks like. I
> disagree. The kernel gives a prompt to indicate that the user can give
> input, and maybe also what kind of input. A debugger might produce a
> different prompt to indicate that the user is in debug mode. See also the
> doc on sys.ps1 and sys.ps2 (http://docs.python.org/library/sys.html): the
> user (or interpreter) can put an object on it that evaluates to something
> nice when str()'nged.
> * You plan on keeping history in the kernel. In this case I think this is
> the task of the client. Otherwise you'd get your own history mixed with that
> of someone else using that kernel? History is, I think, a feature of the
> client to help the programmer. I see no use for storing it at the kernel.
We keep the history is both places, which allows either party to
reconstruct things if the other goes down.
> * I really think you can do with less sockets. I believe that the (black)
> req/rep pair is not needed. You only seem to use it for when raw_input is
> used. But why? When raw_input is used, you can just block and wait for some
> stdin (I think that'll be the execute_request message). This should not be
> too hard by replacing sys.stdin with an object that has a readline method
> that does this. If two users are present, and one calls raw_input, they can
> both provide input (whoever's first). To indicate this to the *other* user,
> however, his prompt should be replaced with an empty string, so his cursor
> is positioned right after the <text> in raw_input('<text>').
We could do with fewer pairs, but having different socket pairs for
different purposes buys us a lot of isolation, encapsulation and
robustness in our code base.
> * I think you can do with even less sockets :) But this is more of a wild
> idea. Say that John set up an experiment at work and wants to check the
> results in the bar on his Android (sorry I stole your example here,
> Fernando). Now his experiment crashed, producing a traceback in the client
> at his work PC. But now he cannot see the traceback as he just logged in!
> ----- So what about storing all stdout, stderr and pyin (basically all
> "terminal-output") at the kernel? And rather than pub/sub, use the existing
> req/rep to obtain this stuff. Maybe you can even pass new terminal-output
> along with other replies. The client should indicate in the request a
> sequence number to indicate to the kernel what messages were already
> received. This does mean, however, that the client would have to
> periodically query the kernel. But maybe this can also be done automatically
> by writing a thin layer on top of the zmq interface. Oh, and you'd need to
> encapsulate multiple terminal-messages in a single reply.
> Just my two cents,
> On 31 August 2010 07:28, Fernando Perez <fperez.net at gmail.com> wrote:
>> On Mon, Aug 30, 2010 at 1:51 AM, Almar Klein <almar.klein at gmail.com>
>> > Ah right. Although I'm not sure how often one would use such this in
>> > practice, it's certainly a nice feature, and seems to open op a range of
>> > possibilities. I can imagine this requirement makes things considerably
>> > harder to implement, but since you're designing a whole new protocol
>> > from
>> > scratch, it's probably a good choice to include it now.
>> And the whole thing fits naturally in our design for tools that enable
>> both interactive/collaborative computing and distributed/parallel work
>> within one single framework. After all, it's just manipulating
>> namespaces :)
>> >> In our case obviously the kernel itself remains unresponsive, but the
>> >> important part is that the networking doesn't suffer. So we have
>> >> enough information to take action even in the face of an unresponsive
>> >> kernel.
>> > I'm quite a new to networking, so sorry for if this sounds stupid: Other
>> > than the heartbeat stuff not working, would it also have other effects?
>> > I
>> > mean, data can not be send or received, so would maybe network buffers
>> > overflow or anything?
>> Depending on how you implemented your networking layer, you're likely
>> to lose data. And you'll need to ensure that your api recovers
>> gracefully from half-sent messages, unreplied messages, etc.
>> Getting a robust and efficient message transport layer written is not
>> easy work. It takes expertise and detailed knowledge, coupled with
>> extensive real-world experience, to do it right. We simply decided to
>> piggy back on some of the best that was out there, rather than trying
>> to rewrite our own. The features we gain from zmq (it's not just the
>> low-level performance, it's also the simple but powerful semantics of
>> their various socket types, which we've baked into the very core of
>> our design) are well worth the price of a C dependency in this case.
>> > Further, am I right that the heartbeat is not necessary when
>> > communicating
>> > between processes on the same box using 'localhost' (since some network
>> > layers are bypassed)? That would give a short term solution for IEP.
>> Yes, on local host you can detect the process via other mechanisms.
>> The question is whether the system recovers gracefully from dropped
>> messages or incomplete connections. You do need to engineer that into
>> the code itself, so that you don't lock up your client when the kernel
>> becomes unresponsive, for example.
>> I'm sure we still have corner cases in our code where we can lock up,
>> it's not easy to prevent all such occurrences.
>> > No, that's the great thing! All channels are multiplexed over the same
>> > socket pair. When writing a message to a channel, it is put in a queue,
>> > adding a small header to indicate the channel id. There is a single
>> > thread
>> > that sends and receives messages over the socket. It just pops the
>> > messages
>> > from the queue and sends them to the other side. At the receiver side,
>> > the
>> > messages are distributed to the queue corresponding to the right
>> > channel. So
>> > there's one 'global' queue on the sending side and one queue per channel
>> > on
>> > the receiver side.
>> Ah, excellent! It seems your channels are similar to our message
>> types, we simply dispatch on the message type (a string) with the
>> appropriate handler. The twist in ipython is that we have used as an
>> integral part of the design the various types of zmq sockets: req/rep
>> for stdin control, xrep/xreq for execution requests multiplexed across
>> clients, and pub/sub for side effects (things that don't fit in a
>> functional paradigm).
>> We thus have a very strong marriage between the abstractions that zmq
>> exposes and our design. Honestly, I sometimes feel as if zmq had been
>> designed for us, because it makes certain things we'd wanted for a
>> very long time almost embarrassingly easy.
>> Thanks a lot for sharing your ideas, it's always super useful to look
>> at these questions from multiple perspectives.
> IPython-dev mailing list
> IPython-dev at scipy.org
Brian E. Granger, Ph.D.
Assistant Professor of Physics
Cal Poly State University, San Luis Obispo
bgranger at calpoly.edu
ellisonbg at gmail.com
More information about the IPython-dev