4 ms·
This is just doubling down on the wrong approach. The right approach is to avoid simple syscalls like sleep() or recv(), and instead call use multiplexing call
by quietbritishjim 1y ago
This is just doubling down on the wrong approach.
The right approach is to avoid simple syscalls like sleep() or recv(), and instead call use multiplexing calls like epoll() or io_uring(). These natively support being interrupted by some other thread because you can pass, at minimum, two things for them to wait for: the thing you're actually interested in, and some token that can be signalled from another thread. For example, you could start a unix socket pair which you do a read wait on alongside the real work, then write to it from another thread to signal cancellation. Of course, by the time you're doing that you really could multiplex useful IO too.
You also need to manually check this mechanism from time to time even if you're doing CPU bound work.
If you're using an async framework like asyncio/Trio in Python or ASIO in C++, you can request a callback to be run from another other thread (this is the real foothold because it's effectively interrupting a long sleep/recv/whatever to do other work in the thread) at which point you can call cancellation on whatever IO is still outstanding (e.g. call task.cancel() in asyncio). Then you're effectively allowing this cancellation to happen at every await point.
(In C# you can pass around a CancellationToken, which you can cancel directly from another thread to save that extra bit of indirection.)
- rostayob 1y agoThis is noted in the blog post, but the problem is that sometimes you don't have the freedom to do so. See this sidenote and the section next to it: https://mazzo.li/posts/stopping-linux-threads.html#fn3 https://mazzo.li/posts/stopping-linux-threads.html#fn3 .
- quietbritishjim 1y agoI'll admit: I didn't see that. But I also disagree with it. Yes, the logical conclusion of starting down that path is that you end up with full on use of coroutines and some IO framework (though I don't see the problem with that). But a simple wrapper for individual calls that is recv+cancel rather than just recv etc is better than any solution mentioned in the blog post. The fact is, if you want to wait for more than one thing at once at the syscall level (in this case, IO + inter thread cancellation), then the way to do that is to use select or poll or something else actually designed for that.
- cyberax 1y agoI had this problem, and I solved it by farming the known-blocking syscalls to a separate thread pool. Then the calling thread can just abandon the wait. To make it a bit better, you can also use bounded timeouts (~1-2 seconds) with retries for some calls like recvfrom() via SO_TIMEOUT so that the termination time becomes bounded. This is probably the cleanest solution that is portable.
- naasking 1y agoThen those thread pool threads have to be careful not to take locks and other scarce resources that aren't cleaned up then, no?
- cyberax 1y agoSure. But if you only use it to run blocking requests, it's probably going to be pretty trivial. It won't be super-high performance because of all the cache bounces, but if you're not using something like epoll, then you probably are not really focused on performance anyway.