But the code that was supposed to wait until completion was interrupted by an exception, which unrolled the stack beyond the function owning the relevant memory. So the completing async operation writes into memory owned by somebody else now.
This is a general complication with completion based async IO. You could get the same problems without any interrupts, if you just don't wait for async IO to complete in some circumstances.
I know this is not about Rust. Forgive me that I mentally connect this story with async Rust.
* Readiness based: You use `select` (or preferably its successors) to figure out which file descriptor has data available. Then you synchronously read the data, which completes instantly because there is data available. This is the traditional way on Linux, and also the way Rust's async works. This approach doesn't run into any lifetime issues, because you only need to provide the memory (`&mut [u8]`) to a sync call once data is available. This approach works well from sequential streams, like sockets or pipes, but isn't a natural match for random access file IO.
* Completion based: You trigger an IO operation specifying the target memory. The system notifies you when it completes. This approach needs you to keep the memory available until the operation finishes. This is the traditional way on Windows (IO completion ports) and also used by the more recent io_uring on Linux. This one runs into the lifetime issues that caused the bug in the linked article. Thus in Rust it can't safely be used with a `&mut [u8]`, you have to transfer ownership of the buffer to the IO library, so it can keep it alive until the operation completes. The ringbahn crate is an example of this approach in Rust.
I find this article helpful in clearing up confusion of asynchronous vs non-blocking I/O: http://blog.omega-prime.co.uk/?p=155
Anything that obscures the flow of control is a violation of literate programming principles, and exceptions are pretty high on that particular shit list.
Yep, it's called Application Verifier and it will check all kinds of system calls and make the memory allocator incredibly aggro towards checking validity
> The other thing is, how can a system call block a thread but somehow that same thread runs other stuff in between that blocking call?
When you call select(), your thread is in a special state called Alertable Wait; one way to get out of it is to have an APC queued to your thread, which has a similar vibe to a UNIX signal (with all of the similar caveats about how easy it is to fuck things up in it). Basically, OP's scenario is similar to corruption caused by a badly written signal handler
I mean, signal handlers exist on Linux, too ...
> when you have a blocking syscall interrupted you will get a sigabort on linux
Doesn't the syscall just return EINTR?
https://docs.microsoft.com/en-us/windows/win32/api/processth...
Imagine how much he could have achieved in those five days if he had just unfucked obvious bug magnets proactively without first painstakingly proving that they were really manifesting as bugs, and how.
The other thing is, how can a system call block a thread but somehow that same thread runs other stuff in between that blocking call? Even if that is possible that sounds like a dangerous game to play, and this class of bugs seems natural.
I can imagine the scenario where a thread is cancelled mid system call, but it will be the kernel that will cancel the thread and responsible to know that half way syscalls need to be cleaned up. This way the application does not get system call call leaks? Also if i remember one debugging session correctly, when you have a blocking syscall interrupted you will get a sigabort on linux.