diff --git a/examples/linux/file_coroutines_test.cpp b/examples/linux/file_coroutines_test.cpp new file mode 100644 index 000000000..5e0dfba6f --- /dev/null +++ b/examples/linux/file_coroutines_test.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +// requires both coroutine and liburing support (for now) +#if !UNIFEX_NO_COROUTINES and !UNIFEX_NO_LIBURING +# include +using io_context = unifex::linuxos::io_uring_context; + +# include +# include + +using namespace unifex; + +int main() { + io_context ctx{}; + auto sched = ctx.get_scheduler(); + inplace_stop_source stopSource; + std::thread t{[&] { + ctx.run(stopSource.get_token()); + }}; + scope_guard stopOnExit = [&]() noexcept { + stopSource.request_stop(); + t.join(); + }; + sync_wait([&]() -> task { + auto file = open_file_write_only( + sched, filesystem::path{"file_coroutine_test.txt"}); + constexpr char hello[]{"hello\n"}; + size_t offset = 0; + for (int ii = 0; ii < 42; ++ii) { + offset += co_await async_write_some_at( + file, offset, as_bytes(span{hello, sizeof(hello) - 1})); + } + std::printf("wrote %zu bytes\n", offset); + }()); + return 0; +} +#else +# include +int main() { + printf("neither io_uring or coroutine support found\n"); + return 0; +} +#endif diff --git a/include/unifex/as_exception_ptr.hpp b/include/unifex/as_exception_ptr.hpp new file mode 100644 index 000000000..086a4739f --- /dev/null +++ b/include/unifex/as_exception_ptr.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace unifex +{ + namespace _as_exception_ptr + { + inline constexpr struct _fn { + // forward std::exception_ptr + std::exception_ptr operator()(std::exception_ptr eptr) const noexcept { + return eptr; + } + + // default std::error_code to std::exception_ptr conversion + std::exception_ptr operator()(std::error_code&& error) const noexcept { + return make_exception_ptr( + std::system_error{(std::error_code &&) error}); + } + + // convert std::exception based types to std::exception_ptr + template(typename Exception) + (requires (!tag_invocable<_fn, Exception>) AND + derived_from) + std::exception_ptr operator()(Exception&& ex) const noexcept { + return make_exception_ptr((Exception &&) ex); + } + + // use customization point + // to resolve ErrorCode -> std::exception_ptr conversion + template(typename ErrorCode) + (requires tag_invocable<_fn, ErrorCode>) + std::exception_ptr operator()(ErrorCode&& error) const noexcept { + static_assert(nothrow_tag_invocable<_fn, ErrorCode>, + "as_exception_ptr() customisations must be declared noexcept"); + return tag_invoke(*this, (ErrorCode &&) error); + } + } as_exception_ptr{}; + } // namespace _as_exception_ptr + + using _as_exception_ptr::as_exception_ptr; +} // namespace unifex + +#include diff --git a/include/unifex/receiver_concepts.hpp b/include/unifex/receiver_concepts.hpp index eefd418a7..69e6b743d 100644 --- a/include/unifex/receiver_concepts.hpp +++ b/include/unifex/receiver_concepts.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -96,12 +97,20 @@ namespace _rec_cpo { using set_error_member_result_t = decltype(UNIFEX_DECLVAL(Receiver).set_error(UNIFEX_DECLVAL(Error))); template - using _result_t = - typename conditional_t< - tag_invocable<_set_error_fn, Receiver, Error>, - meta_tag_invoke_result<_set_error_fn>, - meta_quote2>::template apply; + static auto _select() { + if constexpr (tag_invocable<_set_error_fn, Receiver, Error>) { + return meta_tag_invoke_result<_set_error_fn>{}; + } else if constexpr (tag_invocable<_set_error_fn, Receiver, std::exception_ptr>) { + return meta_tag_invoke_result<_set_error_fn>{}; + } else { + return meta_quote2{}; + } + } + template + using _result_t = typename decltype(_set_error_fn::_select()) + ::template apply; public: + // call through tag_invoke template(typename Receiver, typename Error) (requires tag_invocable<_set_error_fn, Receiver, Error>) auto operator()(Receiver&& r, Error&& error) const noexcept @@ -115,8 +124,11 @@ namespace _rec_cpo { return unifex::tag_invoke( _set_error_fn{}, (Receiver &&) r, (Error&&) error); } + + // direct call template(typename Receiver, typename Error) - (requires (!tag_invocable<_set_error_fn, Receiver, Error>)) + (requires (!tag_invocable<_set_error_fn, Receiver, Error>) AND + std::invocable) auto operator()(Receiver&& r, Error&& error) const noexcept -> _result_t { static_assert( @@ -124,6 +136,20 @@ namespace _rec_cpo { "receiver.set_error() method must be nothrow invocable"); return static_cast(r).set_error((Error&&) error); } + + // call through as_exception_ptr + template(typename Receiver, typename Error) + (requires (!tag_invocable<_set_error_fn, Receiver, Error>) AND + (!std::invocable) AND + std::invocable AND + std::invocable) + auto operator()(Receiver&& r, Error&& error) const noexcept + -> _result_t { + static_assert( + noexcept(static_cast(r).set_error(as_exception_ptr((Error&&) error))), + "receiver.set_error() method must be nothrow invocable"); + return static_cast(r).set_error(as_exception_ptr((Error&&) error)); + } } set_error{}; inline const struct _set_done_fn { diff --git a/include/unifex/sync_wait.hpp b/include/unifex/sync_wait.hpp index 834d0281e..a4c5486b4 100644 --- a/include/unifex/sync_wait.hpp +++ b/include/unifex/sync_wait.hpp @@ -15,14 +15,15 @@ */ #pragma once +#include +#include +#include +#include #include #include #include #include -#include #include -#include -#include #include #include @@ -83,15 +84,6 @@ struct _receiver { signal_complete(); } - void set_error(std::error_code ec) && noexcept { - std::move(*this).set_error(make_exception_ptr(std::system_error{ec, "sync_wait"})); - } - - template - void set_error(Error&& e) && noexcept { - std::move(*this).set_error(make_exception_ptr((Error&&)e)); - } - void set_done() && noexcept { promise_.state_ = promise::state::done; signal_complete(); diff --git a/include/unifex/tag_invoke.hpp b/include/unifex/tag_invoke.hpp index 3351ca53c..c3c8eb3ec 100644 --- a/include/unifex/tag_invoke.hpp +++ b/include/unifex/tag_invoke.hpp @@ -101,8 +101,11 @@ namespace unifex { template UNIFEX_CONCEPT tag_invocable = - (sizeof(_tag_invoke::try_tag_invoke(0)) == - sizeof(_tag_invoke::yes_type)); + (is_tag_invocable_v); + + template + UNIFEX_CONCEPT nothrow_tag_invocable = + (is_nothrow_tag_invocable_v); template using meta_tag_invoke_result = diff --git a/test/as_exception_ptr_test.cpp b/test/as_exception_ptr_test.cpp new file mode 100644 index 000000000..52560c6da --- /dev/null +++ b/test/as_exception_ptr_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +using namespace unifex; + +TEST(as_exception_ptr, error_code) { + try { + std::rethrow_exception( + as_exception_ptr(std::make_error_code(std::errc::not_supported))); + } catch (std::system_error& ex) { + EXPECT_EQ(ex.code(), std::errc::not_supported); + } +} + +struct test_error { + int error_code; + + friend std::exception_ptr + tag_invoke(tag_t, test_error&& error) noexcept { + return std::make_exception_ptr(std::runtime_error( + std::to_string(std::forward(error).error_code))); + } +}; + +TEST(as_exception_ptr, custom_error) { + try { + std::rethrow_exception(as_exception_ptr(test_error{42})); + } catch (std::runtime_error& ex) { + EXPECT_EQ(ex.what(), std::string_view{"42"}); + } +} + +struct error_code_receiver { + std::optional& ec_; + void set_error(std::error_code ec) && noexcept { + ec_ = (std::error_code &&) ec; + } +}; + +struct exception_ptr_receiver { + std::optional& ex_; + void set_error(std::exception_ptr ex) && noexcept { + ex_ = (std::exception_ptr &&) ex; + } +}; + +TEST(as_exception_ptr, set_error) { + { // direct call with error_code + std::optional ec{}; + unifex::set_error( + error_code_receiver{ec}, + std::make_error_code(std::errc::not_supported)); + EXPECT_TRUE(ec.has_value()); + } + { // direct call with exception_ptr + std::optional ex{}; + auto eptr = std::make_exception_ptr( + std::system_error{std::make_error_code(std::errc::not_supported)}); + unifex::set_error(exception_ptr_receiver{ex}, std::move(eptr)); + EXPECT_TRUE(ex.has_value()); + } + { // call through as_exception_ptr CPO + std::optional ex{}; + unifex::set_error( + exception_ptr_receiver{ex}, + std::make_error_code(std::errc::not_supported)); + EXPECT_TRUE(ex.has_value()); + } +}