(providing my own answer... I'll wait for few days and if there are no problems with it -- I'll mark it as accepted)
I spent some time investigating this and here is what I unearthed:
- C++ standard does not specify what is going to happen in this case
- Clang and GCC seem to use C++ Itanium ABI
Itanimum ABI suggests to use heap for exceptions:
Storage is needed for exceptions being thrown. This storage must
persist while stack is being unwound, since it will be used by the
handler, and must be thread-safe. Exception object storage will
therefore normally be allocated in the heap
...
Memory will be allocated by the __cxa_allocate_exception
runtime library routine.
So, yeah... throwing an exception will likely involve locking mutexes and searching for a free memory block. :-(
It also mentions this:
If __cxa_allocate_exception cannot allocate an exception object under these constraints, it calls terminate()
Yep... in GCC and Clang "throw myX();" can kill your app and you can't do a thing about it (maybe writing your own __cxa_allocate_exception
can help -- but it certainly won't be portable)
It gets even better:
3.4.1 Allocating the Exception Object
Memory for an exception object will be allocated by the
__cxa_allocate_exception runtime library routine, with general requirements as described in Section 2.4.2. If normal allocation
fails, then it will attempt to allocate one of the emergency buffers,
described in Section 3.3.1, under the following constraints:
- The exception object size, including headers, is under 1KB.
- The current thread does not already hold four buffers.
- There are fewer than 16 other threads holding buffers, or this thread
will wait until one of the others releases its buffers before acquiring one.
Yep, your program can simply hang! Granted chance of this are small -- you'd need to exhaust memory and your threads need to use up all 16 emergency buffers and enter wait for another thread that should generate an exception. But if you do things with std::current_exception
(like chaining exception and passing them between threads) -- it is not so improbable.
Conclusion:
This is a deficiency in C++ standard -- you can't write 100% reliable programs (that use exceptions). Text book example is a server that accepts connections from clients and executes submitted tasks. Obvious approach to handle problems would be to throw an exception, which will unwind everything and close connection -- all other clients won't be affected and server will continue to operate (even under low memory conditions). Alas, such server is impossible to write in C++.
You can claim that modern systems (i.e. Linux) will kill such server before we reach this situation anyway. But (1) it is not an argument; (2) memory manager can be set to overcommit; (3) OOM killer won't be triggered for 32-bit app running on 64bit hardware with enough memory (or if app artificially limited memory allocation).
On personal note I am quite pissed about this discovery -- for many years I claimed that my code handles out-of-memory gracefully. Turns out I lied to my clients. :-( Might as well start intercepting memory allocation, call std::terminate
and treat all related functions as noexcept
-- this will certainly make my life easier (coding-wise). No wonder they still use Ada to programs rockets.