boost::throw_exception potential changes
Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.) This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.) This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see. It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably. The normal call now can be almost the same as the naked throw, https://godbolt.org/z/WssWzfrfW (47 lines.) And if BOOST_THROW_EXCEPTION is used, which still adds the source location (file/line/function) to the exception, the result is 97 lines. (https://godbolt.org/z/nETnqrsx8) However, there is a downside. Due to the way std::exception_ptr works under GCC/Clang (and all compilers using the same ABI), the exception is never copied, not when it's captured with std::current_exception (and consequently boost::current_exception), nor when it's rethrown with boost/std::rethrow_exception. This causes problems if an exception is captured, rethrown, caught and modified, then rethrown again using the same exception_ptr. The second rethrow throws the modified exception. This problem can be seen in https://godbolt.org/z/K9eh71ovP. So... what do we do? Should I go ahead with these changes to throw_exception (having a macro to revert to the old behavior, of course)? Or is that too much of a break? We can of course offer the same functionality as part of Boost.Exception, like boost::exception::raise, or similar, but would that be enough to offset the inconvenience?
On Mon, Jan 31, 2022 at 8:48 PM Peter Dimov via Boost
So... what do we do? Should I go ahead with these changes to throw_exception (having a macro to revert to the old behavior, of course)? Or is that too much of a break?
What happens if different libraries are compiled with different settings? Thanks
Vinnie Falco wrote:
On Mon, Jan 31, 2022 at 8:48 PM Peter Dimov via Boost
wrote: So... what do we do? Should I go ahead with these changes to throw_exception (having a macro to revert to the old behavior, of course)? Or is that too much of a break?
What happens if different libraries are compiled with different settings?
I think I can make it work.
On Mon, Jan 31, 2022 at 8:48 PM Peter Dimov via Boost
This causes problems if an exception is captured, rethrown, caught and modified, then rethrown again using the same exception_ptr. The second rethrow throws the modified exception.
There is an additional problem in that things go kaboom if the error info map ends up being accessed concurrently. The way this situation is avoided by boost::current_exception (which predates std::current_exception) is a bit tricky: - The error info map is allocated separately from the exception object and held in a custom (non-thread-safe) reference counting smart pointer stored in the boost::exception base, so copies of the exception object created by the copy constructor share the same map. This is done for efficiency (remember, the library is very old), and because it does not interfere with the normal use of boost::exception within a single thread. - When we make a boost::exception_ptr (via boost::current_exception), the boost::exception base is recognized automatically, and the error info map is copied. Thus, the new exception object held by boost::exception_ptr has its own separate error info map and it is safe to rethrow it in another thread (the standard specifies that current_exception is allowed to make a copy or not).
Opinions please?
-----Original Message----- From: Peter Dimov Sent: Tuesday, 1 February 2022 06:48 To: 'boost'
Subject: boost::throw_exception potential changes Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.)
This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.)
This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see.
It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably.
The normal call now can be almost the same as the naked throw, https://godbolt.org/z/WssWzfrfW (47 lines.)
And if BOOST_THROW_EXCEPTION is used, which still adds the source location (file/line/function) to the exception, the result is 97 lines. (https://godbolt.org/z/nETnqrsx8)
However, there is a downside. Due to the way std::exception_ptr works under GCC/Clang (and all compilers using the same ABI), the exception is never copied, not when it's captured with std::current_exception (and consequently boost::current_exception), nor when it's rethrown with boost/std::rethrow_exception.
This causes problems if an exception is captured, rethrown, caught and modified, then rethrown again using the same exception_ptr. The second rethrow throws the modified exception.
This problem can be seen in https://godbolt.org/z/K9eh71ovP.
So... what do we do? Should I go ahead with these changes to throw_exception (having a macro to revert to the old behavior, of course)? Or is that too much of a break?
We can of course offer the same functionality as part of Boost.Exception, like boost::exception::raise, or similar, but would that be enough to offset the inconvenience?
On Wed, Feb 2, 2022 at 10:52 AM Peter Dimov via Boost
Opinions please?
I would try to evaluate the impact of this overhead relative to the rest of the exception handling overhead added by modern implementations. I doubt this causes any problems in practice, I've used the library on platforms that are somewhat limited in resources, and this overhead never showed on my radar (I agree that it is unpleasant to see).
On 2/1/22 07:48, Peter Dimov via Boost wrote:
Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.)
This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.)
This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see.
It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably.
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing. I also think one could use dynamic_cast to boost::exception to the same effect, and that would also break after such a change. If some lightweight throwing mechanism is needed (of which I'm not convinced), I would prefer that to be a new API.
On 2/2/22 22:34, Andrey Semashev wrote:
On 2/1/22 07:48, Peter Dimov via Boost wrote:
Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.)
This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.)
This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see.
It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably.
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing. I also think one could use dynamic_cast to boost::exception to the same effect, and that would also break after such a change.
If some lightweight throwing mechanism is needed (of which I'm not convinced), I would prefer that to be a new API.
If we want to optimize code size, I would rather preferred if there was an API that allows to move exception construction into a function. That would save quite some code duplication, especially if there are many instances of throwing the same exception type. Here is a toy example: https://godbolt.org/z/hbv56E5z6 Wrapping this into a macro, I think, would require to pass the exception type as a separate argument, and requires support for variadic macros. E.g.: BOOST_THROW(std::runtime_error, "bad things happen"); Note that this would retain compatibility with boost::exception.
On Wed, Feb 2, 2022 at 1:49 PM Andrey Semashev via Boost < boost@lists.boost.org> wrote:
On 2/2/22 22:34, Andrey Semashev wrote:
On 2/1/22 07:48, Peter Dimov via Boost wrote:
Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.)
This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.)
This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see.
It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably.
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing. I also think one could use dynamic_cast to boost::exception to the same effect, and that would also break after such a change.
If some lightweight throwing mechanism is needed (of which I'm not convinced), I would prefer that to be a new API.
If we want to optimize code size, I would rather preferred if there was an API that allows to move exception construction into a function.
You could move the throw statement itself into a function, so instead of a throw statement the compiler would generate a call to a function that throws. But I remain unconvinced that the overhead matters.
On 2/3/22 00:58, Emil Dotchevski via Boost wrote:
On Wed, Feb 2, 2022 at 1:49 PM Andrey Semashev via Boost < boost@lists.boost.org> wrote:
On 2/2/22 22:34, Andrey Semashev wrote:
On 2/1/22 07:48, Peter Dimov via Boost wrote:
Currently, `boost::throw_exception(x)` automatically throws an exception derived from `x` that (1) injects a boost::exception base class if there's none, and (2) injects a base class that enables boost::exception_ptr support (boost::current_exception and boost::rethrow_exception.)
This is convenient, but the downside is the amount of generated code, e.g. https://godbolt.org/z/5T8T8GEqP (422 lines) compared to https://godbolt.org/z/1zr1odf7n (36 lines.)
This is not the end of the world as this code is only generated once per exception type, not on every call to throw_exception, but it's still unpleasant to see.
It so happens that boost::exception_ptr has recently acquired the ability to work under C++11 without the need for the supporting base class, by using the standard std::exception_ptr infrastructure. So if we also remove the automatic injection of boost::exception as a base class, and ask users to derive their exceptions from it if they desire having it as a base, it's possible to simplify boost::throw_exception considerably.
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing. I also think one could use dynamic_cast to boost::exception to the same effect, and that would also break after such a change.
If some lightweight throwing mechanism is needed (of which I'm not convinced), I would prefer that to be a new API.
If we want to optimize code size, I would rather preferred if there was an API that allows to move exception construction into a function.
You could move the throw statement itself into a function, so instead of a throw statement the compiler would generate a call to a function that throws. But I remain unconvinced that the overhead matters.
Sure, but that requires a lot of work, since every throw site needs to be modified in such way. Whereas BOOST_THROW could be nearly a drop-in replacement for BOOST_THROW_EXCEPTION or raw throw and still be more efficient than the former.
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing.
By the way you can use Boost LEAF for this, it's more convenient and more
efficient. Instead of:
typedef boost::error_info
On 2/7/22 10:57, Emil Dotchevski via Boost wrote:
I would like to ask to not do this, as this is a breaking change and I have written code (outside Boost) that relies on BOOST_THROW_EXCEPTION and boost::exception. That is, I expect that an exception thrown by BOOST_THROW_EXCEPTION triggers `catch (boost::exception& e)` handler, where I may augment it with additional info before rethrowing.
By the way you can use Boost LEAF for this, it's more convenient and more efficient. Instead of:
typedef boost::error_info
my_info; void f() { try { g(); } catch( boost::exception & e ) { e << my_info(42); throw; } }
You'd use:
struct my_info { int value };
void f() { auto load = on_error( my_info{42} ); g(); }
The above generally works with any exception, there is no need to derive from a special base type.
Thanks, but that introduces a new info passing mechanism (i.e. part of the data is passed with the exception and part is passed in Boost.LEAF). I'd rather keep everything contained in the exception. BTW, does Boost.LEAF work for passing error states across shared library boundaries?
participants (4)
-
Andrey Semashev
-
Emil Dotchevski
-
Peter Dimov
-
Vinnie Falco