<html><head><meta http-equiv="Content-Type" content="text/html; charset=us-ascii"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><br class=""><div><br class=""><blockquote type="cite" class=""><div class="">On Jan 6, 2024, at 11:12 AM, Nicolas Martyanoff <<a href="mailto:nicolas@n16f.net" class="">nicolas@n16f.net</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div class="">Ron Garret <<a href="mailto:ron@flownet.com" class="">ron@flownet.com</a>> writes:<br class=""><br class=""><blockquote type="cite" class=""><blockquote type="cite" class="">On Jan 6, 2024, at 6:00 AM, Nicolas Martyanoff <<a href="mailto:nicolas@n16f.net" class="">nicolas@n16f.net</a>> wrote:<br class=""><br class="">In CL, this really does not map well to streams because streams are<br class="">fundamentally blocking (see for example the semantic of READ-SEQUENCE).<br class=""></blockquote><br class="">That's not true. CL:READ-SEQUENCE is blocking, but LISTEN works on<br class="">binary streams so implementing a non-blocking version is an elementary<br class="">exercise.<br class=""></blockquote><br class="">A function similar to LISTEN would not help in any way.<br class=""><br class="">OS primitives dealing with non-blocking IO are event based: you do not<br class="">ask if you can read (or write) a file descriptor, you register it<br class="">against a some kind of multiplexer (e.g. an epoll instance), then<br class="">another primitive lets you ask this multiplexer which events were<br class="">signaled for any registered file descriptor. You then dispatch these<br class="">events manually which usually mean running the appropriate callback.<br class=""><br class="">The approach is fundamentally top-down: you start from IO events, then<br class="">dispatch. But a read event only means you can perform one single<br class="">non-blocking read call. So you need to guarantee that any stream<br class="">function reading your file descriptor will perform one read and one read<br class="">only (if you do a second one, you'll potentially block with all the<br class="">infortunate consequences for your event loop). You cannot guarantee that<br class="">(heck you cannot even know if standard streams are buffered or not, and<br class="">don't get me started on external formats), and various stream operations<br class="">are fundamentally based on the idea of reading repeatedly<br class="">(READ-SEQUENCE, READ-LINE, etc.). So you are forced to bypass streams,<br class="">and use your FFI to call read() on your own and implement buffering<br class="">yourself. Similar issue when writing.<br class=""><br class="">I work around this problem by implementing my own TCP streams using the<br class="">(non-standard) Gray stream API. In non-blocking mode, I signal a<br class="">condition indicating that I could not complete the operation because<br class="">doing so would require additional calls to read(). This way the IO mux<br class="">knows which events must be watched for. But then we're back writing all<br class="">parsers as operating on IO buffers instead of just reading streams, and<br class="">you still end up juggling with multiple callbacks for processing which<br class="">results in much more complex code.<br class=""><br class="">These problems do not exist in Go or in Erlang: you can use a<br class="">stream-like system and just read the file descriptor. The runtime<br class="">handles IO in a non-blocking way and assigns another green thread for<br class="">execution until the read call can continue; when it happens, it schedule<br class="">the initial green thread for execution, and the read call returns. You<br class="">simply cannot emulate that in CL unless your implementation provides the<br class="">correct runtime support.<br class=""></div></div></blockquote><div><br class=""></div><div>I think I need to clarify something. Recall the statement I was responding to:</div><div><br class=""></div><div><blockquote type="cite" class="">In CL, this really does not map well to streams <b class="">because streams are fundamentally blocking</b> (see for example the semantic of READ-SEQUENCE). [Emphasis added.]<br class=""></blockquote><br class=""></div><div>where "this" is something along the lines of: writing performant I/O-bound code that has to handle a lot of asynchronous events, the sort of thing Go and Erlang handle well.</div><div><br class=""></div><div>The thing I was taking issue with was not that "does not map well to streams" but rather the "because streams are fundamentally blocking." It's not the claim I'm disputing, it's the explanation. (Well, I actually dispute the claim too, but that's a different discussion. But I don't dispute that Go and Erlang are better at "this" than (current implementations of) CL, so this is probably a moot point.)</div><div><br class=""></div><div>The reason "this doesn't map well onto streams" is not because "streams are fundamentally blocking" in the sense that read-sequence is blocking. It is possible to write a non-blocking version of read-sequence in standard CL. It won't be particularly efficient, but you can do it, so there is nothing "fundamental" about the fact that read-sequence-no-hang is not part of standard Common Lisp.</div><div><br class=""></div><div>The reason that CL is not as well suited as Go and Erlang to writing performant I/O-bound code that has to handle a lot of asynchronous events is because Go and Erlang were specifically designed to handle that problem and CL wasn't. CL was designed long before large-scale web servers were a thing. The fact that you can use the language to do this kind of work *at all* is remarkable, a testament to the foresight of its designers. (Can you imagine writing a web server in Fortran or Cobol?) Go and Erlang were designed from the ground up to address this specific problem, and so *of course* they are going to be better at it than any language that was not specifically designed for it. But the fact that Go and Erlang are better at it has nothing to do with CL streams being "fundamentally blocking".</div><div><br class=""></div><div>If you want Go/Erlang-style I/O in CL you can have it. The solutions to the problem implemented in Go and Erlang can be implemented in CL. This obviously requires language extensions because the standard base language doesn't even have threads/processes at all, but open-source implementations of CL are almost infinitely malleable. Any feature that you find in any other language can be integrated into CL with varying degrees of effort. The biggest challenge is that AFAIK no modern CL implementation uses green threads, so you'd need to implement those, and that's non-trivial at least if efficiency is a concern. But it's not impossible. Most CL implementations used green threads back in the days before pre-emptive multitaksing became widely available, so this is a well-worn path. It's just a question of how much work you're willing to do (or how big a check you're willing to write).</div><div><br class=""></div><div>rg</div><div><br class=""></div></div></body></html>