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

stackful coroutines. #158

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .drone.star
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ deps = [
'libs/assert',
'libs/beast',
'libs/bind',
'libs/callable_traits',
'libs/chrono',
'libs/circular_buffer',
'libs/concept_check',
'libs/config',
'libs/container',
'libs/context',
'libs/core',
'libs/date_time',
'libs/detail',
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ jobs:
python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -165,6 +167,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# temporary workaround,
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
cmd /c bootstrap
Expand Down Expand Up @@ -221,6 +225,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -277,6 +283,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -350,6 +358,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -403,6 +413,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -458,6 +470,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down Expand Up @@ -533,6 +547,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio

Expand Down
6 changes: 6 additions & 0 deletions doc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ include::reference/error.adoc[]
include::reference/config.adoc[]
include::reference/leaf.adoc[]

include::reference/experimental/context.adoc[]
include::reference/experimental/fiber.adoc[]
include::reference/experimental/continuation.adoc[]



= In-Depth

include::background/custom_executors.adoc[]
Expand Down
69 changes: 69 additions & 0 deletions doc/reference/experimental/context.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[#context]
== cobalt/experimental/context.hpp

WARNING: This is (most likely) undefined behaviour, since the violates a precondition in the standard. A paper to address this can be found here (https://isocpp.org/files/papers/P3203R0.html).

This header provides `experimental` support for using `boost.fiber` based stackful coroutines
as if they were C++20 coroutines. That is, they can use `awaitables` by being able to be put into a `coroutine_handle`.
Likewise the implementation uses a C++20 coroutine promise and runs is as if it was a C++20 coroutine.

[source,cpp]
----
//
void delay(experimental::context<promise<void>> h, std::chrono::milliseconds ms)
{
asio::steady_timer tim{co_await cobalt::this_coro::executor, ms};
h.await(tim.async_wait(cobalt::use_op)); // instead of co_await.
}

cobalt::main co_main(int argc, char *argv[])
{
cobalt::promise<void> dl = cobalt::experimental::make_context(&delay, 50);
co_await dl;
co_return 0;
}
----

=== Reference

[source,cpp]
----
// The internal coroutine context.
/// Args are the function arguments after the handle.
template<typename Return, typename ... Args>
struct context
{
// Get a handle to the promise
promise_type & promise();
const promise_type & promise() const;

// Convert it to any context if the underlying promise is the same
template<typename Return_, typename ... Args_>
constexpr operator context<Return_, Args_...>() const;

// Await something. Uses await_transform automatically.
template<typename Awaitable>
auto await(Awaitable && aw);
// Yield a value, if supported by the promise.
template<typename Yield>
auto yield(Yield && value);
};


// Create a fiber with a custom stack allocator (see boost.fiber for details) and explicit result (e.g. `promise<void>`)
template<typename Return, typename ... Args, std::invocable<context<Return, Args...>, Args...> Func, typename StackAlloc>
auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args);

// Create a fiber with the default allocator and explicit result (e.g. `promise<void>`)
template<typename Return, typename ... Args, std::invocable<context<Return, Args...>, Args...> Func>
auto make_context(Func && func, Args && ... args);

// Create a fiber with a custom stack allocator and implicit result (deduced from the first argument to func).
template<typename ... Args, typename Func, typename StackAlloc>
auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args);

// Create a fiber with the default stack allocator and implicit result (deduced from the first argument to func).
template<typename ... Args, typename Func>
auto make_context(Func && func, Args && ... args);
----

63 changes: 63 additions & 0 deletions doc/reference/experimental/continuation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[#continuation]
== cobalt/experimental/continuation.hpp

WARNING: This is clearly undefined behaviour, since it does an explicit specialization of `std::coroutine_handle`. A paper to address this can be found here (https://isocpp.org/files/papers/P3203R0.html).

This header provides low level support for creating a `std::coroutine_handle` from within a `boost::context::continuation`.


[source,cpp]
----
template<cobalt::awaitable_type Aw>
auto continuation_await(boost::context::continuation && f, Aw && aw)
{
if (!aw.await_ready())
{
boost::cobalt::experimental::continuation_frame ff; // This must be on the continuation stack
f = std::move(f).resume_with(
[&](boost::context::continuation && f_) -> boost::context::continuation
{
ff.continuation() = std::move(f_);
std::coroutine_handle<boost::context::continuation> h(ff);

static_assert(std::is_void_v<decltype(aw.await_suspend(h)>,
"This example only supports awaitables return `void` from await_suspend");
aw.await_suspend(h);
return {};
});
}

return aw.await_resume();
}
----

=== Reference

[source,cpp]
----
struct experimental::continuation_frame
{
boost::context::continuation& continuation();
const boost::context::continuation& continuation() const;

continuation_frame(boost::context::continuation && continuation_ = {});
};

template<>
struct std::coroutine_handle<boost::context::continuation>
{
constexpr operator coroutine_handle<>() const noexcept;
constexpr explicit operator bool() const noexcept;

constexpr bool done() const noexcept:
void operator()() const noexcept;

void resume() const noexcept;
void destroy() const noexcept;

boost::context::continuation& promise() const noexcept;
constexpr void* address() const noexcept;
// construct if from a frame
coroutine_handle(boost::cobalt::experimental::continuation_frame & frame
};
----
63 changes: 63 additions & 0 deletions doc/reference/experimental/fiber.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[#fiber]
== cobalt/experimental/fiber.hpp

WARNING: This is clearly undefined behaviour, since it does an explicit specialization of `std::coroutine_handle`. A paper to address this can be found here (https://isocpp.org/files/papers/P3203R0.html).

This header provides low level support for creating a `std::coroutine_handle` from within a `boost::context::fiber`.


[source,cpp]
----
template<cobalt::awaitable_type Aw>
auto fiber_await(boost::context::fiber && f, Aw && aw)
{
if (!aw.await_ready())
{
boost::cobalt::experimental::fiber_frame ff; // This must be on the fiber stack
f = std::move(f).resume_with(
[&](boost::context::fiber && f_) -> boost::context::fiber
{
ff.fiber() = std::move(f_);
std::coroutine_handle<boost::context::fiber> h(ff);

static_assert(std::is_void_v<decltype(aw.await_suspend(h)>,
"This example only supports awaitables return `void` from await_suspend");
aw.await_suspend(h);
return {};
});
}

return aw.await_resume();
}
----

=== Reference

[source,cpp]
----
struct experimental::fiber_frame
{
boost::context::fiber& fiber();
const boost::context::fiber& fiber() const;

fiber_frame(boost::context::fiber && fiber_ = {});
};

template<>
struct std::coroutine_handle<boost::context::fiber>
{
constexpr operator coroutine_handle<>() const noexcept;
constexpr explicit operator bool() const noexcept;

constexpr bool done() const noexcept:
void operator()() const noexcept;

void resume() const noexcept;
void destroy() const noexcept;

boost::context::fiber& promise() const noexcept;
constexpr void* address() const noexcept;
// construct if from a frame
coroutine_handle(boost::cobalt::experimental::fiber_frame & frame
};
----
4 changes: 0 additions & 4 deletions include/boost/cobalt/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@
# include <boost/asio/any_io_executor.hpp>
#endif

#if BOOST_VERSION < 108200
#error "Boost.Async needs Boost v1.82 or later otherwise the Boost.ASIO is missing needed support"
#endif

#if defined(_MSC_VER)
// msvc doesn't correctly suspend for self-deletion, hence we must workaround here
#define BOOST_COBALT_NO_SELF_DELETE 1
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/detached.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct detached_promise
{
}

std::suspend_never initial_suspend() {return {};}
std::suspend_never initial_suspend() noexcept {return {};}
std::suspend_never final_suspend() noexcept {return {};}

void return_void() {}
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ struct generator_promise
this->reset_cancellation_source(signal.slot());
}

std::suspend_never initial_suspend() {return {};}
std::suspend_never initial_suspend() noexcept {return {};}

struct final_awaitable
{
Expand Down
5 changes: 3 additions & 2 deletions include/boost/cobalt/detail/join.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,17 @@ struct join_variadic_impl
using rt = system::result<t, std::exception_ptr>;
if (error)
return rt(system::in_place_error, error);

if constexpr(!all_void)
return mp11::tuple_transform(
[]<typename T>(std::optional<T> & var)
-> T
-> rt
{
BOOST_ASSERT(var.has_value());
return std::move(*var);
}, result);
else
return system::in_place_value;
return rt{system::in_place_value};
}
};
awaitable operator co_await() &&
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct main_promise : signal_helper,
return my_resource->deallocate(raw, size);
}
#endif
std::suspend_always initial_suspend() {return {};}
std::suspend_always initial_suspend() noexcept {return {};}

BOOST_COBALT_DECL
auto final_suspend() noexcept -> std::suspend_never;
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/promise.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ struct cobalt_promise
this->reset_cancellation_source(signal.slot());
}

std::suspend_never initial_suspend() {return {};}
std::suspend_never initial_suspend() noexcept {return {};}
auto final_suspend() noexcept
{
return final_awaitable{this};
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ struct task_promise
}
};

auto initial_suspend()
auto initial_suspend() noexcept
{

return initial_awaitable{this};
Expand Down
2 changes: 1 addition & 1 deletion include/boost/cobalt/detail/with.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct with_impl<T>::promise_type
e = std::current_exception();
}

std::suspend_always initial_suspend() {return {};}
std::suspend_always initial_suspend() noexcept {return {};}

struct final_awaitable
{
Expand Down
Loading
Loading