3 ms·
I've used something similar to CPS in async I/O programs, where at each point that a function wants more I/O it queues up a continuation for the event and then
by cryptonector 2y ago
I've used something similar to CPS in async I/O programs, where at each point that a function wants more I/O it queues up a continuation for the event and then returns to the event loop. This forces one to represent state in a compact way and to use very little stack space for state representation, all of which reduces per-client memory footprint a great deal.
- upghost 2y agohey that's really clever. was this for open source or commercial? was it received well? what language? Very interesting technique
- cryptonector 2y ago> was this for open source or commercial? Proprietary, though I have permission to open source it (but I need to make sure that $WORK picks a license for it). This was specifically for a poor man's Kafka as an HTTP server that implements "tail -f" semantics for GET when using `Range:` with the end offset left unspecified, and with `{URI, weak-ETag, offset}` resembling the Kafka `{topic, partition, offset}`. Non-range requests end when EOF is reached in the file being fetched. Range requests with the end offset specified end as soon as the requested range has been sent back. Range requests with the end offset left unspecified do not end until the file is unlinked or renamed away, using inotify to detect those cases, and every time data is written to the file that data is immediately sent to the clients that are waiting "at the tail". The connection acceptor in the event loop creates a client record and queues reads to call the reader continuation which is just `{client, reader}`, then when a request is fully read the reader continuation will queue up a writer continuation to write the response, and so on. The fact that the underlying abstraction is "just a file" makes this very easy to use because `write(2)` is how you publish data and `rename(2)` and `unlink(2)` is how you "commit transactions" / roll logs in your application, and clients immediately find out. All that completely asynchronously with how the data gets served. You can even script an application, like using bash, since `echo foo >> some_file` is how you publish data. It's extremely useful. > what language? C, specifically for Linux, using epoll and inotify. The server is multi-processed, with each process single-threaded, and it spawns NPROC workers (or however many you specify). But naturally this would work very well in Rust, C++, etc., and it could be multi-threaded instead of multi-processed. > Very interesting technique It's just C10K with a dash of CPS :) In this particular program the sequence of events is pretty linear. Since the progression is fairly linear all the continuation functions are one next to the other and one can just read them linearly. This makes the code quite readable.
- upghost 2y agoThat's cool. So I suppose you are using function pointers for the continuations? That's pretty wild, I hadn't even considered that!
- fweimer 2y agoWhat's the alternative? Update the state object associated with the I/O event in place (and unregister/re-register it with the event loop as needed)?
- cryptonector 2y ago> What's the alternative? Alternatives include: - thread per-client - thread per-client (but green threads) - co-routines Co-routines are very similar to what I'm already doing, really. For fairly linear code one can easily end up with co-routine functions that are multi-thousand lines of code long -- see PuTTY for example. > Update the state object associated with the I/O event in place (and unregister/re-register it with the event loop as needed)? I have a simple data structure to represent the continuation, which is really more like a closure, consisting of `{fd, data, handler_function}` (this being C). Events are typically one-shots. When queuing I/O I just update the handler_function of that struct and then register the one-shot event with epoll, re-using this "closure" object. When it comes time to close a connection and we're done handling events we free (or, rather, put on a free-list) the closure, and destruct the client data structure.
- upghost 2y agoTo be fair, continuations are also the classic way to implement coroutines in languages that don't have them. Probably should've mentioned that in my original comment...
- cryptonector 2y agoFor sure.