Skip to content

Commit

Permalink
feat(coro): dpp::task::cancel(), lock-free dpp::task
Browse files Browse the repository at this point in the history
  • Loading branch information
Mishura4 committed Aug 21, 2023
1 parent 7570cf7 commit 489579a
Show file tree
Hide file tree
Showing 5 changed files with 456 additions and 129 deletions.
4 changes: 1 addition & 3 deletions include/dpp/coro/awaitable.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct confirmation_callback_t;
* @brief A co_await-able object handling an API call.
*
* @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables.
* @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @tparam R The return type of the API call. Defaults to confirmation_callback_t
*/
template <typename R>
Expand Down Expand Up @@ -112,8 +112,6 @@ struct awaitable {
*/
template <typename T>
void await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept(noexcept(std::invoke(fun, std::declval<std::function<void(R)>&&>()))) {
if constexpr (requires (T promise) {{promise.is_sync} -> std::same_as<bool&>;})
caller.promise().is_sync = false;
std::invoke(fun, [this, caller](auto &&api_result) {
result = api_result;
caller.resume();
Expand Down
94 changes: 92 additions & 2 deletions include/dpp/coro/coro.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,86 @@ namespace std_coroutine = std;
# endif
#endif

#ifndef _DOXYGEN_
/**
* @brief Concept to check if a type has a useable `operator co_await()` member
*/
template <typename T>
concept has_co_await_member = requires (T expr) { expr.operator co_await(); };

/**
* @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
*/
template <typename T>
concept has_free_co_await = requires (T expr) { operator co_await(expr); };

/**
* @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
*/
template <typename T>
concept has_await_members = requires (T expr) { expr.await_ready(); expr.await_suspend(); expr.await_resume(); };

/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (has_co_await_member<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(expr.operator co_await())) {
decltype(auto) awaiter = expr.operator co_await();
return awaiter;
}

/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (!has_co_await_member<T> && has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(operator co_await(expr))) {
decltype(auto) awaiter = operator co_await(static_cast<T&&>(expr));
return awaiter;
}

/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (!has_co_await_member<T> && !has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept {
return static_cast<T&&>(expr);
}
#else
/**
* @brief Concept to check if a type has a useable `operator co_await()` member
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
bool has_co_await_member;

/**
* @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
bool has_free_co_await;

/**
* @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
bool has_await_members;

/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*
* This function is conditionally noexcept, if the returned expression also is.
*/
decltype(auto) co_await_resolve(auto&& expr) {}
#endif

} // namespace detail

struct confirmation_callback_t;
Expand All @@ -76,13 +156,23 @@ template <typename R = confirmation_callback_t>
class async;

template <typename R = void>
#ifndef _DOXYGEN_
requires std::is_same_v<void, R> || (!std::is_reference_v<R> && std::is_move_constructible_v<R> && std::is_move_assignable_v<R>)
#ifndef DOXYGEN
requires (!std::is_reference_v<R>)
#endif
class task;

struct job;

#ifdef DPP_CORO_TEST
/**
* @brief Allocation count of a certain type, for testing purposes.
*
* @todo Remove when coro is stable
*/
template <typename T>
inline int coro_alloc_count = 0;
#endif

} // namespace dpp

#endif /* DPP_CORO */
Expand Down
26 changes: 25 additions & 1 deletion include/dpp/coro/job.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ namespace dpp {
*
* This object stores no state and is the recommended way to use coroutines if you do not need to co_await the result.
*
* @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing.
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @warning - It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing.
* At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references.
* This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html.
* For this reason, `co_await` will error if any parameters are passed by reference.
Expand All @@ -45,11 +46,26 @@ struct job {};

namespace detail {

#ifdef DPP_CORO_TEST
struct job_promise_base{};
#endif

/**
* @brief Coroutine promise type for a job
*/
template <bool has_reference_params>
struct job_promise {

#ifdef DPP_CORO_TEST
job_promise() {
++coro_alloc_count<job_promise_base>;
}

~job_promise() {
--coro_alloc_count<job_promise_base>;
}
#endif

/*
* @brief Function called when the job is done.
*
Expand Down Expand Up @@ -112,6 +128,14 @@ struct job_promise {

} // namespace dpp

template <>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::job> {
/**
* @brief Promise type for this coroutine signature.
*/
using promise_type = dpp::detail::job_promise<false>;
};

/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
Expand Down
Loading

0 comments on commit 489579a

Please sign in to comment.