Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Route boost::current_exception to std::current_exception when available? #10

Closed
pdimov opened this issue Oct 26, 2017 · 30 comments
Closed

Comments

@pdimov
Copy link
Member

pdimov commented Oct 26, 2017

From boostorg/system#18, suggested by @ned14:

Also, one might consider also rerouting boost::current_exception() to std::current_exception() . The latter is surely always going to be equal or better to the Boost implementation.

@zajo
Copy link
Collaborator

zajo commented Oct 26, 2017

It is unclear to me who the target audience for such rerouting would be.

If you use boost::throw_exception() to throw, then boost::current_exception() / boost::rethrow_exception() work as well as std::current_exception() / std::rethrow_exception(), but you must use the boost versions if you do need your code to work on old compilers; if you don't, you may (should?) use the std versions.

If you don't use boost::throw_exception() to throw, then you should always use the std::current_exception() / std::rethrow_exception(), since your code won't work correctly on old compilers anyway.

Secondly, there are other components in Boost, e.g. boost::function, which aren't "rerouted". If such rerouting is needed, it should probably be done uniformly for all such components, though I'm not sure if there are important subtle semantic differences (there probably are.)

@ned14
Copy link
Member

ned14 commented Oct 26, 2017

This rerouting would only apply on C++ 11 or newer, and presumably only on compilers without borked std::current_exception().

I get the argument "why bother rerouting?". But there is also the argument "why not bother rerouting?". Unlike boost::function, as far as I am aware, there is zero known behavioural differences between std::current_exception/rethrow_exception and boost::current_exception/rethrow_exception. Plus, rerouting these keeps symmetry with the rerouting of Boost.System to its std equivalents.

@zajo
Copy link
Collaborator

zajo commented Oct 26, 2017

My question wasn't so much "why", but who would benefit? I understand the motivation, and I'm not against it in principle, but I am worried about possibly breaking client code which currently works fine, and I think that it is important to understand who cares about this change.

The safe approach would be to provide a new configuration macro, e.g. BOOST_EXCEPTION_USE_STD_EXCEPTION_PTR (off by default), which would guarantee nothing will break. Would that work? This isn't just paranoia, recently I tried to remove the support for non-intrusive boost::exception_ptr, specific for MSVC, thinking that probably nobody uses it any more (since modern compilers have std::exception_ptr), but it turned out I was wrong, so that's back in.

(By the way, I am not aware of any differences between std::function and boost::function, when I changed the allocator support in boost::function to match that of boost::shared_ptr, I wrote a proposal which made it into the standard as well :). But possibly I'm missing something.)

@zajo
Copy link
Collaborator

zajo commented Oct 26, 2017

Actually I've realized that there is another problem: the custom error info container in boost::exception is held by a boost::shared_ptr, and all copies of a boost::exception object share that state. Only when making a boost::exception_ptr, the copied exception object doesn't share that state with the source, but that copy is not created using the copy constructor. A copy created by std::current_exception would share that state, potentially with explosive consequences if the exception is transported to another thread.

While this design is not ideal and can probably be safely changed so boost::exception objects never share state, this would not be a trivial change.

@jeking3
Copy link

jeking3 commented Nov 12, 2018

This discussion stalled, was there a conclusion (for example, since it was not trivial, will we do it?)

@zajo
Copy link
Collaborator

zajo commented Nov 14, 2018

I don't know if this can be fixed. I'll try to explain simpler:

A boost::exception object holds a map of data, through a custom intrusive refcounting smart pointer. Copying exception objects within a thread only changes the refcount.

But that map is not thread-safe, so when a boost::exception object is transported to another thread, we must create a copy of the map, which boost::current_exception knows and does, but std::current_exception would simply invoke the copy constructor, which won't copy the map.

Maybe it can be done with a move constructor, but this would require #ifdefing, since I don't think it's a good idea to break C++03 support.

I'll close this for now, if anyone has an idea for a workaround, feel free to reopen and continue the discussion.

@zajo zajo closed this as completed Nov 14, 2018
@dariomt
Copy link

dariomt commented Feb 22, 2019

I have a suggestion (that would solve my use case):

  1. first have boost::current_exception() do its current work, and recover the exception_ptr
  2. then, if this 'failed' (and we would get an exception_ptr pointing to an unknown_exception), reroute to std::current_exception() if supported

This would:
a) keep existing code using Boost.Exception facilities working correctly
b) make code not using Boost.Exception to throw, work correctly, as if std::current_exception was used

For reference, in my use case I need to

  • use boost::future (which uses boost::current_exception() to transport exceptions across threads)
  • but I cannot use Boost.Exception facilities to throw exceptions (third party code throwing)

Does this make sense?

@zajo
Copy link
Collaborator

zajo commented Mar 3, 2019

Reroute to std::current_exception, how? std::exception_ptr and boost::exception_ptr are different types.

@dariomt
Copy link

dariomt commented Mar 5, 2019

Hum... maybe wrap the std::exception_ptr in current_exception and unwrap it in rethrow_exception?

In boost::exception_detail::current_exception_impl() (around boost/exception/detail/exception_ptr.hpp:424 in boost 1.64)

catch(...)
{
   try {
        // wrap the std::exception_ptr in a clone-enabled Boost.Exception object
       return exception_ptr(shared_ptr<exception_detail::clone_base const>(
              boost::enable_current_exception(std::current_exception()).clone()));
    }
   catch(...) {
      return exception_detail::current_exception_unknown_exception();
   }
}

In boost::rethrow_exception():

   try {
        p.ptr_->rethrow();
    } catch (const std::exception_ptr& ep) {
            std::rethrow_exception(ep);
   }

This (pseudo)code is for exposition only. Also, for simplicity this code assumes std::current_exception is available. I could provide a PR if necessary, but I doubt I can get all the #ifdef-ing right!

PS: I took inspiration from https://stackoverflow.com/questions/22010388/converting-stdexception-ptr-to-boostexception-ptr/22881981

@dariomt
Copy link

dariomt commented Mar 5, 2019

I'm testing the above locally and it looks like it works. Two caveats, though:

  1. clone is private so we need a reference to the base class
exception_detail::clone_base const& base {
    boost::enable_current_exception(std::current_exception())};
return exception_ptr(shared_ptr<exception_detail::clone_base const>(base.clone()));
  1. the catch(std::exception&) (that redirects to current_exception_unknown_std_exception()) above the catch all clause, is intercepting my user defined exception type (which inherits from std::exception) so I had to remove it for this to work. Not sure if that breaks anything, as the std::current_exception should transport that case correctly.

What do you think?

@zajo
Copy link
Collaborator

zajo commented Mar 17, 2019

Oh I see, your idea is to use std::exception_ptr as a way to be able to clone any exception properly, but still returning a boost::exception_ptr. I had misunderstood, I thought you wanted to go the other way around, to be able to effectively make boost::exception_ptr be std::exception_ptr where available.

This is a great idea, thank you. Since you said you've tested, I'm assuming you can send a pull request?

@zajo zajo reopened this Mar 17, 2019
@dariomt
Copy link

dariomt commented Mar 19, 2019

I'm testing locally against Boost 1.64.0. I guess you'd like the PR against devel branch?

Also, do you know which Boost preprocessor macro can I use to detect support for std::exception_ptr?

@zajo
Copy link
Collaborator

zajo commented Mar 21, 2019

There is no config macro in Boost to detect std::exception_ptr support. Probably just check for C++11 support (e.g. #if __cplusplus > 199711L) and assume std::exception_ptr is available.

You can send a diff with Boost 1.64.0 if that's easier. I'll review and commit manually. Thank you!

@dariomt
Copy link

dariomt commented Mar 28, 2019

My main compiler is VS2015, so I'm afraid __cplusplus does not work for me: for some reason it's still defined to be 199711L.
I'm looking at Boost.Config and I can't find anything suitable.
Maybe I can just use BOOST_NO_CXX11_NOEXCEPT, and assume that if noexcept is supported, then std::current_exception is also supported?
Or maybe I should bring this up in the mailing list?

@zajo
Copy link
Collaborator

zajo commented Apr 9, 2019

It can't hurt to talk on the mailing list. But it may be safe to assume exception_ptr is supported if __cplusplus is greater than 199711L.

@dmenendez-gruposantander
Copy link
Contributor

I'm working to provide a PR over develop branch.

BTW, my boss gave me green light to do this task using company time, so I'll do this with my corporatey github account, just to clarify I'm not doing this in my own free time.

@dmenendez-gruposantander
Copy link
Contributor

About the docs, should I update the html files under doc/ directly?
I thought there would be some other source from which the html files are generated...

dmenendez-gruposantander added a commit to dmenendez-gruposantander/exception that referenced this issue May 28, 2019
In boost::current_exception(), after all supported types of exception have
been checked for, a boost::unknown_exception is stored in the
boost::exception_ptr as a last resort to signal that an exception was indeed
stored, but that the actual type and value of the exception was lost.

Now, in case C++11 std::current_exception() is supported, the
std::exception_ptr result of calling std::current_exception() is what
gets stored inside the boost::exception_ptr. Later, inside
boost::rethrow_exception, the std::exception_ptr is retrieved from
the boost::exception_ptr and whatever exception it stores is thrown via
std::rethrow_exception().

The main benefit is than now any exception thrown via plain 'throw'
can be trasnported via boost::exception_ptr. Before this change it was
required that the throw site either used boost::enable_current_exception()
or boost::throw_exception() or threw an exception type inheriting from
boost::exception, which was impossible for third party software
that does not use Boost.Exception.

The detection of std::current_exception() is currently done via config macro
BOOST_NO_CXX11_NOEXCEPT, assuming that if 'noexcept' is supported then
std::exception_ptr is also supported. A better solution would require a new
dedicated macro in Boost.Config.
dmenendez-gruposantander added a commit to dmenendez-gruposantander/config that referenced this issue Jun 4, 2019
Added defect macro BOOST_NO_CXX11_HDR_EXCEPTION

Should be #define'd when:
The standard library does not provide a C++11 compatible version of <exception>.

To solve Boost.Exception issue boostorg/exception#10 I need to detect
compiler support for C++11 compliant:
 - std::exception_ptr
 - std::current_exception()
 - std::rethrow_exception()
@dmenendez-gruposantander
Copy link
Contributor

The changes (code, tests and docs) are ready in my fork (https://github.com/dmenendez-gruposantander/exception).
However I have a dependency on Boost.Config PR boostorg/config#285 being merged to develop (for BOOST_NO_CXX11_HDR_EXCEPTION).
Which is stalled due to CI testing being borked right now :(
I'll create a PR as soon as #285 is merged to develop.

@zajo
Copy link
Collaborator

zajo commented Jul 9, 2019

Yes, let's wait until the config change gets merged, and then I'll look at the PR. Thank you!

jzmaddock added a commit to boostorg/config that referenced this issue Aug 23, 2019
@dmenendez-gruposantander
Copy link
Contributor

Now that the Boost.Config changes have been merged to develop (thanks @jzmaddock!) I've created PR #24 with my changes, so let the code review begin!

Unfortunately, some CI tests are unexpectedly failing, and I'm having a hard time deciphering the errors...
For some versions of gcc it seems confused about instantiating abstract class clone_base (which I'd say I'm not) and the clang tests I don't think are even related to my changes!

Any help with this is appreciated, as I am being optimistic and was hoping to get this merged in time for the 1.72 release.

@pdimov
Copy link
Member Author

pdimov commented Aug 28, 2019

The clang errors are because Travis switched to Xenial by default; dist: trusty needs to be added to .travis.yml as the old clang versions don't work on Xenial.

@dmenendez-gruposantander
Copy link
Contributor

Hum... This is not something I should fix in my PR, is it?

@dmenendez-gruposantander
Copy link
Contributor

Is there something I can do to help with this issue?
Not that I'm familiar with the CI setting, but if all it takes is "dist: trusty needs to be added to .travis.yml" then I could... create a PR for that?

@dmenendez-gruposantander
Copy link
Contributor

Hi, I'm sorry to be nagging about this, but I don't see how I can get this PR merged...
Is the CI config already fixed?
How do I "retry" the PR?
I'd really like to get this merged for 1.72, please.

dmenendez-gruposantander added a commit to dmenendez-gruposantander/exception that referenced this issue Oct 18, 2019
In boost::current_exception(), after all supported types of exception have
been checked for, a boost::unknown_exception is stored in the
boost::exception_ptr as a last resort to signal that an exception was indeed
stored, but that the actual type and value of the exception was lost.

Now, in case C++11 std::current_exception() is supported, the
std::exception_ptr result of calling std::current_exception() is what
gets stored inside the boost::exception_ptr. Later, inside
boost::rethrow_exception, the std::exception_ptr is retrieved from
the boost::exception_ptr and whatever exception it stores is thrown via
std::rethrow_exception().

The main benefit is than now any exception thrown via plain 'throw'
can be trasnported via boost::exception_ptr. Before this change it was
required that the throw site either used boost::enable_current_exception()
or boost::throw_exception() or threw an exception type inheriting from
boost::exception, which was impossible for third party software
that does not use Boost.Exception.

The detection of std::current_exception() is currently done via config macro
BOOST_NO_CXX11_NOEXCEPT, assuming that if 'noexcept' is supported then
std::exception_ptr is also supported. A better solution would require a new
dedicated macro in Boost.Config.
dmenendez-gruposantander added a commit to dmenendez-gruposantander/exception that referenced this issue Oct 18, 2019
@dmenendez-gruposantander
Copy link
Contributor

OK, I added dist: trusty in my PR and the clang problems are solved.

However I still have problem with old versions of gcc which get confused when I bind a exception_detail::clone_base const& to the temporary coming out of boost::enable_current_exception().
Somehow gcc thinks I want a copy!
Ideas?

I tried const auto& but the actual type returned from boost::enable_current_exception() has clone() as a private member function!

@dmenendez-gruposantander
Copy link
Contributor

At last! All tests are green!
Could you review the proposed changes for this PR please?

PS: the gcc problem was due to initializing the reference with braces instead of using =
go figure! slaps face

@zajo
Copy link
Collaborator

zajo commented Oct 25, 2019

Yes, I'll look at it. Thank you!

@dmenendez-gruposantander
Copy link
Contributor

Any chance to get this into the 1.73 release?
Is there anything else I can help with?

zajo pushed a commit that referenced this issue Jan 29, 2020
* Attempt to solve issue #10

In boost::current_exception(), after all supported types of exception have
been checked for, a boost::unknown_exception is stored in the
boost::exception_ptr as a last resort to signal that an exception was indeed
stored, but that the actual type and value of the exception was lost.

Now, in case C++11 std::current_exception() is supported, the
std::exception_ptr result of calling std::current_exception() is what
gets stored inside the boost::exception_ptr. Later, inside
boost::rethrow_exception, the std::exception_ptr is retrieved from
the boost::exception_ptr and whatever exception it stores is thrown via
std::rethrow_exception().

The main benefit is than now any exception thrown via plain 'throw'
can be trasnported via boost::exception_ptr. Before this change it was
required that the throw site either used boost::enable_current_exception()
or boost::throw_exception() or threw an exception type inheriting from
boost::exception, which was impossible for third party software
that does not use Boost.Exception.

The detection of std::current_exception() is currently done via config macro
BOOST_NO_CXX11_NOEXCEPT, assuming that if 'noexcept' is supported then
std::exception_ptr is also supported. A better solution would require a new
dedicated macro in Boost.Config.

* Detect support for std::current_exception() via config macro BOOST_NO_CXX11_HDR_EXCEPTION

The temporary solution via BOOST_NO_CXX11_NOEXCEPT was an ugly hack
that is no longer necessary now that Boost.Config has
BOOST_NO_CXX11_HDR_EXCEPTION (pending merge to develop).

* Attempt to solve issue #10

In boost::current_exception(), after all supported types of exception have
been checked for, a boost::unknown_exception is stored in the
boost::exception_ptr as a last resort to signal that an exception was indeed
stored, but that the actual type and value of the exception was lost.

Now, in case C++11 std::current_exception() is supported, the
std::exception_ptr result of calling std::current_exception() is what
gets stored inside the boost::exception_ptr. Later, inside
boost::rethrow_exception, the std::exception_ptr is retrieved from
the boost::exception_ptr and whatever exception it stores is thrown via
std::rethrow_exception().

The main benefit is than now any exception thrown via plain 'throw'
can be trasnported via boost::exception_ptr. Before this change it was
required that the throw site either used boost::enable_current_exception()
or boost::throw_exception() or threw an exception type inheriting from
boost::exception, which was impossible for third party software
that does not use Boost.Exception.

The detection of std::current_exception() is currently done via config macro
BOOST_NO_CXX11_NOEXCEPT, assuming that if 'noexcept' is supported then
std::exception_ptr is also supported. A better solution would require a new
dedicated macro in Boost.Config.

* Detect support for std::current_exception() via config macro BOOST_NO_CXX11_HDR_EXCEPTION

The temporary solution via BOOST_NO_CXX11_NOEXCEPT was an ugly hack
that is no longer necessary now that Boost.Config has
BOOST_NO_CXX11_HDR_EXCEPTION (pending merge to develop).

* Document detection of C++11 std::current_exception through BOOST_NO_CXX11_HDR_EXCEPTION

* Added dist: trusty for Travis config, as recommended by pdimov in issue #10

* Stupid gcc 4.7 and 4.8 are confused initializing a reference with braces, using equal sign hoping it works (tested similar code on godbolt).
@dmenendez-gruposantander
Copy link
Contributor

In my last (and first) contribution to Boost I was asked about Copyright.
I'm never sure about how to proceed with this, if it should be part of the PR or where it should appear.

This is it, in case it's necessary:
Copyright 2019 Dario Menendez, Banco Santander

@zajo
Copy link
Collaborator

zajo commented Feb 18, 2020

Done.

@zajo zajo closed this as completed Feb 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants