diff --git a/include/seastar/core/coroutine.hh b/include/seastar/core/coroutine.hh index da35d3ce95..67b501cb6b 100644 --- a/include/seastar/core/coroutine.hh +++ b/include/seastar/core/coroutine.hh @@ -46,9 +46,8 @@ public: promise_type(promise_type&&) = delete; promise_type(const promise_type&) = delete; - template - void return_value(U&&... value) { - _promise.set_value(std::forward(value)...); + void return_value(T value) { + _promise.set_value_strict(std::forward(value)); } void return_value(coroutine::exception ce) noexcept { @@ -59,11 +58,6 @@ public: _promise.set_exception(std::move(eptr)); } - [[deprecated("Forwarding coroutine returns are deprecated as too dangerous. Use 'co_return co_await ...' until explicit syntax is available.")]] - void return_value(future&& fut) noexcept { - fut.forward_to(std::move(_promise)); - } - void unhandled_exception() noexcept { _promise.set_exception(std::current_exception()); } diff --git a/include/seastar/core/future.hh b/include/seastar/core/future.hh index 3d89f6b309..b74b710021 100644 --- a/include/seastar/core/future.hh +++ b/include/seastar/core/future.hh @@ -297,6 +297,9 @@ struct uninitialized_wrapper { public: uninitialized_wrapper() noexcept = default; + void uninitialized_set_strict(T v) { + new (&_v.value) auto(std::forward(v)); + } template requires (!std::same_as...>, std::tuple>) void @@ -546,6 +549,7 @@ inline void future_state_base::any::check_failure() noexcept { } struct ready_future_marker {}; +struct strict_type_ready_future_marker {}; struct exception_future_marker {}; struct future_for_get_promise_marker {}; @@ -601,6 +605,13 @@ struct future_state : public future_state_base, private internal::uninitialized move_it(std::move(x)); return *this; } + future_state(strict_type_ready_future_marker, T v) noexcept : future_state_base(state::result) { + try { + this->uninitialized_set_strict(std::forward(v)); + } catch (...) { + new (this) future_state(current_exception_future_marker()); + } + } template future_state(ready_future_marker, A&&... a) noexcept : future_state_base(state::result) { try { @@ -609,6 +620,10 @@ struct future_state : public future_state_base, private internal::uninitialized new (this) future_state(current_exception_future_marker()); } } + void set_strict(T v) { + assert(_u.st == state::future); + new (this) future_state(strict_type_ready_future_marker(), std::forward(v)); + } template void set(A&&... a) noexcept { assert(_u.st == state::future); @@ -852,7 +867,8 @@ public: template class promise_base_with_type : protected internal::promise_base { protected: - using future_state = seastar::future_state>; + using value_type = future_stored_type_t; + using future_state = seastar::future_state; future_state* get_state() noexcept { return static_cast(_state); } @@ -876,6 +892,13 @@ public: } } + void set_value_strict(value_type v) noexcept { + if (auto *s = get_state()) { + s->set_strict(std::forward(v)); + make_ready(); + } + } + template void set_value(A&&... a) noexcept { if (auto *s = get_state()) { @@ -911,6 +934,7 @@ private: SEASTAR_MODULE_EXPORT template class promise : private internal::promise_base_with_type { + using value_type = typename internal::promise_base_with_type::value_type; using future_state = typename internal::promise_base_with_type::future_state; future_state _local_state; @@ -954,6 +978,16 @@ public: /// was attached to the future, it will run. future get_future() noexcept; + /// \brief Sets the promises value using exactly declared type + /// + /// Forwards the argument and makes them available to the associated + /// future. May be called either before or after \c get_future(). + /// + /// pr.set_value_strict(42); + void set_value_strict(value_type v) noexcept { + internal::promise_base_with_type::set_value_strict(std::forward(v)); + } + /// \brief Sets the promises value /// /// Forwards the arguments and makes them available to the associated diff --git a/tests/unit/coroutines_test.cc b/tests/unit/coroutines_test.cc index 0b1d3e79c8..9b768f415a 100644 --- a/tests/unit/coroutines_test.cc +++ b/tests/unit/coroutines_test.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1018,3 +1019,67 @@ SEASTAR_TEST_CASE(test_lambda_coroutine_in_continuation) { })); BOOST_REQUIRE_EQUAL(sin1, sin2); } + +#ifdef __cpp_lib_expected + +future> void_return_expected() { + co_return {}; +} + +SEASTAR_TEST_CASE(test_std_expected_void_specialization) { + auto result = co_await void_return_expected(); + BOOST_REQUIRE(result.has_value()); +} + +#endif + +struct explisyt; +struct implicit { + implicit() = default; + implicit(explisyt &&) {} + operator explisyt(); +}; +struct explisyt { + explisyt() = default; + explicit operator implicit() { + throw 42; + } + explicit explisyt(implicit &&) { + throw 42; + } +}; +implicit::operator explisyt() { return {}; } + +// implicit conversion used, explicit ctor ruled out +future i2e() { + co_return implicit(); +}; + +// implicit ctor used, explicit conversion ruled out +future e2i() { + co_return explisyt(); +}; + +future> co_return_vector(int cnt) { + switch (cnt) { + case 0: + co_return {}; + case 2: + co_return {"foo", "foo"}; + case 3: + co_return {3, "foo"}; + default: + throw std::runtime_error("bad option"); + } +} + +SEASTAR_TEST_CASE(test_co_return_conversions) { + std::ignore = co_await i2e(); + std::ignore = co_await e2i(); + BOOST_REQUIRE_EQUAL(co_await co_return_vector(0), (std::vector{})); + // gcc 13.3 will ICE if we inline the expected values here + std::vector foo_x2{"foo", "foo"}; + BOOST_REQUIRE_EQUAL(co_await co_return_vector(2), foo_x2); + std::vector foo_x3{"foo", "foo", "foo"}; + BOOST_REQUIRE_EQUAL(co_await co_return_vector(3), foo_x3); +} \ No newline at end of file