3 ms·
If anyone is interested in a real-world but still meant for educational purposes example (a la minix), I wrote a generic tcp proxy in rust/tokio some years ago,
by ComputerGuru 3y ago
If anyone is interested in a real-world but still meant for educational purposes example (a la minix), I wrote a generic tcp proxy in rust/tokio some years ago, specifically for use as a teaching example to demonstrate how to correctly handle what would, at first blush, seem to be an incredibly straightforward task: https://github.com/mqudsi/tcpproxy https://github.com/mqudsi/tcpproxy
I've updated it over the years (moving from chaining futures to using async, using the latest tokio, etc) to keep it relevant.
In particular, correctly splitting a duplex connection into send/receive streams and then aborting one side of the connection when the other closes without missing events is trickier than it should be.
- Groxx 3y agoI am relieved that the source is just shy of 200 lines, because my initial thought was "there's some bookkeeping, sure, but that doesn't sound too bad". (I'm actually surprised it's that small, and it's clearly written for clarity not size. nicely done!) For anyone not used to more than simple concurrency stuff though, yeah, loads of subtle footguns. And they're often the sort of thing you might not notice in normal manual tests. Thanks for the link and maintaining it!
- ComputerGuru 3y agoThanks. Perhaps I did go overboard with that disclaimer.. probably because I myself made the mistake of initially using [0] the oh-so-convenient tokio::io::copy() instead of writing my own copy method that would drop the other half of the connection when one side was closed. The copy_with_abort() routine is still taking the easy way out in this not-optimized-for-heavy-production-use sample because it uses a broadcast channel per connection to reactively signal that the other half of the connection should be closed (rather than timing out every x ms to see if an abort flag has been set). In the real world, I'd probably replace the join! macro with a manual event loop to be able to do the same but without creating a broadcast channel per-connection. (I maintain an extremely lightweight "awaitable bools" library for rust [1] that is perfect for this kind of thing (roughly equivalent to a "bounded broadcast_channel<()> of queue length 1, but each "channel" is only a single (optionally stack-allocated) byte) — but it's for event loops in synchronous code and not async executor compatible.) [0]: https://github.com/mqudsi/tcpproxy/commit/0164ef836a49f2f73879eacbd22a9490f6639877#diff-42cb6807ad74b3e201c5a7ca98b911c5fa08380e942be6e4ac5807f8377f87fcL97-L100 https://github.com/mqudsi/tcpproxy/commit/0164ef836a49f2f738... [1]: https://github.com/neosmart/rsevents https://github.com/neosmart/rsevents
- Groxx 3y agoNah, I think it's a fair assessment - I do a fair bit of concurrency work, and do a bunch of education stuff at work. I've built and helped fix multiple things close to or (regrettably) more complicated. It's not surprising that I guessed the general shape correctly. But there are quite a lot of people stuck somewhere between "can reliably use a single blocking queue correctly / coarsely protect things with one mutex" and "can handle multiple competing interactions with error handling". More material to help cross that threshold is definitely a good thing.