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

Get exception ptr CPO #249

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

Garcia6l20
Copy link

Create new get_exception_ptr CPO allowing users to control how any kind of error type will be internally converted to an std::exception_ptr.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 4, 2021
@Garcia6l20 Garcia6l20 force-pushed the get_exception_ptr branch 2 times, most recently from 2beb643 to 859347c Compare April 4, 2021 08:26
  - add get_exception_ptr_test
  - add set_error(exception_ptr_convertible) overload in _awaitable_base
examples/linux/io_epoll_test.cpp Outdated Show resolved Hide resolved
examples/linux/io_epoll_test.cpp Outdated Show resolved Hide resolved
examples/linux/io_uring_test.cpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/await_transform.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
@Garcia6l20 Garcia6l20 force-pushed the get_exception_ptr branch 3 times, most recently from 1a7c082 to 32ba5da Compare April 8, 2021 13:24
  - std::exception_ptr forwarding
  - std::exception -> std::exception_ptr conversion
  - tag_invoke(get_exception_ptr, ...) customization points
 - make any_sender_of use get_exception_ptr CPO
 - make async_scope use get_exception_ptr CPO
@Garcia6l20 Garcia6l20 requested a review from ericniebler April 8, 2021 17:43
CMakeLists.txt Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_stop_token.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/get_exception_ptr.hpp Outdated Show resolved Hide resolved
examples/linux/file_coroutines_test.cpp Outdated Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Show resolved Hide resolved
include/unifex/await_transform.hpp Outdated Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/any_sender_of.hpp Outdated Show resolved Hide resolved
include/unifex/any_sender_of.hpp Outdated Show resolved Hide resolved
examples/linux/file_coroutines_test.cpp Outdated Show resolved Hide resolved
 - set_error now uses as_exception_ptr to transform Errors to exception_ptr
 - rollback:
   - any_sender_of.hpp
   - async_scope.hpp
   - await_transform.hpp
 - cleanup tag_invocable/nothrow_tag_invocable concepts
 - file_coroutine_test does not used __FILE__ macro
 - sync_wait now requires only one set_error(exception_ptr) method
include/unifex/as_exception_ptr.hpp Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Outdated Show resolved Hide resolved
tag_invoke(tag_t<as_exception_ptr>, std::error_code&& error) noexcept {
return make_exception_ptr(
std::system_error{std::forward<std::error_code>(error)});
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if this were an overload in _fn rather than a tag_invoke overload at namespace scope.

include/unifex/as_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/as_exception_ptr.hpp Outdated Show resolved Hide resolved
include/unifex/receiver_concepts.hpp Outdated Show resolved Hide resolved
include/unifex/receiver_concepts.hpp Outdated Show resolved Hide resolved
(requires (!tag_invocable<_set_error_fn, Receiver, Error>) &&
tag_invocable<decltype(as_exception_ptr), Error>)
auto operator()(Receiver&& r, Error&& error) const noexcept
-> _result_t<Receiver, std::exception_ptr> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to update the _result_t alias to account for this overload. Look how it is done elsewhere with if constexpr in other customization points.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a _select function (reproduced from connect CPO), I hope it is fine.

Comment on lines 130 to 131
(requires (!tag_invocable<_set_error_fn, Receiver, Error>) &&
tag_invocable<decltype(as_exception_ptr), Error>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way you have constrained this requirement means that it will always convert the error to an exception_ptr if it can and the set_error() method can be invoked with an exception_ptr.

I think that what we want instead is to try to invoke set_error() with the provided type and only if there is no tag_invoke() or member function set_error() overload that accepts that type then, if the error type can be converted to exception_ptr and you can invoke set_error (either by tag_invoke or member function) with an exception_ptr then do that as a fallback.

So the order of resolution would be:

  • tag_invoke w/ original error type
  • set_error member fn w/ original error type
  • tag_invoke w/ exception_ptr AND error type is convertible to exception_ptr
  • set_error member fn w/ exception_ptr AND error type is convertible to exception_ptr

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I missed it.
I added a test case to validate this.

 - add as_exception_ptr.set_error test case
Copy link
Contributor

@lewissbaker lewissbaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for taking so long to get back to this.
A couple of additional comments.

If you don't have bandwidth for this just let me know and I'll fix it up.

}

// default std::error_code to std::exception_ptr conversion
std::exception_ptr operator()(std::error_code&& error) const noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the std::error_code can be passed by-value here (it is cheap to copy, and passing by-value allows callers to pass lvalues to this function)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::exception_ptr operator()(std::error_code&& error) const noexcept {
std::exception_ptr operator()(std::error_code error) const noexcept {

Comment on lines +130 to +131
(requires (!tag_invocable<_set_error_fn, Receiver, Error>) AND
std::invocable<decltype(&Receiver::set_error), Receiver, Error>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that there is another case that still needs to be handled in here.

At the moment this code has the following order of preference in which it resolves the implementation of unifex::set_error(r, err):

  1. tag_invoke(set_error, r, err) if that is well-formed; otherwise
  2. r.set_error(err) if that is well-formed; otherwise
  3. r.set_error(as_exception_ptr(err)) if that is well-formed; otherwise
  4. set_error(r, err) call is ill-formed

I think that we probably need to have an extra case inserted into here to handle the case where tag_invoke(set_err, r, err) is ill-formed but tag_invoke(set_err, r, as_exception_ptr(err)) is well-formed.
I think this should probably sit in between cases 2 and 3 above, although it's possible you could argue it should go between cases 1 and 2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could possibly be handled with a two-stage resolution process using a helper.

struct set_error_fn {
private:
  template(typename Receiver, typename Error)
    (requires tag_invocable<set_error_fn, Receiver, Error>)
  static void dispatch(Receiver&& r, Error&& err) noexcept {
    static_assert(is_nothrow_tag_invocable_v<set_error_fn, Receiver, Error>);
    static_assert(std::is_void_v<tag_invoke_result_t<set_error_fn, Receiver, Error>>);
    tag_invoke(set_error_fn{}, (Receiver&&)r, (Error&&)err);
   }
   
   template(typename Receiver, typename Error>
     (requires (!tag_invocable<set_error_fn, Receiver, Error> AND
          has_member_set_error<Receiver, Error>)
   static void dispatch(Receiver&& r, Error&& err) noexcept {
     static_cast<Receiver&&>(r).set_error(static_cast<Error&&>(err));
   }
   template<typename Receiver, typename Error, typename = void>
   static constexpr bool can_dispatch_v = false;
   template<typename Receiver, typename Error>
   static constexpr bool can_dispatch_v<Receiver, Error, std::void_t<decltype(dispatch(std::declval<Receiver>(), std::declval<Error>()))>> = true;
public:
  template(typename Receiver, typename Error)
    (requires can_dispatch_v<Receiver, Error>)
  void operator()(Receiver&& r, Error&& err) const noexcept {
    dispatch((Receiver&&)r, (Error&&)err);
  }
  template(typename Receiver, typename Error)
    (requires (!can_dispatch_v<Receiver, Error>) AND
              callable<decltype(as_exception_ptr), Error> AND
              can_dispatch_v<Receiver, std::exception_ptr>)
   void operator()(Receiver&& r, Error&& err) const noexcept {
     dispatch((Receiver&&)r, as_exception_ptr((Error&&)err));
   }
};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we allowed to use syntax like if constexpr ( requires { is_this_ill_formed(error); }) {...} else {...} ? Wich is OK in GCC11 and latest MSVC. It would greatly simplify this kind of dispatching stuff.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After double-check this syntax does not work on MSVC (I was actualy using clang-cl)

Comment on lines +107 to +108
UNIFEX_CONCEPT nothrow_tag_invocable =
(is_nothrow_tag_invocable_v<CPO, Args...>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the nothrow type-trait queries haven't been represented as concepts but rather as boolean type-traits.
I'm not sure exactly why that is, though. Maybe @ericniebler has some thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precedence for one. The fact that it's all syntax and no semantics for another. Neither of which are particularly compelling reasons.

}

// default std::error_code to std::exception_ptr conversion
std::exception_ptr operator()(std::error_code&& error) const noexcept {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::exception_ptr operator()(std::error_code&& error) const noexcept {
std::exception_ptr operator()(std::error_code error) const noexcept {

template(typename Receiver, typename Error)
(requires (!tag_invocable<_set_error_fn, Receiver, Error>))
(requires (!tag_invocable<_set_error_fn, Receiver, Error>) AND
std::invocable<decltype(&Receiver::set_error), Receiver, Error>)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::invocable<decltype(&Receiver::set_error), Receiver, Error>

This is not the right way to check for a .set_error(Error) member function. Consider that Receiver::set_error could be a template. In that case, &Receiver::set_error is ill-formed. Check how such constraints are checked elsewhere; you'll need to test the validity of the expression rec.set_error(err) in a SFINAE context (or with a concept) to get a correct answer.

template(typename Receiver, typename Error)
(requires (!tag_invocable<_set_error_fn, Receiver, Error>) AND
(!std::invocable<decltype(&Receiver::set_error), Receiver, Error>) AND
std::invocable<decltype(&Receiver::set_error), Receiver, std::exception_ptr> AND
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here about testing for a .set_error(e) member function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants