Very wild bug. I feel like this is some kind of a worst-case "exceptions bad" lesson, but I've only been doing systems level programming for a couple of years so I'm probably talking out my ass.
This experienced systems programmer agrees with you 100%. This is an exceptionally bad case, but even in more normal circumstances, C++ exceptions are outrageously dangerous. Correct behavior requires the cooperation of not just the thrower and catcher, but everything in between. And there are basically no guardrails to enforce that. If you throw through C++ code that hasn’t been made exception safe, it just goes. You can throw through code written in plain C, which doesn’t even have exceptions.
It’s probably feasible to use them if you draw a tight boundary around the exception-using code, use RAII without fail inside that boundary to ensure everything cleans up properly, and make sure all code that might be called from the outside has try/catch blocks. (And, obviously, don’t trigger async calls to throw from the middle of someone else’s function!).
I find it a lot easier to avoid them entirely. Error handling is a little more annoying, but it’s worth it.
To this day, I still do not understand why the C++ language designers are infaturated with exceptions. They are more trouble than they are worth. It is as if they looked at people doing horrific things to implement exception handling with C's setjmp/longjmp and said "Lets make this a language feature with more structure".
I'm not so sure. The bug was that when an exception occurred while select was blocked then select did not properly clean up after itself. But no code in select actually dealt with exceptions at all, so handling it doesn't really make sense.
Without exceptions the possible control flow is entirely explicit. It would have at least been obvious that cleanup wasn't properly handled in the select function for all cases.
An exception was effectively injected from outside of the code via low level shenanigans. That's not "exceptions bad" that's "low level monkeying with program control flow can blow up in your face".
Another thing to note is that exactly the same bug can be made in Rust or go, both of which officially don't have exceptions. They both, of course, do have exceptions and just call them a different name.
As it happens, Rust 1.81 recently added a feature where it aborts by default when you attempt to unwind from a function with the "C" ABI [0], which mitigates this issue for most FFI use cases.
Of course, it's not uncommon to find unsafe Rust code that misbehaves badly when something panics, which is yet another of its hazards that I wish were better documented.
In this case, I'd put down the lesson as "exceptions in C++ are very dangerous if you're coming out of C code", since they can cause undocumented and unexpected behavior that ultimately led to the use-after-return issue here.
Exceptions aren't bad because of the name. Exceptions are bad because lower stack frames unwind higher ones, without giving those frames any opportunity to do something sane with any captured resources. Calling it panic/recover doesn't help anything.