This commit is contained in:
jeanlemotan
2024-07-02 18:13:47 +02:00
commit bbeaa887cd
173 changed files with 34365 additions and 0 deletions
@@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_auto_reset_event_operation;
/// An async auto-reset event is a coroutine synchronisation abstraction
/// that allows one or more coroutines to wait until some thread calls
/// set() on the event.
///
/// When a coroutine awaits a 'set' event the event is automatically
/// reset back to the 'not set' state, thus the name 'auto reset' event.
class async_auto_reset_event
{
public:
/// Initialise the event to either 'set' or 'not set' state.
async_auto_reset_event(bool initiallySet = false) noexcept;
~async_auto_reset_event();
/// Wait for the event to enter the 'set' state.
///
/// If the event is already 'set' then the event is set to the 'not set'
/// state and the awaiting coroutine continues without suspending.
/// Otherwise, the coroutine is suspended and later resumed when some
/// thread calls 'set()'.
///
/// Note that the coroutine may be resumed inside a call to 'set()'
/// or inside another thread's call to 'operator co_await()'.
async_auto_reset_event_operation operator co_await() const noexcept;
/// Set the state of the event to 'set'.
///
/// If there are pending coroutines awaiting the event then one
/// pending coroutine is resumed and the state is immediately
/// set back to the 'not set' state.
///
/// This operation is a no-op if the event was already 'set'.
void set() noexcept;
/// Set the state of the event to 'not-set'.
///
/// This is a no-op if the state was already 'not set'.
void reset() noexcept;
private:
friend class async_auto_reset_event_operation;
void resume_waiters(std::uint64_t initialState) const noexcept;
// Bits 0-31 - Set count
// Bits 32-63 - Waiter count
mutable std::atomic<std::uint64_t> m_state;
mutable std::atomic<async_auto_reset_event_operation*> m_newWaiters;
mutable async_auto_reset_event_operation* m_waiters;
};
class async_auto_reset_event_operation
{
public:
async_auto_reset_event_operation() noexcept;
explicit async_auto_reset_event_operation(const async_auto_reset_event& event) noexcept;
async_auto_reset_event_operation(const async_auto_reset_event_operation& other) noexcept;
bool await_ready() const noexcept { return m_event == nullptr; }
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class async_auto_reset_event;
const async_auto_reset_event* m_event;
async_auto_reset_event_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
std::atomic<std::uint32_t> m_refCount;
};
}
#endif
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_LATCH_HPP_INCLUDED
#define CPPCORO_ASYNC_LATCH_HPP_INCLUDED
#include <cppcoro/async_manual_reset_event.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_latch
{
public:
/// Construct the latch with the specified initial count.
///
/// \param initialCount
/// The initial count of the latch. The latch will become signalled once
/// \c this->count_down() has been called \p initialCount times.
/// The latch will be immediately signalled on construction if this
/// parameter is zero or negative.
async_latch(std::ptrdiff_t initialCount) noexcept
: m_count(initialCount)
, m_event(initialCount <= 0)
{}
/// Query if the latch has become signalled.
///
/// The latch is marked as signalled once the count reaches zero.
bool is_ready() const noexcept { return m_event.is_set(); }
/// Decrement the count by n.
///
/// Any coroutines awaiting this latch will be resumed once the count
/// reaches zero. ie. when this method has been called at least 'initialCount'
/// times.
///
/// Any awaiting coroutines that are currently suspended waiting for the
/// latch to become signalled will be resumed inside the last call to this
/// method (ie. the call that decrements the count to zero).
///
/// \param n
/// The amount to decrement the count by.
void count_down(std::ptrdiff_t n = 1) noexcept
{
if (m_count.fetch_sub(n, std::memory_order_acq_rel) <= n)
{
m_event.set();
}
}
/// Allows the latch to be awaited within a coroutine.
///
/// If the latch is already signalled (ie. the count has been decremented
/// to zero) then the awaiting coroutine will continue without suspending.
/// Otherwise, the coroutine will suspend and will later be resumed inside
/// a call to `count_down()`.
auto operator co_await() const noexcept
{
return m_event.operator co_await();
}
private:
std::atomic<std::ptrdiff_t> m_count;
async_manual_reset_event m_event;
};
}
#endif
@@ -0,0 +1,104 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_manual_reset_event_operation;
/// An async manual-reset event is a coroutine synchronisation abstraction
/// that allows one or more coroutines to wait until some thread calls
/// set() on the event.
///
/// When a coroutine awaits a 'set' event the coroutine continues without
/// suspending. Otherwise, if it awaits a 'not set' event the coroutine is
/// suspended and is later resumed inside the call to 'set()'.
///
/// \seealso async_auto_reset_event
class async_manual_reset_event
{
public:
/// Initialise the event to either 'set' or 'not set' state.
///
/// \param initiallySet
/// If 'true' then initialises the event to the 'set' state, otherwise
/// initialises the event to the 'not set' state.
async_manual_reset_event(bool initiallySet = false) noexcept;
~async_manual_reset_event();
/// Wait for the event to enter the 'set' state.
///
/// If the event is already 'set' then the coroutine continues without
/// suspending.
///
/// Otherwise, the coroutine is suspended and later resumed when some
/// thread calls 'set()'. The coroutine will be resumed inside the next
/// call to 'set()'.
async_manual_reset_event_operation operator co_await() const noexcept;
/// Query if the event is currently in the 'set' state.
bool is_set() const noexcept;
/// Set the state of the event to 'set'.
///
/// If there are pending coroutines awaiting the event then all
/// pending coroutines are resumed within this call.
/// Any coroutines that subsequently await the event will continue
/// without suspending.
///
/// This operation is a no-op if the event was already 'set'.
void set() noexcept;
/// Set the state of the event to 'not-set'.
///
/// Any coroutines that subsequently await the event will suspend
/// until some thread calls 'set()'.
///
/// This is a no-op if the state was already 'not set'.
void reset() noexcept;
private:
friend class async_manual_reset_event_operation;
// This variable has 3 states:
// - this - The state is 'set'.
// - nullptr - The state is 'not set' with no waiters.
// - other - The state is 'not set'.
// Points to an 'async_manual_reset_event_operation' that is
// the head of a linked-list of waiters.
mutable std::atomic<void*> m_state;
};
class async_manual_reset_event_operation
{
public:
explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept;
bool await_ready() const noexcept;
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class async_manual_reset_event;
const async_manual_reset_event& m_event;
async_manual_reset_event_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
};
}
#endif
+200
View File
@@ -0,0 +1,200 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
#include <mutex> // for std::adopt_lock_t
namespace cppcoro
{
class async_mutex_lock;
class async_mutex_lock_operation;
class async_mutex_scoped_lock_operation;
/// \brief
/// A mutex that can be locked asynchronously using 'co_await'.
///
/// Ownership of the mutex is not tied to any particular thread.
/// This allows the coroutine owning the lock to transition from
/// one thread to another while holding a lock.
///
/// Implementation is lock-free, using only std::atomic values for
/// synchronisation. Awaiting coroutines are suspended without blocking
/// the current thread if the lock could not be acquired synchronously.
class async_mutex
{
public:
/// \brief
/// Construct to a mutex that is not currently locked.
async_mutex() noexcept;
/// Destroys the mutex.
///
/// Behaviour is undefined if there are any outstanding coroutines
/// still waiting to acquire the lock.
~async_mutex();
/// \brief
/// Attempt to acquire a lock on the mutex without blocking.
///
/// \return
/// true if the lock was acquired, false if the mutex was already locked.
/// The caller is responsible for ensuring unlock() is called on the mutex
/// to release the lock if the lock was acquired by this call.
bool try_lock() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.lock_async()'
/// expression has type 'void'.
async_mutex_lock_operation lock_async() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously, returning an object that
/// will call unlock() automatically when it goes out of scope.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.scoped_lock_async()'
/// expression returns an 'async_mutex_lock' object that will call
/// this->mutex() when it destructs.
async_mutex_scoped_lock_operation scoped_lock_async() noexcept;
/// \brief
/// Unlock the mutex.
///
/// Must only be called by the current lock-holder.
///
/// If there are lock operations waiting to acquire the
/// mutex then the next lock operation in the queue will
/// be resumed inside this call.
void unlock();
private:
friend class async_mutex_lock_operation;
static constexpr std::uintptr_t not_locked = 1;
// assume == reinterpret_cast<std::uintptr_t>(static_cast<void*>(nullptr))
static constexpr std::uintptr_t locked_no_waiters = 0;
// This field provides synchronisation for the mutex.
//
// It can have three kinds of values:
// - not_locked
// - locked_no_waiters
// - a pointer to the head of a singly linked list of recently
// queued async_mutex_lock_operation objects. This list is
// in most-recently-queued order as new items are pushed onto
// the front of the list.
std::atomic<std::uintptr_t> m_state;
// Linked list of async lock operations that are waiting to acquire
// the mutex. These operations will acquire the lock in the order
// they appear in this list. Waiters in this list will acquire the
// mutex before waiters added to the m_newWaiters list.
async_mutex_lock_operation* m_waiters;
};
/// \brief
/// An object that holds onto a mutex lock for its lifetime and
/// ensures that the mutex is unlocked when it is destructed.
///
/// It is equivalent to a std::lock_guard object but requires
/// that the result of co_await async_mutex::lock_async() is
/// passed to the constructor rather than passing the async_mutex
/// object itself.
class async_mutex_lock
{
public:
explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
: m_mutex(&mutex)
{}
async_mutex_lock(async_mutex_lock&& other) noexcept
: m_mutex(other.m_mutex)
{
other.m_mutex = nullptr;
}
async_mutex_lock(const async_mutex_lock& other) = delete;
async_mutex_lock& operator=(const async_mutex_lock& other) = delete;
// Releases the lock.
~async_mutex_lock()
{
if (m_mutex != nullptr)
{
m_mutex->unlock();
}
}
private:
async_mutex* m_mutex;
};
class async_mutex_lock_operation
{
public:
explicit async_mutex_lock_operation(async_mutex& mutex) noexcept
: m_mutex(mutex)
{}
bool await_ready() const noexcept { return false; }
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
protected:
friend class async_mutex;
async_mutex& m_mutex;
private:
async_mutex_lock_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
};
class async_mutex_scoped_lock_operation : public async_mutex_lock_operation
{
public:
using async_mutex_lock_operation::async_mutex_lock_operation;
[[nodiscard]]
async_mutex_lock await_resume() const noexcept
{
return async_mutex_lock{ m_mutex, std::adopt_lock };
}
};
}
#endif
+102
View File
@@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
#define CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
#include <cppcoro/on_scope_exit.hpp>
#include <atomic>
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <cassert>
namespace cppcoro
{
class async_scope
{
public:
async_scope() noexcept
: m_count(1u)
{}
~async_scope()
{
// scope must be co_awaited before it destructs.
assert(m_continuation);
}
template<typename AWAITABLE>
void spawn(AWAITABLE&& awaitable)
{
[](async_scope* scope, std::decay_t<AWAITABLE> awaitable) -> oneway_task
{
scope->on_work_started();
auto decrementOnCompletion = on_scope_exit([scope] { scope->on_work_finished(); });
co_await std::move(awaitable);
}(this, std::forward<AWAITABLE>(awaitable));
}
[[nodiscard]] auto join() noexcept
{
class awaiter
{
async_scope* m_scope;
public:
awaiter(async_scope* scope) noexcept : m_scope(scope) {}
bool await_ready() noexcept
{
return m_scope->m_count.load(std::memory_order_acquire) == 0;
}
bool await_suspend(cppcoro::coroutine_handle<> continuation) noexcept
{
m_scope->m_continuation = continuation;
return m_scope->m_count.fetch_sub(1u, std::memory_order_acq_rel) > 1u;
}
void await_resume() noexcept
{}
};
return awaiter{ this };
}
private:
void on_work_finished() noexcept
{
if (m_count.fetch_sub(1u, std::memory_order_acq_rel) == 1)
{
m_continuation.resume();
}
}
void on_work_started() noexcept
{
assert(m_count.load(std::memory_order_relaxed) != 0);
m_count.fetch_add(1, std::memory_order_relaxed);
}
struct oneway_task
{
struct promise_type
{
cppcoro::suspend_never initial_suspend() noexcept { return {}; }
cppcoro::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
oneway_task get_return_object() { return {}; }
void return_void() {}
};
};
std::atomic<size_t> m_count;
cppcoro::coroutine_handle<> m_continuation;
};
}
#endif
+27
View File
@@ -0,0 +1,27 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
#define CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
#include <cppcoro/detail/get_awaiter.hpp>
#include <type_traits>
namespace cppcoro
{
template<typename T, typename = void>
struct awaitable_traits
{};
template<typename T>
struct awaitable_traits<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
{
using awaiter_t = decltype(cppcoro::detail::get_awaiter(std::declval<T>()));
using await_result_t = decltype(std::declval<awaiter_t>().await_resume());
};
}
#endif
+24
View File
@@ -0,0 +1,24 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
#define CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
#include <stdexcept>
namespace cppcoro
{
/// \brief
/// Exception thrown when you attempt to retrieve the result of
/// a task that has been detached from its promise/coroutine.
class broken_promise : public std::logic_error
{
public:
broken_promise()
: std::logic_error("broken promise")
{}
};
}
#endif
@@ -0,0 +1,87 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
#define CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
#include <cppcoro/cancellation_token.hpp>
#include <functional>
#include <utility>
#include <type_traits>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
namespace detail
{
class cancellation_state;
struct cancellation_registration_list_chunk;
struct cancellation_registration_state;
}
class cancellation_registration
{
public:
/// Registers the callback to be executed when cancellation is requested
/// on the cancellation_token.
///
/// The callback will be executed if cancellation is requested for the
/// specified cancellation token. If cancellation has already been requested
/// then the callback will be executed immediately, before the constructor
/// returns. If cancellation has not yet been requested then the callback
/// will be executed on the first thread to request cancellation inside
/// the call to cancellation_source::request_cancellation().
///
/// \param token
/// The cancellation token to register the callback with.
///
/// \param callback
/// The callback to be executed when cancellation is requested on the
/// the cancellation_token. Note that callback must not throw an exception
/// if called when cancellation is requested otherwise std::terminate()
/// will be called.
///
/// \throw std::bad_alloc
/// If registration failed due to insufficient memory available.
template<
typename FUNC,
typename = std::enable_if_t<std::is_constructible_v<std::function<void()>, FUNC&&>>>
cancellation_registration(cancellation_token token, FUNC&& callback)
: m_callback(std::forward<FUNC>(callback))
{
register_callback(std::move(token));
}
cancellation_registration(const cancellation_registration& other) = delete;
cancellation_registration& operator=(const cancellation_registration& other) = delete;
/// Deregisters the callback.
///
/// After the destructor returns it is guaranteed that the callback
/// will not be subsequently called during a call to request_cancellation()
/// on the cancellation_source.
///
/// This may block if cancellation has been requested on another thread
/// is it will need to wait until this callback has finished executing
/// before the callback can be destroyed.
~cancellation_registration();
private:
friend class detail::cancellation_state;
friend struct detail::cancellation_registration_state;
void register_callback(cancellation_token&& token);
detail::cancellation_state* m_state;
std::function<void()> m_callback;
detail::cancellation_registration_list_chunk* m_chunk;
std::uint32_t m_entryIndex;
};
}
#endif
+71
View File
@@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
#define CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
namespace cppcoro
{
class cancellation_token;
namespace detail
{
class cancellation_state;
}
class cancellation_source
{
public:
/// Construct to a new cancellation source.
cancellation_source();
/// Create a new reference to the same underlying cancellation
/// source as \p other.
cancellation_source(const cancellation_source& other) noexcept;
cancellation_source(cancellation_source&& other) noexcept;
~cancellation_source();
cancellation_source& operator=(const cancellation_source& other) noexcept;
cancellation_source& operator=(cancellation_source&& other) noexcept;
/// Query if this cancellation source can be cancelled.
///
/// A cancellation source object will not be cancellable if it has
/// previously been moved into another cancellation_source instance
/// or was copied from a cancellation_source that was not cancellable.
bool can_be_cancelled() const noexcept;
/// Obtain a cancellation token that can be used to query if
/// cancellation has been requested on this source.
///
/// The cancellation token can be passed into functions that you
/// may want to later be able to request cancellation.
cancellation_token token() const noexcept;
/// Request cancellation of operations that were passed an associated
/// cancellation token.
///
/// Any cancellation callback registered via a cancellation_registration
/// object will be called inside this function by the first thread to
/// call this method.
///
/// This operation is a no-op if can_be_cancelled() returns false.
void request_cancellation();
/// Query if some thread has called 'request_cancellation()' on this
/// cancellation_source.
bool is_cancellation_requested() const noexcept;
private:
detail::cancellation_state* m_state;
};
}
#endif
+72
View File
@@ -0,0 +1,72 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
#define CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
namespace cppcoro
{
class cancellation_source;
class cancellation_registration;
namespace detail
{
class cancellation_state;
}
class cancellation_token
{
public:
/// Construct to a cancellation token that can't be cancelled.
cancellation_token() noexcept;
/// Copy another cancellation token.
///
/// New token will refer to the same underlying state.
cancellation_token(const cancellation_token& other) noexcept;
cancellation_token(cancellation_token&& other) noexcept;
~cancellation_token();
cancellation_token& operator=(const cancellation_token& other) noexcept;
cancellation_token& operator=(cancellation_token&& other) noexcept;
void swap(cancellation_token& other) noexcept;
/// Query if it is possible that this operation will be cancelled
/// or not.
///
/// Cancellable operations may be able to take more efficient code-paths
/// if they don't need to handle cancellation requests.
bool can_be_cancelled() const noexcept;
/// Query if some thread has requested cancellation on an associated
/// cancellation_source object.
bool is_cancellation_requested() const noexcept;
/// Throws cppcoro::operation_cancelled exception if cancellation
/// has been requested for the associated operation.
void throw_if_cancellation_requested() const;
private:
friend class cancellation_source;
friend class cancellation_registration;
cancellation_token(detail::cancellation_state* state) noexcept;
detail::cancellation_state* m_state;
};
inline void swap(cancellation_token& a, cancellation_token& b) noexcept
{
a.swap(b);
}
}
#endif
+166
View File
@@ -0,0 +1,166 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CONFIG_HPP_INCLUDED
#define CPPCORO_CONFIG_HPP_INCLUDED
/////////////////////////////////////////////////////////////////////////////
// Compiler Detection
#if defined(_MSC_VER)
# define CPPCORO_COMPILER_MSVC _MSC_FULL_VER
#else
# define CPPCORO_COMPILER_MSVC 0
#endif
#if defined(__clang__)
# define CPPCORO_COMPILER_CLANG (__clang_major__ * 10000 + \
__clang_minor__ * 100 + \
__clang_patchlevel__)
#else
# define CPPCORO_COMPILER_CLANG 0
#endif
#if defined(__GNUC__)
# define CPPCORO_COMPILER_GCC (__GNUC__ * 10000 + \
__GNUC_MINOR__ * 100 + \
__GNUC_PATCHLEVEL__)
#else
# define CPPCORO_COMPILER_GCC 0
#endif
/// \def CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
/// Defined to 1 if the compiler supports returning a coroutine_handle from
/// the await_suspend() method as a way of transferring execution
/// to another coroutine with a guaranteed tail-call.
#if CPPCORO_COMPILER_CLANG
# if __clang_major__ >= 7
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 1
# endif
#endif
#ifndef CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 0
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_ASSUME(X) __assume(X)
#else
# define CPPCORO_ASSUME(X)
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_NOINLINE __declspec(noinline)
#elif CPPCORO_COMPILER_CLANG || CPPCORO_COMPILER_GCC
# define CPPCORO_NOINLINE __attribute__((noinline))
#else
# define CPPCORO_NOINLINE
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_FORCE_INLINE __forceinline
#elif CPPCORO_COMPILER_CLANG
# define CPPCORO_FORCE_INLINE __attribute__((always_inline))
#else
# define CPPCORO_FORCE_INLINE inline
#endif
/////////////////////////////////////////////////////////////////////////////
// OS Detection
/// \def CPPCORO_OS_WINNT
/// Defined to non-zero if the target platform is a WindowsNT variant.
/// 0x0500 - Windows 2000
/// 0x0501 - Windows XP/Server 2003
/// 0x0502 - Windows XP SP2/Server 2003 SP1
/// 0x0600 - Windows Vista/Server 2008
/// 0x0601 - Windows 7
/// 0x0602 - Windows 8
/// 0x0603 - Windows 8.1
/// 0x0A00 - Windows 10
#if defined(_WIN32_WINNT) || defined(_WIN32)
# if !defined(_WIN32_WINNT)
// Default to targeting Windows 10 if not defined.
# define _WIN32_WINNT 0x0A00
# endif
# define CPPCORO_OS_WINNT _WIN32_WINNT
#else
# define CPPCORO_OS_WINNT 0
#endif
#if defined(__linux__)
# define CPPCORO_OS_LINUX 1
#else
# define CPPCORO_OS_LINUX 0
#endif
/////////////////////////////////////////////////////////////////////////////
// CPU Detection
/// \def CPPCORO_CPU_X86
/// Defined to 1 if target CPU is of x86 family.
#if CPPCORO_COMPILER_MSVC
# if defined(_M_IX86)
# define CPPCORO_CPU_X86 1
# endif
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
# if defined(__i386__)
# define CPPCORO_CPU_X86 1
# endif
#endif
#if !defined(CPPCORO_CPU_X86)
# define CPPCORO_CPU_X86 0
#endif
/// \def CPPCORO_CPU_X64
/// Defined to 1 if the target CPU is x64 family.
#if CPPCORO_COMPILER_MSVC
# if defined(_M_X64)
# define CPPCORO_CPU_X64 1
# endif
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
# if defined(__x86_64__)
# define CPPCORO_CPU_X64 1
# endif
#endif
#if !defined(CPPCORO_CPU_X64)
# define CPPCORO_CPU_X64 0
#endif
/// \def CPPCORO_CPU_32BIT
/// Defined if compiling for a 32-bit CPU architecture.
#if CPPCORO_CPU_X86
# define CPPCORO_CPU_32BIT 1
#else
# define CPPCORO_CPU_32BIT 0
#endif
/// \def CPPCORO_CPU_64BIT
/// Defined if compiling for a 64-bit CPU architecture.
#if CPPCORO_CPU_X64
# define CPPCORO_CPU_64BIT 1
#else
# define CPPCORO_CPU_64BIT 0
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_CPU_CACHE_LINE std::hardware_destructive_interference_size
#else
// On most architectures we can assume a 64-byte cache line.
# define CPPCORO_CPU_CACHE_LINE 64
#endif
#if CPPCORO_COMPILER_MSVC
#if __has_include(<coroutine>)
#include <yvals_core.h>
#ifdef __cpp_lib_coroutine
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
#endif
#endif
#else
#if __has_include(<coroutine>)
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
#endif
#endif
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef CPPCORO_COROUTINE_HPP_INCLUDED
#define CPPCORO_COROUTINE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#ifdef CPPCORO_COROHEADER_FOUND_AND_USABLE
#include <coroutine>
namespace cppcoro {
using std::coroutine_handle;
using std::suspend_always;
using std::noop_coroutine;
using std::suspend_never;
}
#elif __has_include(<experimental/coroutine>)
#include <experimental/coroutine>
namespace cppcoro {
using std::experimental::coroutine_handle;
using std::experimental::suspend_always;
using std::experimental::suspend_never;
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
using std::experimental::noop_coroutine;
#endif
}
#else
#error Cppcoro requires a C++20 compiler with coroutine support
#endif
#endif
+22
View File
@@ -0,0 +1,22 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_ANY_HPP_INCLUDED
#define CPPCORO_DETAIL_ANY_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
// Helper type that can be cast-to from any type.
struct any
{
template<typename T>
any(T&&) noexcept
{}
};
}
}
#endif
+49
View File
@@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
#define CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
#include <cppcoro/detail/is_awaiter.hpp>
#include <cppcoro/detail/any.hpp>
namespace cppcoro
{
namespace detail
{
template<typename T>
auto get_awaiter_impl(T&& value, int)
noexcept(noexcept(static_cast<T&&>(value).operator co_await()))
-> decltype(static_cast<T&&>(value).operator co_await())
{
return static_cast<T&&>(value).operator co_await();
}
template<typename T>
auto get_awaiter_impl(T&& value, long)
noexcept(noexcept(operator co_await(static_cast<T&&>(value))))
-> decltype(operator co_await(static_cast<T&&>(value)))
{
return operator co_await(static_cast<T&&>(value));
}
template<
typename T,
std::enable_if_t<cppcoro::detail::is_awaiter<T&&>::value, int> = 0>
T&& get_awaiter_impl(T&& value, cppcoro::detail::any) noexcept
{
return static_cast<T&&>(value);
}
template<typename T>
auto get_awaiter(T&& value)
noexcept(noexcept(detail::get_awaiter_impl(static_cast<T&&>(value), 123)))
-> decltype(detail::get_awaiter_impl(static_cast<T&&>(value), 123))
{
return detail::get_awaiter_impl(static_cast<T&&>(value), 123);
}
}
}
#endif
+55
View File
@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
#define CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
#include <type_traits>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
namespace detail
{
template<typename T>
struct is_coroutine_handle
: std::false_type
{};
template<typename PROMISE>
struct is_coroutine_handle<cppcoro::coroutine_handle<PROMISE>>
: std::true_type
{};
// NOTE: We're accepting a return value of coroutine_handle<P> here
// which is an extension supported by Clang which is not yet part of
// the C++ coroutines TS.
template<typename T>
struct is_valid_await_suspend_return_value : std::disjunction<
std::is_void<T>,
std::is_same<T, bool>,
is_coroutine_handle<T>>
{};
template<typename T, typename = std::void_t<>>
struct is_awaiter : std::false_type {};
// NOTE: We're testing whether await_suspend() will be callable using an
// arbitrary coroutine_handle here by checking if it supports being passed
// a coroutine_handle<void>. This may result in a false-result for some
// types which are only awaitable within a certain context.
template<typename T>
struct is_awaiter<T, std::void_t<
decltype(std::declval<T>().await_ready()),
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>())),
decltype(std::declval<T>().await_resume())>> :
std::conjunction<
std::is_constructible<bool, decltype(std::declval<T>().await_ready())>,
detail::is_valid_await_suspend_return_value<
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>()))>>
{};
}
}
#endif
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
#include <chrono>
#include <span>
#include <cppcoro/config.hpp>
#if CPPCORO_OS_LINUX || (CPPCORO_OS_WINNT >= 0x0602)
# include <atomic>
# include <cstdint>
#elif CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#else
# include <mutex>
# include <condition_variable>
#endif
namespace cppcoro
{
class io_service;
namespace detail
{
class lightweight_manual_reset_event
{
public:
lightweight_manual_reset_event(bool initiallySet = false);
~lightweight_manual_reset_event();
void set() noexcept;
void reset() noexcept;
void wait() noexcept;
void wait(std::span<io_service> srvs, std::chrono::system_clock::duration step) noexcept;
private:
#if CPPCORO_OS_LINUX
std::atomic<int> m_value;
#elif CPPCORO_OS_WINNT >= 0x0602
// Windows 8 or newer we can use WaitOnAddress()
std::atomic<std::uint8_t> m_value;
#elif CPPCORO_OS_WINNT
// Before Windows 8 we need to use a WIN32 manual reset event.
cppcoro::detail::win32::handle_t m_eventHandle;
#else
// For other platforms that don't have a native futex
// or manual reset event we can just use a std::mutex
// and std::condition_variable to perform the wait.
// Not so lightweight, but should be portable to all platforms.
std::mutex m_mutex;
std::condition_variable m_cv;
bool m_isSet;
#endif
};
}
}
#endif
+120
View File
@@ -0,0 +1,120 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
#define CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
#include <type_traits>
#include <memory>
namespace cppcoro::detail
{
template<typename T>
struct manual_lifetime
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
template<typename... Args>
std::enable_if_t<std::is_constructible_v<T, Args&&...>> construct(Args&&... args)
noexcept(std::is_nothrow_constructible_v<T, Args&&...>)
{
::new (static_cast<void*>(std::addressof(m_value))) T(static_cast<Args&&>(args)...);
}
void destruct() noexcept(std::is_nothrow_destructible_v<T>)
{
m_value.~T();
}
std::add_pointer_t<T> operator->() noexcept { return std::addressof(**this); }
std::add_pointer_t<const T> operator->() const noexcept { return std::addressof(**this); }
T& operator*() & noexcept { return m_value; }
const T& operator*() const & noexcept { return m_value; }
T&& operator*() && noexcept { return static_cast<T&&>(m_value); }
const T&& operator*() const && noexcept { return static_cast<const T&&>(m_value); }
private:
union {
T m_value;
};
};
template<typename T>
struct manual_lifetime<T&>
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
void construct(T& value) noexcept
{
m_value = std::addressof(value);
}
void destruct() noexcept {}
T* operator->() noexcept { return m_value; }
const T* operator->() const noexcept { return m_value; }
T& operator*() noexcept { return *m_value; }
const T& operator*() const noexcept { return *m_value; }
private:
T* m_value;
};
template<typename T>
struct manual_lifetime<T&&>
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
void construct(T&& value) noexcept
{
m_value = std::addressof(value);
}
void destruct() noexcept {}
T* operator->() noexcept { return m_value; }
const T* operator->() const noexcept { return m_value; }
T& operator*() & noexcept { return *m_value; }
const T& operator*() const & noexcept { return *m_value; }
T&& operator*() && noexcept { return static_cast<T&&>(*m_value); }
const T&& operator*() const && noexcept { return static_cast<const T&&>(*m_value); }
private:
T* m_value;
};
template<>
struct manual_lifetime<void>
{
void construct() noexcept {}
void destruct() noexcept {}
void operator*() const noexcept {}
};
}
#endif
@@ -0,0 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
#define CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
template<typename T>
struct remove_rvalue_reference
{
using type = T;
};
template<typename T>
struct remove_rvalue_reference<T&&>
{
using type = T;
};
template<typename T>
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;
}
}
#endif
+300
View File
@@ -0,0 +1,300 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
#define CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
#include <cppcoro/coroutine.hpp>
#include <cassert>
#include <exception>
#include <utility>
namespace cppcoro
{
namespace detail
{
template<typename RESULT>
class sync_wait_task;
template<typename RESULT>
class sync_wait_task_promise final
{
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<RESULT>>;
public:
using reference = RESULT&&;
sync_wait_task_promise() noexcept
{}
void start(detail::lightweight_manual_reset_event& event)
{
m_event = &event;
coroutine_handle_t::from_promise(*this).resume();
}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coroutine) const noexcept
{
coroutine.promise().m_event->set();
}
void await_resume() noexcept {}
};
return completion_notifier{};
}
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
// See comment in make_sync_wait_task below.
template<typename Awaitable>
Awaitable&& await_transform(Awaitable&& awaitable)
{
return static_cast<Awaitable&&>(awaitable);
}
struct get_promise_t {};
static constexpr get_promise_t get_promise = {};
auto await_transform(get_promise_t)
{
class awaiter
{
public:
awaiter(sync_wait_task_promise* promise) noexcept : m_promise(promise) {}
bool await_ready() noexcept {
return true;
}
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
sync_wait_task_promise& await_resume() noexcept
{
return *m_promise;
}
private:
sync_wait_task_promise* m_promise;
};
return awaiter{ this };
}
#endif
auto yield_value(reference result) noexcept
{
m_result = std::addressof(result);
return final_suspend();
}
void return_void() noexcept
{
// The coroutine should have either yielded a value or thrown
// an exception in which case it should have bypassed return_void().
assert(false);
}
void unhandled_exception()
{
m_exception = std::current_exception();
}
reference result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
return static_cast<reference>(*m_result);
}
private:
detail::lightweight_manual_reset_event* m_event;
std::remove_reference_t<RESULT>* m_result;
std::exception_ptr m_exception;
};
template<>
class sync_wait_task_promise<void>
{
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<void>>;
public:
sync_wait_task_promise() noexcept
{}
void start(detail::lightweight_manual_reset_event& event)
{
m_event = &event;
coroutine_handle_t::from_promise(*this).resume();
}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coroutine) const noexcept
{
coroutine.promise().m_event->set();
}
void await_resume() noexcept {}
};
return completion_notifier{};
}
void return_void() {}
void unhandled_exception()
{
m_exception = std::current_exception();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
detail::lightweight_manual_reset_event* m_event;
std::exception_ptr m_exception;
};
template<typename RESULT>
class sync_wait_task final
{
public:
using promise_type = sync_wait_task_promise<RESULT>;
using coroutine_handle_t = cppcoro::coroutine_handle<promise_type>;
sync_wait_task(coroutine_handle_t coroutine) noexcept
: m_coroutine(coroutine)
{}
sync_wait_task(sync_wait_task&& other) noexcept
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
{}
~sync_wait_task()
{
if (m_coroutine) m_coroutine.destroy();
}
sync_wait_task(const sync_wait_task&) = delete;
sync_wait_task& operator=(const sync_wait_task&) = delete;
void start(lightweight_manual_reset_event& event) noexcept
{
m_coroutine.promise().start(event);
}
decltype(auto) result()
{
return m_coroutine.promise().result();
}
private:
coroutine_handle_t m_coroutine;
};
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Work around bug in MSVC where passing a parameter by universal reference
// results in an error when passed a move-only type, complaining that the copy-constructor
// has been deleted. The parameter should be passed by reference and the compiler should
// notcalling the copy-constructor for the argument
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
{
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield an never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await sync_wait_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
//co_yield co_await std::forward<AWAITABLE>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
#else
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE&& awaitable)
{
co_yield co_await std::forward<AWAITABLE>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
sync_wait_task<void> make_sync_wait_task(AWAITABLE&& awaitable)
{
co_await std::forward<AWAITABLE>(awaitable);
}
#endif
}
}
#endif
@@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
#define CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
#include <functional>
namespace cppcoro
{
namespace detail
{
template<typename T>
struct unwrap_reference
{
using type = T;
};
template<typename T>
struct unwrap_reference<std::reference_wrapper<T>>
{
using type = T;
};
template<typename T>
using unwrap_reference_t = typename unwrap_reference<T>::type;
}
}
#endif
+16
View File
@@ -0,0 +1,16 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
#define CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
struct void_value {};
}
}
#endif
@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
namespace detail
{
class when_all_counter
{
public:
when_all_counter(std::size_t count) noexcept
: m_count(count + 1)
, m_awaitingCoroutine(nullptr)
{}
bool is_ready() const noexcept
{
// We consider this complete if we're asking whether it's ready
// after a coroutine has already been registered.
return static_cast<bool>(m_awaitingCoroutine);
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1;
}
void notify_awaitable_completed() noexcept
{
if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1)
{
m_awaitingCoroutine.resume();
}
}
protected:
std::atomic<std::size_t> m_count;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
}
}
#endif
@@ -0,0 +1,258 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
#include <cppcoro/detail/when_all_counter.hpp>
#include <cppcoro/coroutine.hpp>
#include <tuple>
namespace cppcoro
{
namespace detail
{
template<typename TASK_CONTAINER>
class when_all_ready_awaitable;
template<>
class when_all_ready_awaitable<std::tuple<>>
{
public:
constexpr when_all_ready_awaitable() noexcept {}
explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {}
constexpr bool await_ready() const noexcept { return true; }
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
std::tuple<> await_resume() const noexcept { return {}; }
};
template<typename... TASKS>
class when_all_ready_awaitable<std::tuple<TASKS...>>
{
public:
explicit when_all_ready_awaitable(TASKS&&... tasks)
noexcept(std::conjunction_v<std::is_nothrow_move_constructible<TASKS>...>)
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(tasks)...)
{}
explicit when_all_ready_awaitable(std::tuple<TASKS...>&& tasks)
noexcept(std::is_nothrow_move_constructible_v<std::tuple<TASKS...>>)
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(tasks))
{}
when_all_ready_awaitable(when_all_ready_awaitable&& other) noexcept
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(other.m_tasks))
{}
auto operator co_await() & noexcept
{
struct awaiter
{
awaiter(when_all_ready_awaitable& awaitable) noexcept
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
std::tuple<TASKS...>& await_resume() noexcept
{
return m_awaitable.m_tasks;
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
auto operator co_await() && noexcept
{
struct awaiter
{
awaiter(when_all_ready_awaitable& awaitable) noexcept
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
std::tuple<TASKS...>&& await_resume() noexcept
{
return std::move(m_awaitable.m_tasks);
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
private:
bool is_ready() const noexcept
{
return m_counter.is_ready();
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
start_tasks(std::make_integer_sequence<std::size_t, sizeof...(TASKS)>{});
return m_counter.try_await(awaitingCoroutine);
}
template<std::size_t... INDICES>
void start_tasks(std::integer_sequence<std::size_t, INDICES...>) noexcept
{
(void)std::initializer_list<int>{
(std::get<INDICES>(m_tasks).start(m_counter), 0)...
};
}
when_all_counter m_counter;
std::tuple<TASKS...> m_tasks;
};
template<typename TASK_CONTAINER>
class when_all_ready_awaitable
{
public:
explicit when_all_ready_awaitable(TASK_CONTAINER&& tasks) noexcept
: m_counter(tasks.size())
, m_tasks(std::forward<TASK_CONTAINER>(tasks))
{}
when_all_ready_awaitable(when_all_ready_awaitable&& other)
noexcept(std::is_nothrow_move_constructible_v<TASK_CONTAINER>)
: m_counter(other.m_tasks.size())
, m_tasks(std::move(other.m_tasks))
{}
when_all_ready_awaitable(const when_all_ready_awaitable&) = delete;
when_all_ready_awaitable& operator=(const when_all_ready_awaitable&) = delete;
auto operator co_await() & noexcept
{
class awaiter
{
public:
awaiter(when_all_ready_awaitable& awaitable)
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
TASK_CONTAINER& await_resume() noexcept
{
return m_awaitable.m_tasks;
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
auto operator co_await() && noexcept
{
class awaiter
{
public:
awaiter(when_all_ready_awaitable& awaitable)
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
TASK_CONTAINER&& await_resume() noexcept
{
return std::move(m_awaitable.m_tasks);
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
private:
bool is_ready() const noexcept
{
return m_counter.is_ready();
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
for (auto&& task : m_tasks)
{
task.start(m_counter);
}
return m_counter.try_await(awaitingCoroutine);
}
when_all_counter m_counter;
TASK_CONTAINER m_tasks;
};
}
}
#endif
+357
View File
@@ -0,0 +1,357 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/when_all_counter.hpp>
#include <cppcoro/detail/void_value.hpp>
#include <cppcoro/coroutine.hpp>
#include <cassert>
#include <exception>
namespace cppcoro
{
namespace detail
{
template<typename TASK_CONTAINER>
class when_all_ready_awaitable;
template<typename RESULT>
class when_all_task;
template<typename RESULT>
class when_all_task_promise final
{
public:
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<RESULT>>;
when_all_task_promise() noexcept
{}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coro) const noexcept
{
coro.promise().m_counter->notify_awaitable_completed();
}
void await_resume() const noexcept {}
};
return completion_notifier{};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept
{
// We should have either suspended at co_yield point or
// an exception was thrown before running off the end of
// the coroutine.
assert(false);
}
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
// See comment in make_when_all_task below.
template<typename Awaitable>
Awaitable&& await_transform(Awaitable&& awaitable)
{
return static_cast<Awaitable&&>(awaitable);
}
struct get_promise_t {};
static constexpr get_promise_t get_promise = {};
auto await_transform(get_promise_t)
{
class awaiter
{
public:
awaiter(when_all_task_promise* promise) noexcept : m_promise(promise) {}
bool await_ready() noexcept {
return true;
}
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
when_all_task_promise& await_resume() noexcept
{
return *m_promise;
}
private:
when_all_task_promise* m_promise;
};
return awaiter{ this };
}
#endif
auto yield_value(RESULT&& result) noexcept
{
m_result = std::addressof(result);
return final_suspend();
}
void start(when_all_counter& counter) noexcept
{
m_counter = &counter;
coroutine_handle_t::from_promise(*this).resume();
}
RESULT& result() &
{
rethrow_if_exception();
return *m_result;
}
RESULT&& result() &&
{
rethrow_if_exception();
return std::forward<RESULT>(*m_result);
}
private:
void rethrow_if_exception()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
when_all_counter* m_counter;
std::exception_ptr m_exception;
std::add_pointer_t<RESULT> m_result;
};
template<>
class when_all_task_promise<void> final
{
public:
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<void>>;
when_all_task_promise() noexcept
{}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coro) const noexcept
{
coro.promise().m_counter->notify_awaitable_completed();
}
void await_resume() const noexcept {}
};
return completion_notifier{};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept
{
}
void start(when_all_counter& counter) noexcept
{
m_counter = &counter;
coroutine_handle_t::from_promise(*this).resume();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
when_all_counter* m_counter;
std::exception_ptr m_exception;
};
template<typename RESULT>
class when_all_task final
{
public:
using promise_type = when_all_task_promise<RESULT>;
using coroutine_handle_t = typename promise_type::coroutine_handle_t;
when_all_task(coroutine_handle_t coroutine) noexcept
: m_coroutine(coroutine)
{}
when_all_task(when_all_task&& other) noexcept
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
{}
~when_all_task()
{
if (m_coroutine) m_coroutine.destroy();
}
when_all_task(const when_all_task&) = delete;
when_all_task& operator=(const when_all_task&) = delete;
decltype(auto) result() &
{
return m_coroutine.promise().result();
}
decltype(auto) result() &&
{
return std::move(m_coroutine.promise()).result();
}
decltype(auto) non_void_result() &
{
if constexpr (std::is_void_v<decltype(this->result())>)
{
this->result();
return void_value{};
}
else
{
return this->result();
}
}
decltype(auto) non_void_result() &&
{
if constexpr (std::is_void_v<decltype(this->result())>)
{
std::move(*this).result();
return void_value{};
}
else
{
return std::move(*this).result();
}
}
private:
template<typename TASK_CONTAINER>
friend class when_all_ready_awaitable;
void start(when_all_counter& counter) noexcept
{
m_coroutine.promise().start(counter);
}
coroutine_handle_t m_coroutine;
};
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
{
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield an never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
#else
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
#endif
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
when_all_task<void> make_when_all_task(AWAITABLE awaitable)
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
{
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield and never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await awaitable.get());
#else
co_yield co_await awaitable.get();
#endif
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
when_all_task<void> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
{
co_await awaitable.get();
}
}
}
#endif
+179
View File
@@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WIN32_HPP_INCLUDED
#define CPPCORO_DETAIL_WIN32_HPP_INCLUDED
#include <cppcoro/config.hpp>
#if !CPPCORO_OS_WINNT
# error <cppcoro/detail/win32.hpp> is only supported on the Windows platform.
#endif
#include <utility>
#include <cstdint>
struct _OVERLAPPED;
namespace cppcoro
{
namespace detail
{
namespace win32
{
using handle_t = void*;
using ulongptr_t = std::uintptr_t;
using longptr_t = std::intptr_t;
using dword_t = unsigned long;
using socket_t = std::uintptr_t;
using ulong_t = unsigned long;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4201) // Non-standard anonymous struct/union
#endif
/// Structure needs to correspond exactly to the builtin
/// _OVERLAPPED structure from Windows.h.
struct overlapped
{
ulongptr_t Internal;
ulongptr_t InternalHigh;
union
{
struct
{
dword_t Offset;
dword_t OffsetHigh;
};
void* Pointer;
};
handle_t hEvent;
};
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
struct wsabuf
{
constexpr wsabuf() noexcept
: len(0)
, buf(nullptr)
{}
constexpr wsabuf(void* ptr, std::size_t size)
: len(size <= ulong_t(-1) ? ulong_t(size) : ulong_t(-1))
, buf(static_cast<char*>(ptr))
{}
ulong_t len;
char* buf;
};
struct io_state : win32::overlapped
{
using callback_type = void(
io_state* state,
win32::dword_t errorCode,
win32::dword_t numberOfBytesTransferred,
win32::ulongptr_t completionKey);
io_state(callback_type* callback = nullptr) noexcept
: io_state(std::uint64_t(0), callback)
{}
io_state(void* pointer, callback_type* callback) noexcept
: m_callback(callback)
{
this->Internal = 0;
this->InternalHigh = 0;
this->Pointer = pointer;
this->hEvent = nullptr;
}
io_state(std::uint64_t offset, callback_type* callback) noexcept
: m_callback(callback)
{
this->Internal = 0;
this->InternalHigh = 0;
this->Offset = static_cast<dword_t>(offset);
this->OffsetHigh = static_cast<dword_t>(offset >> 32);
this->hEvent = nullptr;
}
callback_type* m_callback;
};
class safe_handle
{
public:
safe_handle()
: m_handle(nullptr)
{}
explicit safe_handle(handle_t handle)
: m_handle(handle)
{}
safe_handle(const safe_handle& other) = delete;
safe_handle(safe_handle&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
~safe_handle()
{
close();
}
safe_handle& operator=(safe_handle handle) noexcept
{
swap(handle);
return *this;
}
constexpr handle_t handle() const { return m_handle; }
/// Calls CloseHandle() and sets the handle to NULL.
void close() noexcept;
void swap(safe_handle& other) noexcept
{
std::swap(m_handle, other.m_handle);
}
bool operator==(const safe_handle& other) const
{
return m_handle == other.m_handle;
}
bool operator!=(const safe_handle& other) const
{
return m_handle != other.m_handle;
}
bool operator==(handle_t handle) const
{
return m_handle == handle;
}
bool operator!=(handle_t handle) const
{
return m_handle != handle;
}
private:
handle_t m_handle;
};
}
}
}
#endif
@@ -0,0 +1,376 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
#define CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/operation_cancelled.hpp>
#include <cppcoro/detail/win32.hpp>
#include <optional>
#include <system_error>
#include <cppcoro/coroutine.hpp>
#include <cassert>
namespace cppcoro
{
namespace detail
{
class win32_overlapped_operation_base
: protected detail::win32::io_state
{
public:
win32_overlapped_operation_base(
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
win32_overlapped_operation_base(
void* pointer,
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(pointer, callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
win32_overlapped_operation_base(
std::uint64_t offset,
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(offset, callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
_OVERLAPPED* get_overlapped() noexcept
{
return reinterpret_cast<_OVERLAPPED*>(
static_cast<detail::win32::overlapped*>(this));
}
std::size_t get_result()
{
if (m_errorCode != 0)
{
throw std::system_error{
static_cast<int>(m_errorCode),
std::system_category()
};
}
return m_numberOfBytesTransferred;
}
detail::win32::dword_t m_errorCode;
detail::win32::dword_t m_numberOfBytesTransferred;
};
template<typename OPERATION>
class win32_overlapped_operation
: protected win32_overlapped_operation_base
{
protected:
win32_overlapped_operation() noexcept
: win32_overlapped_operation_base(
&win32_overlapped_operation::on_operation_completed)
{}
win32_overlapped_operation(void* pointer) noexcept
: win32_overlapped_operation_base(
pointer,
&win32_overlapped_operation::on_operation_completed)
{}
win32_overlapped_operation(std::uint64_t offset) noexcept
: win32_overlapped_operation_base(
offset,
&win32_overlapped_operation::on_operation_completed)
{}
public:
bool await_ready() const noexcept { return false; }
CPPCORO_NOINLINE
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
{
static_assert(std::is_base_of_v<win32_overlapped_operation, OPERATION>);
m_awaitingCoroutine = awaitingCoroutine;
return static_cast<OPERATION*>(this)->try_start();
}
decltype(auto) await_resume()
{
return static_cast<OPERATION*>(this)->get_result();
}
private:
static void on_operation_completed(
detail::win32::io_state* ioState,
detail::win32::dword_t errorCode,
detail::win32::dword_t numberOfBytesTransferred,
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
{
auto* operation = static_cast<win32_overlapped_operation*>(ioState);
operation->m_errorCode = errorCode;
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
operation->m_awaitingCoroutine.resume();
}
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
template<typename OPERATION>
class win32_overlapped_operation_cancellable
: protected win32_overlapped_operation_base
{
// ERROR_OPERATION_ABORTED value from <Windows.h>
static constexpr detail::win32::dword_t error_operation_aborted = 995L;
protected:
win32_overlapped_operation_cancellable(cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(&win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
void* pointer,
cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(pointer, &win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
std::uint64_t offset,
cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(offset, &win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
win32_overlapped_operation_cancellable&& other) noexcept
: win32_overlapped_operation_base(std::move(other))
, m_state(other.m_state.load(std::memory_order_relaxed))
, m_cancellationToken(std::move(other.m_cancellationToken))
{
assert(m_errorCode == other.m_errorCode);
assert(m_numberOfBytesTransferred == other.m_numberOfBytesTransferred);
}
public:
bool await_ready() const noexcept
{
return m_state.load(std::memory_order_relaxed) == state::completed;
}
CPPCORO_NOINLINE
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
{
static_assert(std::is_base_of_v<win32_overlapped_operation_cancellable, OPERATION>);
m_awaitingCoroutine = awaitingCoroutine;
// TRICKY: Register cancellation callback before starting the operation
// in case the callback registration throws due to insufficient
// memory. We need to make sure that the logic that occurs after
// starting the operation is noexcept, otherwise we run into the
// problem of not being able to cancel the started operation and
// the dilemma of what to do with the exception.
//
// However, doing this means that the cancellation callback may run
// prior to returning below so in the case that cancellation may
// occur we defer setting the state to 'started' until after
// the operation has finished starting. The cancellation callback
// will only attempt to request cancellation of the operation with
// CancelIoEx() once the state has been set to 'started'.
const bool canBeCancelled = m_cancellationToken.can_be_cancelled();
if (canBeCancelled)
{
m_cancellationCallback.emplace(
std::move(m_cancellationToken),
[this] { this->on_cancellation_requested(); });
}
else
{
m_state.store(state::started, std::memory_order_relaxed);
}
// Now start the operation.
const bool willCompleteAsynchronously = static_cast<OPERATION*>(this)->try_start();
if (!willCompleteAsynchronously)
{
// Operation completed synchronously, resume awaiting coroutine immediately.
return false;
}
if (canBeCancelled)
{
// Need to flag that the operation has finished starting now.
// However, the operation may have completed concurrently on
// another thread, transitioning directly from not_started -> complete.
// Or it may have had the cancellation callback execute and transition
// from not_started -> cancellation_requested. We use a compare-exchange
// to determine a winner between these potential racing cases.
state oldState = state::not_started;
if (!m_state.compare_exchange_strong(
oldState,
state::started,
std::memory_order_release,
std::memory_order_acquire))
{
if (oldState == state::cancellation_requested)
{
// Request the operation be cancelled.
// Note that it may have already completed on a background
// thread by now so this request for cancellation may end up
// being ignored.
static_cast<OPERATION*>(this)->cancel();
if (!m_state.compare_exchange_strong(
oldState,
state::started,
std::memory_order_release,
std::memory_order_acquire))
{
assert(oldState == state::completed);
return false;
}
}
else
{
assert(oldState == state::completed);
return false;
}
}
}
return true;
}
decltype(auto) await_resume()
{
// Free memory used by the cancellation callback now that the operation
// has completed rather than waiting until the operation object destructs.
// eg. If the operation is passed to when_all() then the operation object
// may not be destructed until all of the operations complete.
m_cancellationCallback.reset();
if (m_errorCode == error_operation_aborted)
{
throw operation_cancelled{};
}
return static_cast<OPERATION*>(this)->get_result();
}
private:
enum class state
{
not_started,
started,
cancellation_requested,
completed
};
void on_cancellation_requested() noexcept
{
auto oldState = m_state.load(std::memory_order_acquire);
if (oldState == state::not_started)
{
// This callback is running concurrently with await_suspend().
// The call to start the operation may not have returned yet so
// we can't safely request cancellation of it. Instead we try to
// notify the await_suspend() thread by transitioning the state
// to state::cancellation_requested so that the await_suspend()
// thread can request cancellation after it has finished starting
// the operation.
const bool transferredCancelResponsibility =
m_state.compare_exchange_strong(
oldState,
state::cancellation_requested,
std::memory_order_release,
std::memory_order_acquire);
if (transferredCancelResponsibility)
{
return;
}
}
// No point requesting cancellation if the operation has already completed.
if (oldState != state::completed)
{
static_cast<OPERATION*>(this)->cancel();
}
}
static void on_operation_completed(
detail::win32::io_state* ioState,
detail::win32::dword_t errorCode,
detail::win32::dword_t numberOfBytesTransferred,
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
{
auto* operation = static_cast<win32_overlapped_operation_cancellable*>(ioState);
operation->m_errorCode = errorCode;
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
auto state = operation->m_state.load(std::memory_order_acquire);
if (state == state::started)
{
operation->m_state.store(state::completed, std::memory_order_relaxed);
operation->m_awaitingCoroutine.resume();
}
else
{
// We are racing with await_suspend() call suspending.
// Try to mark it as completed using an atomic exchange and look
// at the previous value to determine whether the coroutine suspended
// first (in which case we resume it now) or we marked it as completed
// first (in which case await_suspend() will return false and immediately
// resume the coroutine).
state = operation->m_state.exchange(
state::completed,
std::memory_order_acq_rel);
if (state == state::started)
{
// The await_suspend() method returned (or will return) 'true' and so
// we need to resume the coroutine.
operation->m_awaitingCoroutine.resume();
}
}
}
std::atomic<state> m_state;
cppcoro::cancellation_token m_cancellationToken;
std::optional<cppcoro::cancellation_registration> m_cancellationCallback;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
}
}
#endif
+54
View File
@@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_HPP_INCLUDED
#define CPPCORO_FILE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class io_service;
class file
{
public:
file(file&& other) noexcept = default;
virtual ~file();
/// Get the size of the file in bytes.
std::uint64_t size() const;
protected:
#if CPPCORO_OS_WINNT
file(detail::win32::safe_handle&& fileHandle) noexcept;
static detail::win32::safe_handle open(
detail::win32::dword_t fileAccess,
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode,
file_share_mode shareMode,
file_buffering_mode bufferingMode);
detail::win32::safe_handle m_fileHandle;
#endif
};
}
#endif
+32
View File
@@ -0,0 +1,32 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
#define CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_buffering_mode
{
default_ = 0,
sequential = 1,
random_access = 2,
unbuffered = 4,
write_through = 8,
temporary = 16
};
constexpr file_buffering_mode operator&(file_buffering_mode a, file_buffering_mode b)
{
return static_cast<file_buffering_mode>(
static_cast<int>(a) & static_cast<int>(b));
}
constexpr file_buffering_mode operator|(file_buffering_mode a, file_buffering_mode b)
{
return static_cast<file_buffering_mode>(static_cast<int>(a) | static_cast<int>(b));
}
}
#endif
+40
View File
@@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
#define CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_open_mode
{
/// Open an existing file.
///
/// If file does not already exist when opening the file then raises
/// an exception.
open_existing,
/// Create a new file, overwriting an existing file if one exists.
///
/// If a file exists at the path then it is overwitten with a new file.
/// If no file exists at the path then a new one is created.
create_always,
/// Create a new file.
///
/// If the file already exists then raises an exception.
create_new,
/// Open the existing file if one exists, otherwise create a new empty
/// file.
create_or_open,
/// Open the existing file, truncating the file size to zero.
///
/// If the file does not exist then raises an exception.
truncate_existing
};
}
#endif
+99
View File
@@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
#define CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <atomic>
#include <optional>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
class file_read_operation_impl
{
public:
file_read_operation_impl(
detail::win32::handle_t fileHandle,
void* buffer,
std::size_t byteCount) noexcept
: m_fileHandle(fileHandle)
, m_buffer(buffer)
, m_byteCount(byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
detail::win32::handle_t m_fileHandle;
void* m_buffer;
std::size_t m_byteCount;
};
class file_read_operation
: public cppcoro::detail::win32_overlapped_operation<file_read_operation>
{
public:
file_read_operation(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
void* buffer,
std::size_t byteCount) noexcept
: cppcoro::detail::win32_overlapped_operation<file_read_operation>(fileOffset)
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<file_read_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
file_read_operation_impl m_impl;
};
class file_read_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>
{
public:
file_read_operation_cancellable(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
void* buffer,
std::size_t byteCount,
cancellation_token&& cancellationToken) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>(
fileOffset, std::move(cancellationToken))
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
file_read_operation_impl m_impl;
};
#endif
}
#endif
+45
View File
@@ -0,0 +1,45 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
#define CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_share_mode
{
/// Don't allow any other processes to open the file concurrently.
none = 0,
/// Allow other processes to open the file in read-only mode
/// concurrently with this process opening the file.
read = 1,
/// Allow other processes to open the file in write-only mode
/// concurrently with this process opening the file.
write = 2,
/// Allow other processes to open the file in read and/or write mode
/// concurrently with this process opening the file.
read_write = read | write,
/// Allow other processes to delete the file while this process
/// has the file open.
delete_ = 4
};
constexpr file_share_mode operator|(file_share_mode a, file_share_mode b)
{
return static_cast<file_share_mode>(
static_cast<int>(a) | static_cast<int>(b));
}
constexpr file_share_mode operator&(file_share_mode a, file_share_mode b)
{
return static_cast<file_share_mode>(
static_cast<int>(a) & static_cast<int>(b));
}
}
#endif
+98
View File
@@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
#define CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <atomic>
#include <optional>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
class file_write_operation_impl
{
public:
file_write_operation_impl(
detail::win32::handle_t fileHandle,
const void* buffer,
std::size_t byteCount) noexcept
: m_fileHandle(fileHandle)
, m_buffer(buffer)
, m_byteCount(byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
detail::win32::handle_t m_fileHandle;
const void* m_buffer;
std::size_t m_byteCount;
};
class file_write_operation
: public cppcoro::detail::win32_overlapped_operation<file_write_operation>
{
public:
file_write_operation(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
const void* buffer,
std::size_t byteCount) noexcept
: cppcoro::detail::win32_overlapped_operation<file_write_operation>(fileOffset)
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<file_write_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
file_write_operation_impl m_impl;
};
class file_write_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>
{
public:
file_write_operation_cancellable(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>(fileOffset, std::move(ct))
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
file_write_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef CPPCORO_FILESYSTEM_HPP_INCLUDED
#define CPPCORO_FILESYSTEM_HPP_INCLUDED
#if __has_include(<filesystem>)
#include <filesystem>
namespace cppcoro {
namespace filesystem = std::filesystem;
}
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace cppcoro {
namespace filesystem = std::experimental::filesystem;
}
#else
#error Cppcoro requires a C++20 compiler with filesystem support
#endif
#endif
+169
View File
@@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FMAP_HPP_INCLUDED
#define CPPCORO_FMAP_HPP_INCLUDED
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <utility>
#include <type_traits>
#include <functional>
namespace cppcoro
{
namespace detail
{
template<typename FUNC, typename AWAITABLE>
class fmap_awaiter
{
using awaiter_t = typename awaitable_traits<AWAITABLE&&>::awaiter_t;
FUNC&& m_func;
awaiter_t m_awaiter;
public:
fmap_awaiter(FUNC&& func, AWAITABLE&& awaitable)
noexcept(
std::is_nothrow_move_constructible_v<awaiter_t> &&
noexcept(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable))))
: m_func(static_cast<FUNC&&>(func))
, m_awaiter(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable)))
{}
decltype(auto) await_ready()
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_ready()))
{
return static_cast<awaiter_t&&>(m_awaiter).await_ready();
}
template<typename PROMISE>
decltype(auto) await_suspend(cppcoro::coroutine_handle<PROMISE> coro)
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro))))
{
return static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro));
}
template<
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
decltype(auto) await_resume()
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func))))
{
static_cast<awaiter_t&&>(m_awaiter).await_resume();
return std::invoke(static_cast<FUNC&&>(m_func));
}
template<
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
decltype(auto) await_resume()
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func), static_cast<awaiter_t&&>(m_awaiter).await_resume())))
{
return std::invoke(
static_cast<FUNC&&>(m_func),
static_cast<awaiter_t&&>(m_awaiter).await_resume());
}
};
template<typename FUNC, typename AWAITABLE>
class fmap_awaitable
{
static_assert(!std::is_lvalue_reference_v<FUNC>);
static_assert(!std::is_lvalue_reference_v<AWAITABLE>);
public:
template<
typename FUNC_ARG,
typename AWAITABLE_ARG,
std::enable_if_t<
std::is_constructible_v<FUNC, FUNC_ARG&&> &&
std::is_constructible_v<AWAITABLE, AWAITABLE_ARG&&>, int> = 0>
explicit fmap_awaitable(FUNC_ARG&& func, AWAITABLE_ARG&& awaitable)
noexcept(
std::is_nothrow_constructible_v<FUNC, FUNC_ARG&&> &&
std::is_nothrow_constructible_v<AWAITABLE, AWAITABLE_ARG&&>)
: m_func(static_cast<FUNC_ARG&&>(func))
, m_awaitable(static_cast<AWAITABLE_ARG&&>(awaitable))
{}
auto operator co_await() const &
{
return fmap_awaiter<const FUNC&, const AWAITABLE&>(m_func, m_awaitable);
}
auto operator co_await() &
{
return fmap_awaiter<FUNC&, AWAITABLE&>(m_func, m_awaitable);
}
auto operator co_await() &&
{
return fmap_awaiter<FUNC&&, AWAITABLE&&>(
static_cast<FUNC&&>(m_func),
static_cast<AWAITABLE&&>(m_awaitable));
}
private:
FUNC m_func;
AWAITABLE m_awaitable;
};
}
template<typename FUNC>
struct fmap_transform
{
explicit fmap_transform(FUNC&& f)
noexcept(std::is_nothrow_move_constructible_v<FUNC>)
: func(std::forward<FUNC>(f))
{}
FUNC func;
};
template<
typename FUNC,
typename AWAITABLE,
std::enable_if_t<cppcoro::is_awaitable_v<AWAITABLE>, int> = 0>
auto fmap(FUNC&& func, AWAITABLE&& awaitable)
{
return detail::fmap_awaitable<
std::remove_cv_t<std::remove_reference_t<FUNC>>,
std::remove_cv_t<std::remove_reference_t<AWAITABLE>>>(
std::forward<FUNC>(func),
std::forward<AWAITABLE>(awaitable));
}
template<typename FUNC>
auto fmap(FUNC&& func)
{
return fmap_transform<FUNC>{ std::forward<FUNC>(func) };
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, fmap_transform<FUNC>&& transform)
{
// Use ADL for finding fmap() overload.
return fmap(std::forward<FUNC>(transform.func), std::forward<T>(value));
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, const fmap_transform<FUNC>& transform)
{
// Use ADL for finding fmap() overload.
return fmap(transform.func, std::forward<T>(value));
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, fmap_transform<FUNC>& transform)
{
// Use ADL for finding fmap() overload.
return fmap(transform.func, std::forward<T>(value));
}
}
#endif
+260
View File
@@ -0,0 +1,260 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_GENERATOR_HPP_INCLUDED
#define CPPCORO_GENERATOR_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <utility>
#include <exception>
#include <iterator>
#include <functional>
namespace cppcoro
{
template<typename T>
class generator;
namespace detail
{
template<typename T>
class generator_promise
{
public:
using value_type = std::remove_reference_t<T>;
using reference_type = std::conditional_t<std::is_reference_v<T>, T, T&>;
using pointer_type = value_type*;
generator_promise() = default;
generator<T> get_return_object() noexcept;
constexpr cppcoro::suspend_always initial_suspend() const noexcept { return {}; }
constexpr cppcoro::suspend_always final_suspend() const noexcept { return {}; }
template<
typename U = T,
std::enable_if_t<!std::is_rvalue_reference<U>::value, int> = 0>
cppcoro::suspend_always yield_value(std::remove_reference_t<T>& value) noexcept
{
m_value = std::addressof(value);
return {};
}
cppcoro::suspend_always yield_value(std::remove_reference_t<T>&& value) noexcept
{
m_value = std::addressof(value);
return {};
}
void unhandled_exception()
{
m_exception = std::current_exception();
}
void return_void()
{
}
reference_type value() const noexcept
{
return static_cast<reference_type>(*m_value);
}
// Don't allow any use of 'co_await' inside the generator coroutine.
template<typename U>
cppcoro::suspend_never await_transform(U&& value) = delete;
void rethrow_if_exception()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
pointer_type m_value;
std::exception_ptr m_exception;
};
struct generator_sentinel {};
template<typename T>
class generator_iterator
{
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
public:
using iterator_category = std::input_iterator_tag;
// What type should we use for counting elements of a potentially infinite sequence?
using difference_type = std::ptrdiff_t;
using value_type = typename generator_promise<T>::value_type;
using reference = typename generator_promise<T>::reference_type;
using pointer = typename generator_promise<T>::pointer_type;
// Iterator needs to be default-constructible to satisfy the Range concept.
generator_iterator() noexcept
: m_coroutine(nullptr)
{}
explicit generator_iterator(coroutine_handle coroutine) noexcept
: m_coroutine(coroutine)
{}
friend bool operator==(const generator_iterator& it, generator_sentinel) noexcept
{
return !it.m_coroutine || it.m_coroutine.done();
}
friend bool operator!=(const generator_iterator& it, generator_sentinel s) noexcept
{
return !(it == s);
}
friend bool operator==(generator_sentinel s, const generator_iterator& it) noexcept
{
return (it == s);
}
friend bool operator!=(generator_sentinel s, const generator_iterator& it) noexcept
{
return it != s;
}
generator_iterator& operator++()
{
m_coroutine.resume();
if (m_coroutine.done())
{
m_coroutine.promise().rethrow_if_exception();
}
return *this;
}
// Need to provide post-increment operator to implement the 'Range' concept.
void operator++(int)
{
(void)operator++();
}
reference operator*() const noexcept
{
return m_coroutine.promise().value();
}
pointer operator->() const noexcept
{
return std::addressof(operator*());
}
private:
coroutine_handle m_coroutine;
};
}
template<typename T>
class [[nodiscard]] generator
{
public:
using promise_type = detail::generator_promise<T>;
using iterator = detail::generator_iterator<T>;
generator() noexcept
: m_coroutine(nullptr)
{}
generator(generator&& other) noexcept
: m_coroutine(other.m_coroutine)
{
other.m_coroutine = nullptr;
}
generator(const generator& other) = delete;
~generator()
{
if (m_coroutine)
{
m_coroutine.destroy();
}
}
generator& operator=(generator other) noexcept
{
swap(other);
return *this;
}
iterator begin()
{
if (m_coroutine)
{
m_coroutine.resume();
if (m_coroutine.done())
{
m_coroutine.promise().rethrow_if_exception();
}
}
return iterator{ m_coroutine };
}
detail::generator_sentinel end() noexcept
{
return detail::generator_sentinel{};
}
void swap(generator& other) noexcept
{
std::swap(m_coroutine, other.m_coroutine);
}
private:
friend class detail::generator_promise<T>;
explicit generator(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
template<typename T>
void swap(generator<T>& a, generator<T>& b)
{
a.swap(b);
}
namespace detail
{
template<typename T>
generator<T> generator_promise<T>::get_return_object() noexcept
{
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
return generator<T>{ coroutine_handle::from_promise(*this) };
}
}
template<typename FUNC, typename T>
generator<std::invoke_result_t<FUNC&, typename generator<T>::iterator::reference>> fmap(FUNC func, generator<T> source)
{
for (auto&& value : source)
{
co_yield std::invoke(func, static_cast<decltype(value)>(value));
}
}
}
#endif
+25
View File
@@ -0,0 +1,25 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
#define CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class inline_scheduler
{
public:
inline_scheduler() noexcept = default;
cppcoro::suspend_never schedule() const noexcept
{
return {};
}
};
}
#endif
+321
View File
@@ -0,0 +1,321 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_IO_SERVICE_HPP_INCLUDED
#define CPPCORO_IO_SERVICE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/cancellation_registration.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
#include <optional>
#include <chrono>
#include <cstdint>
#include <atomic>
#include <utility>
#include <mutex>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class io_service
{
public:
class schedule_operation;
class timed_schedule_operation;
/// Initialises the io_service.
///
/// Does not set a concurrency hint. All threads that enter the
/// event loop will actively process events.
io_service();
/// Initialise the io_service with a concurrency hint.
///
/// \param concurrencyHint
/// Specifies the target maximum number of I/O threads to be
/// actively processing events.
/// Note that the number of active threads may temporarily go
/// above this number.
io_service(std::uint32_t concurrencyHint);
~io_service();
io_service(io_service&& other) = delete;
io_service(const io_service& other) = delete;
io_service& operator=(io_service&& other) = delete;
io_service& operator=(const io_service& other) = delete;
/// Returns an operation that when awaited suspends the awaiting
/// coroutine and reschedules it for resumption on an I/O thread
/// associated with this io_service.
[[nodiscard]]
schedule_operation schedule() noexcept;
/// Returns an operation that when awaited will suspend the
/// awaiting coroutine for the specified delay. Once the delay
/// has elapsed, the coroutine will resume execution on an
/// I/O thread associated with this io_service.
///
/// \param delay
/// The amount of time to delay scheduling resumption of the coroutine
/// on an I/O thread. There is no guarantee that the coroutine will
/// be resumed exactly after this delay.
///
/// \param cancellationToken [optional]
/// A cancellation token that can be used to communicate a request to
/// cancel the delayed schedule operation and schedule it for resumption
/// immediately.
/// The co_await operation will throw cppcoro::operation_cancelled if
/// cancellation was requested before the coroutine could be resumed.
template<typename REP, typename PERIOD>
[[nodiscard]]
timed_schedule_operation schedule_after(
const std::chrono::duration<REP, PERIOD>& delay,
cancellation_token cancellationToken = {}) noexcept;
/// Process events until the io_service is stopped.
///
/// \return
/// The number of events processed during this call.
std::uint64_t process_events();
/// Process events until either the io_service is stopped or
/// there are no more pending events in the queue.
///
/// \return
/// The number of events processed during this call.
std::uint64_t process_pending_events();
/// Block until either one event is processed or the io_service is stopped.
///
/// \return
/// The number of events processed during this call.
/// This will either be 0 or 1.
std::uint64_t process_one_event();
/// Process one event if there are any events pending, otherwise if there
/// are no events pending or the io_service is stopped then return immediately.
///
/// \return
/// The number of events processed during this call.
/// This will either be 0 or 1.
std::uint64_t process_one_pending_event();
/// Shut down the io_service.
///
/// This will cause any threads currently in a call to one of the process_xxx() methods
/// to return from that call once they finish processing the current event.
///
/// This call does not wait until all threads have exited the event loop so you
/// must use other synchronisation mechanisms to wait for those threads.
void stop() noexcept;
/// Reset an io_service to prepare it for resuming processing of events.
///
/// Call this after a call to stop() to allow calls to process_xxx() methods
/// to process events.
///
/// After calling stop() you should ensure that all threads have returned from
/// calls to process_xxx() methods before calling reset().
void reset();
bool is_stop_requested() const noexcept;
void notify_work_started() noexcept;
void notify_work_finished() noexcept;
#if CPPCORO_OS_WINNT
detail::win32::handle_t native_iocp_handle() noexcept;
void ensure_winsock_initialised();
#endif
private:
class timer_thread_state;
class timer_queue;
friend class schedule_operation;
friend class timed_schedule_operation;
void schedule_impl(schedule_operation* operation) noexcept;
void try_reschedule_overflow_operations() noexcept;
bool try_enter_event_loop() noexcept;
void exit_event_loop() noexcept;
bool try_process_one_event(bool waitForEvent);
void post_wake_up_event() noexcept;
timer_thread_state* ensure_timer_thread_started();
static constexpr std::uint32_t stop_requested_flag = 1;
static constexpr std::uint32_t active_thread_count_increment = 2;
// Bit 0: stop_requested_flag
// Bit 1-31: count of active threads currently running the event loop
std::atomic<std::uint32_t> m_threadState;
std::atomic<std::uint32_t> m_workCount;
#if CPPCORO_OS_WINNT
detail::win32::safe_handle m_iocpHandle;
std::atomic<bool> m_winsockInitialised;
std::mutex m_winsockInitialisationMutex;
#endif
// Head of a linked-list of schedule operations that are
// ready to run but that failed to be queued to the I/O
// completion port (eg. due to low memory).
std::atomic<schedule_operation*> m_scheduleOperations;
std::atomic<timer_thread_state*> m_timerState;
};
class io_service::schedule_operation
{
public:
schedule_operation(io_service& service) noexcept
: m_service(service)
{}
bool await_ready() const noexcept { return false; }
void await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class io_service;
friend class io_service::timed_schedule_operation;
io_service& m_service;
cppcoro::coroutine_handle<> m_awaiter;
schedule_operation* m_next;
};
class io_service::timed_schedule_operation
{
public:
timed_schedule_operation(
io_service& service,
std::chrono::high_resolution_clock::time_point resumeTime,
cppcoro::cancellation_token cancellationToken) noexcept;
timed_schedule_operation(timed_schedule_operation&& other) noexcept;
~timed_schedule_operation();
timed_schedule_operation& operator=(timed_schedule_operation&& other) = delete;
timed_schedule_operation(const timed_schedule_operation& other) = delete;
timed_schedule_operation& operator=(const timed_schedule_operation& other) = delete;
bool await_ready() const noexcept;
void await_suspend(cppcoro::coroutine_handle<> awaiter);
void await_resume();
private:
friend class io_service::timer_queue;
friend class io_service::timer_thread_state;
io_service::schedule_operation m_scheduleOperation;
std::chrono::high_resolution_clock::time_point m_resumeTime;
cppcoro::cancellation_token m_cancellationToken;
std::optional<cppcoro::cancellation_registration> m_cancellationRegistration;
timed_schedule_operation* m_next;
std::atomic<std::uint32_t> m_refCount;
};
class io_work_scope
{
public:
explicit io_work_scope(io_service& service) noexcept
: m_service(&service)
{
service.notify_work_started();
}
io_work_scope(const io_work_scope& other) noexcept
: m_service(other.m_service)
{
if (m_service != nullptr)
{
m_service->notify_work_started();
}
}
io_work_scope(io_work_scope&& other) noexcept
: m_service(other.m_service)
{
other.m_service = nullptr;
}
~io_work_scope()
{
if (m_service != nullptr)
{
m_service->notify_work_finished();
}
}
void swap(io_work_scope& other) noexcept
{
std::swap(m_service, other.m_service);
}
io_work_scope& operator=(io_work_scope other) noexcept
{
swap(other);
return *this;
}
io_service& service() noexcept
{
return *m_service;
}
private:
io_service* m_service;
};
inline void swap(io_work_scope& a, io_work_scope& b)
{
a.swap(b);
}
}
template<typename REP, typename RATIO>
cppcoro::io_service::timed_schedule_operation
cppcoro::io_service::schedule_after(
const std::chrono::duration<REP, RATIO>& duration,
cppcoro::cancellation_token cancellationToken) noexcept
{
return timed_schedule_operation{
*this,
std::chrono::high_resolution_clock::now() + duration,
std::move(cancellationToken)
};
}
#endif
+26
View File
@@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_IS_AWAITABLE_HPP_INCLUDED
#define CPPCORO_IS_AWAITABLE_HPP_INCLUDED
#include <cppcoro/detail/get_awaiter.hpp>
#include <type_traits>
namespace cppcoro
{
template<typename T, typename = std::void_t<>>
struct is_awaitable : std::false_type {};
template<typename T>
struct is_awaitable<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
: std::true_type
{};
template<typename T>
constexpr bool is_awaitable_v = is_awaitable<T>::value;
}
#endif
@@ -0,0 +1,829 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
#define CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/sequence_barrier.hpp>
#include <cppcoro/sequence_range.hpp>
#include <cppcoro/sequence_traits.hpp>
#include <cppcoro/detail/manual_lifetime.hpp>
#include <atomic>
#include <cstdint>
#include <cassert>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_operation;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_operation;
template<typename SEQUENCE, typename TRAITS>
class multi_producer_sequencer_wait_operation_base;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_wait_operation;
/// A multi-producer sequencer is a thread-synchronisation primitive that can be
/// used to synchronise access to a ring-buffer of power-of-two size where you
/// have multiple producers concurrently claiming slots in the ring-buffer and
/// publishing items.
///
/// When a writer wants to write to a slot in the buffer it first atomically
/// increments a counter by the number of slots it wishes to allocate.
/// It then waits until all of those slots have become available and then
/// returns the range of sequence numbers allocated back to the caller.
/// The caller then writes to those slots and when done publishes them by
/// writing the sequence numbers published to each of the slots to the
/// corresponding element of an array of equal size to the ring buffer.
/// When a reader wants to check if the next sequence number is available
/// it then simply needs to read from the corresponding slot in this array
/// to check if the value stored there is equal to the sequence number it
/// is wanting to read.
///
/// This means concurrent writers are wait-free when there is space available
/// in the ring buffer, requiring a single atomic fetch-add operation as the
/// only contended write operation. All other writes are to memory locations
/// owned by a particular writer. Concurrent writers can publish items out of
/// order so that one writer does not hold up other writers until the ring
/// buffer fills up.
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class multi_producer_sequencer
{
public:
multi_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence = TRAITS::initial_sequence);
/// The size of the circular buffer. This will be a power-of-two.
std::size_t buffer_size() const noexcept { return m_sequenceMask + 1; }
/// Lookup the last-known-published sequence number after the specified
/// sequence number.
SEQUENCE last_published_after(SEQUENCE lastKnownPublished) const noexcept;
/// Wait until the specified target sequence number has been published.
///
/// Returns an awaitable type that when co_awaited will suspend the awaiting
/// coroutine until the specified 'targetSequence' number and all prior sequence
/// numbers have been published.
template<typename SCHEDULER>
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) const noexcept;
/// Query if there are currently any slots available for claiming.
///
/// Note that this return-value is only approximate if you have multiple producers
/// since immediately after returning true another thread may have claimed the
/// last available slot.
bool any_available() const noexcept;
/// Claim a single slot in the buffer and wait until that slot becomes available.
///
/// Returns an Awaitable type that yields the sequence number of the slot that
/// was claimed.
///
/// Once the producer has claimed a slot then they are free to write to that
/// slot within the ring buffer. Once the value has been initialised the item
/// must be published by calling the .publish() method, passing the sequence
/// number.
template<typename SCHEDULER>
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_one(SCHEDULER& scheduler) noexcept;
/// Claim a contiguous range of sequence numbers corresponding to slots within
/// a ring-buffer.
///
/// This will claim at most the specified count of sequence numbers but may claim
/// fewer if there are only fewer entries available in the buffer. But will claim
/// at least one sequence number.
///
/// Returns an awaitable that will yield a sequence_range object containing the
/// sequence numbers that were claimed.
///
/// The caller is responsible for ensuring that they publish every element of the
/// returned sequence range by calling .publish().
template<typename SCHEDULER>
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept;
/// Publish the element with the specified sequence number, making it available
/// to consumers.
///
/// Note that different sequence numbers may be published by different producer
/// threads out of order. A sequence number will not become available to consumers
/// until all preceding sequence numbers have also been published.
///
/// \param sequence
/// The sequence number of the elemnt to publish
/// This sequence number must have been previously acquired via a call to 'claim_one()'
/// or 'claim_up_to()'.
void publish(SEQUENCE sequence) noexcept;
/// Publish a contiguous range of sequence numbers, making each of them available
/// to consumers.
///
/// This is equivalent to calling publish(seq) for each sequence number, seq, in
/// the specified range, but is more efficient since it only checks to see if
/// there are coroutines that need to be woken up once.
void publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept;
private:
template<typename SEQUENCE2, typename TRAITS2>
friend class multi_producer_sequencer_wait_operation_base;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class multi_producer_sequencer_claim_operation;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class multi_producer_sequencer_claim_one_operation;
void resume_ready_awaiters() noexcept;
void add_awaiter(multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
const std::size_t m_sequenceMask;
const std::unique_ptr<std::atomic<SEQUENCE>[]> m_published;
alignas(CPPCORO_CPU_CACHE_LINE)
std::atomic<SEQUENCE> m_nextToClaim;
alignas(CPPCORO_CPU_CACHE_LINE)
mutable std::atomic<multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>*> m_awaiters;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_awaiter
{
public:
multi_producer_sequencer_claim_awaiter(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
const sequence_range<SEQUENCE, TRAITS>& claimedRange,
SCHEDULER& scheduler) noexcept
: m_barrierWait(consumerBarrier, claimedRange.back() - bufferSize, scheduler)
, m_claimedRange(claimedRange)
{}
bool await_ready() const noexcept
{
return m_barrierWait.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_barrierWait.await_suspend(awaitingCoroutine);
}
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
{
return m_claimedRange;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_barrierWait;
sequence_range<SEQUENCE, TRAITS> m_claimedRange;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_operation
{
public:
multi_producer_sequencer_claim_operation(
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
std::size_t count,
SCHEDULER& scheduler) noexcept
: m_sequencer(sequencer)
, m_count(count < sequencer.buffer_size() ? count : sequencer.buffer_size())
, m_scheduler(scheduler)
{
}
multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
{
// We wait until the awaitable is actually co_await'ed before we claim the
// range of elements. If we claimed them earlier, then it may be possible for
// the caller to fail to co_await the result eg. due to an exception, which
// would leave the sequence numbers unable to be published and would eventually
// deadlock consumers that waited on them.
//
// TODO: We could try and acquire only as many as are available if fewer than
// m_count elements are available. This would complicate the logic here somewhat
// as we'd need to use a compare-exchange instead.
const SEQUENCE first = m_sequencer.m_nextToClaim.fetch_add(m_count, std::memory_order_relaxed);
return multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
m_sequencer.m_consumerBarrier,
m_sequencer.buffer_size(),
sequence_range<SEQUENCE, TRAITS>{ first, first + m_count },
m_scheduler
};
}
private:
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
std::size_t m_count;
SCHEDULER& m_scheduler;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_awaiter
{
public:
multi_producer_sequencer_claim_one_awaiter(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE claimedSequence,
SCHEDULER& scheduler) noexcept
: m_waitOp(consumerBarrier, claimedSequence - bufferSize, scheduler)
, m_claimedSequence(claimedSequence)
{}
bool await_ready() const noexcept
{
return m_waitOp.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_waitOp.await_suspend(awaitingCoroutine);
}
SEQUENCE await_resume() noexcept
{
return m_claimedSequence;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_waitOp;
SEQUENCE m_claimedSequence;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_operation
{
public:
multi_producer_sequencer_claim_one_operation(
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SCHEDULER& scheduler) noexcept
: m_sequencer(sequencer)
, m_scheduler(scheduler)
{}
multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
{
return multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
m_sequencer.m_consumerBarrier,
m_sequencer.buffer_size(),
m_sequencer.m_nextToClaim.fetch_add(1, std::memory_order_relaxed),
m_scheduler
};
}
private:
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
SCHEDULER& m_scheduler;
};
template<typename SEQUENCE, typename TRAITS>
class multi_producer_sequencer_wait_operation_base
{
public:
multi_producer_sequencer_wait_operation_base(
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished) noexcept
: m_sequencer(sequencer)
, m_targetSequence(targetSequence)
, m_lastKnownPublished(lastKnownPublished)
, m_readyToResume(false)
{}
multi_producer_sequencer_wait_operation_base(
const multi_producer_sequencer_wait_operation_base& other) noexcept
: m_sequencer(other.m_sequencer)
, m_targetSequence(other.m_targetSequence)
, m_lastKnownPublished(other.m_lastKnownPublished)
, m_readyToResume(false)
{}
bool await_ready() const noexcept
{
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
m_sequencer.add_awaiter(this);
// Mark the waiter as ready to resume.
// If it was already marked as ready-to-resume within the call to add_awaiter() or
// on another thread then this exchange() will return true. In this case we want to
// resume immediately and continue execution by returning false.
return !m_readyToResume.exchange(true, std::memory_order_acquire);
}
SEQUENCE await_resume() noexcept
{
return m_lastKnownPublished;
}
protected:
friend class multi_producer_sequencer<SEQUENCE, TRAITS>;
void resume(SEQUENCE lastKnownPublished) noexcept
{
m_lastKnownPublished = lastKnownPublished;
if (m_readyToResume.exchange(true, std::memory_order_release))
{
resume_impl();
}
}
virtual void resume_impl() noexcept = 0;
const multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
SEQUENCE m_targetSequence;
SEQUENCE m_lastKnownPublished;
multi_producer_sequencer_wait_operation_base* m_next;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
std::atomic<bool> m_readyToResume;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_wait_operation :
public multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>
{
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
public:
multi_producer_sequencer_wait_operation(
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) noexcept
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(sequencer, targetSequence, lastKnownPublished)
, m_scheduler(scheduler)
{}
multi_producer_sequencer_wait_operation(
const multi_producer_sequencer_wait_operation& other) noexcept
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(other)
, m_scheduler(other.m_scheduler)
{}
~multi_producer_sequencer_wait_operation()
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter.destruct();
}
if (m_isScheduleOperationCreated)
{
m_scheduleOperation.destruct();
}
}
SEQUENCE await_resume() noexcept(noexcept(m_scheduleOperation->await_resume()))
{
if (m_isScheduleOperationCreated)
{
m_scheduleOperation->await_resume();
}
return multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
}
private:
void resume_impl() noexcept override
{
try
{
m_scheduleOperation.construct(m_scheduler.schedule());
m_isScheduleOperationCreated = true;
m_scheduleAwaiter.construct(detail::get_awaiter(
static_cast<schedule_operation&&>(*m_scheduleOperation)));
m_isScheduleAwaiterCreated = true;
if (!m_scheduleAwaiter->await_ready())
{
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
if constexpr (std::is_void_v<await_suspend_result_t>)
{
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
return;
}
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
{
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
{
return;
}
}
else
{
// Assume it returns a coroutine_handle.
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
return;
}
}
}
catch (...)
{
// Ignore failure to reschedule and resume inline?
// Should we catch the exception and rethrow from await_resume()?
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
}
// Resume outside the catch-block.
this->m_awaitingCoroutine.resume();
}
SCHEDULER& m_scheduler;
// Can't use std::optional<T> here since T could be a reference.
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
bool m_isScheduleOperationCreated = false;
bool m_isScheduleAwaiterCreated = false;
};
template<typename SEQUENCE, typename TRAITS>
multi_producer_sequencer<SEQUENCE, TRAITS>::multi_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence)
: m_consumerBarrier(consumerBarrier)
, m_sequenceMask(bufferSize - 1)
, m_published(std::make_unique<std::atomic<SEQUENCE>[]>(bufferSize))
, m_nextToClaim(initialSequence + 1)
, m_awaiters(nullptr)
{
// bufferSize must be a positive power-of-two
assert(bufferSize > 0 && (bufferSize & (bufferSize - 1)) == 0);
// but must be no larger than the max diff value.
using diff_t = typename TRAITS::difference_type;
using unsigned_diff_t = std::make_unsigned_t<diff_t>;
constexpr unsigned_diff_t maxSize = static_cast<unsigned_diff_t>(std::numeric_limits<diff_t>::max());
assert(bufferSize <= maxSize);
SEQUENCE seq = initialSequence - (bufferSize - 1);
do
{
#ifdef __cpp_lib_atomic_value_initialization
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
#else // ^^^ __cpp_lib_atomic_value_initialization // !__cpp_lib_atomic_value_initialization vvv
std::atomic_init(&m_published[seq & m_sequenceMask], seq);
#endif // !__cpp_lib_atomic_value_initialization
} while (seq++ != initialSequence);
}
template<typename SEQUENCE, typename TRAITS>
SEQUENCE multi_producer_sequencer<SEQUENCE, TRAITS>::last_published_after(
SEQUENCE lastKnownPublished) const noexcept
{
const auto mask = m_sequenceMask;
SEQUENCE seq = lastKnownPublished + 1;
while (m_published[seq & mask].load(std::memory_order_acquire) == seq)
{
lastKnownPublished = seq++;
}
return lastKnownPublished;
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::wait_until_published(
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) const noexcept
{
return multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>{
*this, targetSequence, lastKnownPublished, scheduler
};
}
template<typename SEQUENCE, typename TRAITS>
bool multi_producer_sequencer<SEQUENCE, TRAITS>::any_available() const noexcept
{
return TRAITS::precedes(
m_nextToClaim.load(std::memory_order_relaxed),
m_consumerBarrier.last_published() + buffer_size());
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
{
return multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
{
return multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, count, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
{
m_published[sequence & m_sequenceMask].store(sequence, std::memory_order_seq_cst);
// Resume any waiters that might have been satisfied by this publish operation.
resume_ready_awaiters();
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept
{
if (range.empty())
{
return;
}
// Publish all but the first sequence number using relaxed atomics.
// No consumer should be reading those subsequent sequence numbers until they've seen
// that the first sequence number in the range is published.
for (SEQUENCE seq : range.skip(1))
{
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
}
// Now publish the first sequence number with seq_cst semantics.
m_published[range.front() & m_sequenceMask].store(range.front(), std::memory_order_seq_cst);
// Resume any waiters that might have been satisfied by this publish operation.
resume_ready_awaiters();
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::resume_ready_awaiters() noexcept
{
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
awaiter_t* awaiters = m_awaiters.load(std::memory_order_seq_cst);
if (awaiters == nullptr)
{
// No awaiters
return;
}
// There were some awaiters. Try to acquire the list of waiters with an
// atomic exchange as we might be racing with other consumers/producers.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_seq_cst);
if (awaiters == nullptr)
{
// Didn't acquire the list
// Some other thread is now responsible for resuming them. Our job is done.
return;
}
SEQUENCE lastKnownPublished;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
awaiter_t* awaitersToRequeue;
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
do
{
using diff_t = typename TRAITS::difference_type;
lastKnownPublished = last_published_after(awaiters->m_lastKnownPublished);
// First scan the list of awaiters and split them into 'requeue' and 'resume' lists.
auto minDiff = std::numeric_limits<diff_t>::max();
do
{
auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
if (diff > 0)
{
// Not ready yet.
minDiff = diff < minDiff ? diff : minDiff;
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
}
else
{
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters->m_lastKnownPublished = lastKnownPublished;
awaiters = awaiters->m_next;
} while (awaiters != nullptr);
// Null-terinate the requeue list
*awaitersToRequeueTail = nullptr;
if (awaitersToRequeue != nullptr)
{
// Requeue the waiters that are not ready yet.
awaiter_t* oldHead = nullptr;
while (!m_awaiters.compare_exchange_weak(oldHead, awaitersToRequeue, std::memory_order_seq_cst, std::memory_order_relaxed))
{
*awaitersToRequeueTail = oldHead;
}
// Reset the awaitersToRequeue list
awaitersToRequeueTail = &awaitersToRequeue;
const SEQUENCE earliestTargetSequence = lastKnownPublished + minDiff;
// Now we need to check again to see if any of the waiters we just enqueued
// is now satisfied by a concurrent call to publish().
//
// We need to be a bit more careful here since we are no longer holding any
// awaiters and so producers/consumers may advance the sequence number arbitrarily
// far. If the sequence number advances more than buffer_size() ahead of the
// earliestTargetSequence then the m_published[] array may have sequence numbers
// that have advanced beyond earliestTargetSequence, potentially even wrapping
// sequence numbers around to then be preceding where they were before. If this
// happens then we don't need to worry about resuming any awaiters that were waiting
// for 'earliestTargetSequence' since some other thread has already resumed them.
// So the only case we need to worry about here is when all m_published entries for
// sequence numbers in range [lastKnownPublished + 1, earliestTargetSequence] have
// published sequence numbers that match the range.
const auto sequenceMask = m_sequenceMask;
SEQUENCE seq = lastKnownPublished + 1;
while (m_published[seq & sequenceMask].load(std::memory_order_seq_cst) == seq)
{
lastKnownPublished = seq;
if (seq == earliestTargetSequence)
{
// At least one of the awaiters we just published is now satisfied.
// Reacquire the list of awaiters and continue around the outer loop.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
break;
}
++seq;
}
}
} while (awaiters != nullptr);
// Null-terminate list of awaiters to resume.
*awaitersToResumeTail = nullptr;
while (awaitersToResume != nullptr)
{
awaiter_t* next = awaitersToResume->m_next;
awaitersToResume->resume(lastKnownPublished);
awaitersToResume = next;
}
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::add_awaiter(
multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept
{
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
SEQUENCE targetSequence = awaiter->m_targetSequence;
SEQUENCE lastKnownPublished = awaiter->m_lastKnownPublished;
awaiter_t* awaitersToEnqueue = awaiter;
awaiter_t** awaitersToEnqueueTail = &awaiter->m_next;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
const SEQUENCE sequenceMask = m_sequenceMask;
do
{
// Enqueue the awaiters.
{
awaiter_t* oldHead = m_awaiters.load(std::memory_order_relaxed);
do
{
*awaitersToEnqueueTail = oldHead;
} while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToEnqueue,
std::memory_order_seq_cst,
std::memory_order_relaxed));
}
// Reset list of waiters
awaitersToEnqueueTail = &awaitersToEnqueue;
// Check to see if the last-known published sequence number has advanced
// while we were enqueuing the awaiters. Need to use seq_cst memory order
// here to ensure that if there are concurrent calls to publish() that would
// wake up any of the awaiters we just enqueued that either we will see their
// write to m_published slots or they will see our write to m_awaiters.
//
// Note also, that we are assuming that the last-known published sequence is
// not going to advance more than buffer_size() ahead of targetSequence since
// there is at least one consumer that won't be resumed and so thus can't
// publish the sequence number it's waiting for to its sequence_barrier and so
// producers won't be able to claim its slot in the buffer.
//
// TODO: Check whether we can weaken the memory order here to just use 'seq_cst' on the
// first .load() and then use 'acquire' on subsequent .load().
while (m_published[(lastKnownPublished + 1) & sequenceMask].load(std::memory_order_seq_cst) == (lastKnownPublished + 1))
{
++lastKnownPublished;
}
if (!TRAITS::precedes(lastKnownPublished, targetSequence))
{
// At least one awaiter we just enqueued has now been satisified.
// To ensure it is woken up we need to reacquire the list of awaiters and resume
awaiter_t* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
using diff_t = typename TRAITS::difference_type;
diff_t minDiff = std::numeric_limits<diff_t>::max();
while (awaiters != nullptr)
{
diff_t diff = TRAITS::difference(targetSequence, lastKnownPublished);
if (diff > 0)
{
// Not yet ready.
minDiff = diff < minDiff ? diff : minDiff;
*awaitersToEnqueueTail = awaiters;
awaitersToEnqueueTail = &awaiters->m_next;
awaiters->m_lastKnownPublished = lastKnownPublished;
}
else
{
// Now ready.
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
}
// Calculate the earliest sequence number that any awaiters in the
// awaitersToEnqueue list are waiting for. We'll use this next time
// around the loop.
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
}
// Null-terminate list of awaiters to enqueue.
*awaitersToEnqueueTail = nullptr;
} while (awaitersToEnqueue != nullptr);
// Null-terminate awaiters to resume.
*awaitersToResumeTail = nullptr;
// Finally, resume any awaiters we've found that are ready to go.
while (awaitersToResume != nullptr)
{
// Read m_next before calling .resume() as resuming could destroy the awaiter.
awaiter_t* next = awaitersToResume->m_next;
awaitersToResume->resume(lastKnownPublished);
awaitersToResume = next;
}
}
}
#endif
+147
View File
@@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
#include <cppcoro/net/ipv4_address.hpp>
#include <cppcoro/net/ipv6_address.hpp>
#include <cassert>
#include <optional>
#include <string>
namespace cppcoro
{
namespace net
{
class ip_address
{
public:
// Constructs to IPv4 address 0.0.0.0
ip_address() noexcept;
ip_address(ipv4_address address) noexcept;
ip_address(ipv6_address address) noexcept;
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
const ipv4_address& to_ipv4() const;
const ipv6_address& to_ipv6() const;
const std::uint8_t* bytes() const noexcept;
std::string to_string() const;
static std::optional<ip_address> from_string(std::string_view string) noexcept;
bool operator==(const ip_address& rhs) const noexcept;
bool operator!=(const ip_address& rhs) const noexcept;
// ipv4_address sorts less than ipv6_address
bool operator<(const ip_address& rhs) const noexcept;
bool operator>(const ip_address& rhs) const noexcept;
bool operator<=(const ip_address& rhs) const noexcept;
bool operator>=(const ip_address& rhs) const noexcept;
private:
enum class family
{
ipv4,
ipv6
};
family m_family;
union
{
ipv4_address m_ipv4;
ipv6_address m_ipv6;
};
};
inline ip_address::ip_address() noexcept
: m_family(family::ipv4)
, m_ipv4()
{}
inline ip_address::ip_address(ipv4_address address) noexcept
: m_family(family::ipv4)
, m_ipv4(address)
{}
inline ip_address::ip_address(ipv6_address address) noexcept
: m_family(family::ipv6)
, m_ipv6(address)
{
}
inline const ipv4_address& ip_address::to_ipv4() const
{
assert(is_ipv4());
return m_ipv4;
}
inline const ipv6_address& ip_address::to_ipv6() const
{
assert(is_ipv6());
return m_ipv6;
}
inline const std::uint8_t* ip_address::bytes() const noexcept
{
return is_ipv4() ? m_ipv4.bytes() : m_ipv6.bytes();
}
inline bool ip_address::operator==(const ip_address& rhs) const noexcept
{
if (is_ipv4())
{
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
}
}
inline bool ip_address::operator!=(const ip_address& rhs) const noexcept
{
return !(*this == rhs);
}
inline bool ip_address::operator<(const ip_address& rhs) const noexcept
{
if (is_ipv4())
{
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
}
}
inline bool ip_address::operator>(const ip_address& rhs) const noexcept
{
return rhs < *this;
}
inline bool ip_address::operator<=(const ip_address& rhs) const noexcept
{
return !(rhs < *this);
}
inline bool ip_address::operator>=(const ip_address& rhs) const noexcept
{
return !(*this < rhs);
}
}
}
#endif
+161
View File
@@ -0,0 +1,161 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ip_address.hpp>
#include <cppcoro/net/ipv4_endpoint.hpp>
#include <cppcoro/net/ipv6_endpoint.hpp>
#include <cassert>
#include <optional>
#include <string>
namespace cppcoro
{
namespace net
{
class ip_endpoint
{
public:
// Constructs to IPv4 end-point 0.0.0.0:0
ip_endpoint() noexcept;
ip_endpoint(ipv4_endpoint endpoint) noexcept;
ip_endpoint(ipv6_endpoint endpoint) noexcept;
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
const ipv4_endpoint& to_ipv4() const;
const ipv6_endpoint& to_ipv6() const;
ip_address address() const noexcept;
std::uint16_t port() const noexcept;
std::string to_string() const;
static std::optional<ip_endpoint> from_string(std::string_view string) noexcept;
bool operator==(const ip_endpoint& rhs) const noexcept;
bool operator!=(const ip_endpoint& rhs) const noexcept;
// ipv4_endpoint sorts less than ipv6_endpoint
bool operator<(const ip_endpoint& rhs) const noexcept;
bool operator>(const ip_endpoint& rhs) const noexcept;
bool operator<=(const ip_endpoint& rhs) const noexcept;
bool operator>=(const ip_endpoint& rhs) const noexcept;
private:
enum class family
{
ipv4,
ipv6
};
family m_family;
union
{
ipv4_endpoint m_ipv4;
ipv6_endpoint m_ipv6;
};
};
inline ip_endpoint::ip_endpoint() noexcept
: m_family(family::ipv4)
, m_ipv4()
{}
inline ip_endpoint::ip_endpoint(ipv4_endpoint endpoint) noexcept
: m_family(family::ipv4)
, m_ipv4(endpoint)
{}
inline ip_endpoint::ip_endpoint(ipv6_endpoint endpoint) noexcept
: m_family(family::ipv6)
, m_ipv6(endpoint)
{
}
inline const ipv4_endpoint& ip_endpoint::to_ipv4() const
{
assert(is_ipv4());
return m_ipv4;
}
inline const ipv6_endpoint& ip_endpoint::to_ipv6() const
{
assert(is_ipv6());
return m_ipv6;
}
inline ip_address ip_endpoint::address() const noexcept
{
if (is_ipv4())
{
return m_ipv4.address();
}
else
{
return m_ipv6.address();
}
}
inline std::uint16_t ip_endpoint::port() const noexcept
{
return is_ipv4() ? m_ipv4.port() : m_ipv6.port();
}
inline bool ip_endpoint::operator==(const ip_endpoint& rhs) const noexcept
{
if (is_ipv4())
{
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
}
}
inline bool ip_endpoint::operator!=(const ip_endpoint& rhs) const noexcept
{
return !(*this == rhs);
}
inline bool ip_endpoint::operator<(const ip_endpoint& rhs) const noexcept
{
if (is_ipv4())
{
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
}
}
inline bool ip_endpoint::operator>(const ip_endpoint& rhs) const noexcept
{
return rhs < *this;
}
inline bool ip_endpoint::operator<=(const ip_endpoint& rhs) const noexcept
{
return !(rhs < *this);
}
inline bool ip_endpoint::operator>=(const ip_endpoint& rhs) const noexcept
{
return !(*this < rhs);
}
}
}
#endif
+134
View File
@@ -0,0 +1,134 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro::net
{
class ipv4_address
{
using bytes_t = std::uint8_t[4];
public:
constexpr ipv4_address()
: m_bytes{ 0, 0, 0, 0 }
{}
explicit constexpr ipv4_address(std::uint32_t integer)
: m_bytes{
static_cast<std::uint8_t>(integer >> 24),
static_cast<std::uint8_t>(integer >> 16),
static_cast<std::uint8_t>(integer >> 8),
static_cast<std::uint8_t>(integer) }
{}
explicit constexpr ipv4_address(const std::uint8_t(&bytes)[4])
: m_bytes{ bytes[0], bytes[1], bytes[2], bytes[3] }
{}
explicit constexpr ipv4_address(
std::uint8_t b0,
std::uint8_t b1,
std::uint8_t b2,
std::uint8_t b3)
: m_bytes{ b0, b1, b2, b3 }
{}
constexpr const bytes_t& bytes() const { return m_bytes; }
constexpr std::uint32_t to_integer() const
{
return
std::uint32_t(m_bytes[0]) << 24 |
std::uint32_t(m_bytes[1]) << 16 |
std::uint32_t(m_bytes[2]) << 8 |
std::uint32_t(m_bytes[3]);
}
static constexpr ipv4_address loopback()
{
return ipv4_address(127, 0, 0, 1);
}
constexpr bool is_loopback() const
{
return m_bytes[0] == 127;
}
constexpr bool is_private_network() const
{
return m_bytes[0] == 10 ||
(m_bytes[0] == 172 && (m_bytes[1] & 0xF0) == 0x10) ||
(m_bytes[0] == 192 && m_bytes[2] == 168);
}
constexpr bool operator==(ipv4_address other) const
{
return
m_bytes[0] == other.m_bytes[0] &&
m_bytes[1] == other.m_bytes[1] &&
m_bytes[2] == other.m_bytes[2] &&
m_bytes[3] == other.m_bytes[3];
}
constexpr bool operator!=(ipv4_address other) const
{
return !(*this == other);
}
constexpr bool operator<(ipv4_address other) const
{
return to_integer() < other.to_integer();
}
constexpr bool operator>(ipv4_address other) const
{
return other < *this;
}
constexpr bool operator<=(ipv4_address other) const
{
return !(other < *this);
}
constexpr bool operator>=(ipv4_address other) const
{
return !(*this < other);
}
/// Parse a string representation of an IP address.
///
/// Parses strings of the form:
/// - "num.num.num.num" where num is an integer in range [0, 255].
/// - A single integer value in range [0, 2^32).
///
/// \param string
/// The string to parse.
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
///
/// \return
/// The IP address if successful, otherwise std::nullopt if the string
/// could not be parsed as an IPv4 address.
static std::optional<ipv4_address> from_string(std::string_view string) noexcept;
/// Convert the IP address to dotted decimal notation.
///
/// eg. "12.67.190.23"
std::string to_string() const;
private:
alignas(std::uint32_t) std::uint8_t m_bytes[4];
};
}
#endif
+82
View File
@@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ipv4_address.hpp>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro
{
namespace net
{
class ipv4_endpoint
{
public:
// Construct to 0.0.0.0:0
ipv4_endpoint() noexcept
: m_address()
, m_port(0)
{}
explicit ipv4_endpoint(ipv4_address address, std::uint16_t port = 0) noexcept
: m_address(address)
, m_port(port)
{}
const ipv4_address& address() const noexcept { return m_address; }
std::uint16_t port() const noexcept { return m_port; }
std::string to_string() const;
static std::optional<ipv4_endpoint> from_string(std::string_view string) noexcept;
private:
ipv4_address m_address;
std::uint16_t m_port;
};
inline bool operator==(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return a.address() == b.address() &&
a.port() == b.port();
}
inline bool operator!=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(a == b);
}
inline bool operator<(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return a.address() < b.address() ||
(a.address() == b.address() && a.port() < b.port());
}
inline bool operator>(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return b < a;
}
inline bool operator<=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(b < a);
}
inline bool operator>=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(a < b);
}
}
}
#endif
+245
View File
@@ -0,0 +1,245 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro::net
{
class ipv4_address;
class ipv6_address
{
using bytes_t = std::uint8_t[16];
public:
constexpr ipv6_address();
explicit constexpr ipv6_address(
std::uint64_t subnetPrefix,
std::uint64_t interfaceIdentifier);
constexpr ipv6_address(
std::uint16_t part0,
std::uint16_t part1,
std::uint16_t part2,
std::uint16_t part3,
std::uint16_t part4,
std::uint16_t part5,
std::uint16_t part6,
std::uint16_t part7);
explicit constexpr ipv6_address(
const std::uint16_t(&parts)[8]);
explicit constexpr ipv6_address(
const std::uint8_t(&bytes)[16]);
constexpr const bytes_t& bytes() const { return m_bytes; }
constexpr std::uint64_t subnet_prefix() const;
constexpr std::uint64_t interface_identifier() const;
/// Get the IPv6 unspedified address :: (all zeroes).
static constexpr ipv6_address unspecified();
/// Get the IPv6 loopback address ::1.
static constexpr ipv6_address loopback();
/// Parse a string representation of an IPv6 address.
///
/// \param string
/// The string to parse.
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
///
/// \return
/// The IP address if successful, otherwise std::nullopt if the string
/// could not be parsed as an IPv4 address.
static std::optional<ipv6_address> from_string(std::string_view string) noexcept;
/// Convert the IP address to contracted string form.
///
/// Address is broken up into 16-bit parts, with each part represended in 1-4
/// lower-case hexadecimal with leading zeroes omitted. Parts are separated
/// by separated by a ':'. The longest contiguous run of zero parts is contracted
/// to "::".
///
/// For example:
/// ipv6_address::unspecified() -> "::"
/// ipv6_address::loopback() -> "::1"
/// ipv6_address(0x0011223344556677, 0x8899aabbccddeeff) ->
/// "11:2233:4455:6677:8899:aabb:ccdd:eeff"
/// ipv6_address(0x0102030400000000, 0x003fc447ab991011) ->
/// "102:304::3f:c447:ab99:1011"
std::string to_string() const;
constexpr bool operator==(const ipv6_address& other) const;
constexpr bool operator!=(const ipv6_address& other) const;
constexpr bool operator<(const ipv6_address& other) const;
constexpr bool operator>(const ipv6_address& other) const;
constexpr bool operator<=(const ipv6_address& other) const;
constexpr bool operator>=(const ipv6_address& other) const;
private:
alignas(std::uint64_t) std::uint8_t m_bytes[16];
};
constexpr ipv6_address::ipv6_address()
: m_bytes{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 }
{}
constexpr ipv6_address::ipv6_address(
std::uint64_t subnetPrefix,
std::uint64_t interfaceIdentifier)
: m_bytes{
static_cast<std::uint8_t>(subnetPrefix >> 56),
static_cast<std::uint8_t>(subnetPrefix >> 48),
static_cast<std::uint8_t>(subnetPrefix >> 40),
static_cast<std::uint8_t>(subnetPrefix >> 32),
static_cast<std::uint8_t>(subnetPrefix >> 24),
static_cast<std::uint8_t>(subnetPrefix >> 16),
static_cast<std::uint8_t>(subnetPrefix >> 8),
static_cast<std::uint8_t>(subnetPrefix),
static_cast<std::uint8_t>(interfaceIdentifier >> 56),
static_cast<std::uint8_t>(interfaceIdentifier >> 48),
static_cast<std::uint8_t>(interfaceIdentifier >> 40),
static_cast<std::uint8_t>(interfaceIdentifier >> 32),
static_cast<std::uint8_t>(interfaceIdentifier >> 24),
static_cast<std::uint8_t>(interfaceIdentifier >> 16),
static_cast<std::uint8_t>(interfaceIdentifier >> 8),
static_cast<std::uint8_t>(interfaceIdentifier) }
{}
constexpr ipv6_address::ipv6_address(
std::uint16_t part0,
std::uint16_t part1,
std::uint16_t part2,
std::uint16_t part3,
std::uint16_t part4,
std::uint16_t part5,
std::uint16_t part6,
std::uint16_t part7)
: m_bytes{
static_cast<std::uint8_t>(part0 >> 8),
static_cast<std::uint8_t>(part0),
static_cast<std::uint8_t>(part1 >> 8),
static_cast<std::uint8_t>(part1),
static_cast<std::uint8_t>(part2 >> 8),
static_cast<std::uint8_t>(part2),
static_cast<std::uint8_t>(part3 >> 8),
static_cast<std::uint8_t>(part3),
static_cast<std::uint8_t>(part4 >> 8),
static_cast<std::uint8_t>(part4),
static_cast<std::uint8_t>(part5 >> 8),
static_cast<std::uint8_t>(part5),
static_cast<std::uint8_t>(part6 >> 8),
static_cast<std::uint8_t>(part6),
static_cast<std::uint8_t>(part7 >> 8),
static_cast<std::uint8_t>(part7) }
{}
constexpr ipv6_address::ipv6_address(
const std::uint16_t(&parts)[8])
: ipv6_address(
parts[0], parts[1], parts[2], parts[3],
parts[4], parts[5], parts[6], parts[7])
{}
constexpr ipv6_address::ipv6_address(const std::uint8_t(&bytes)[16])
: m_bytes{
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11],
bytes[12], bytes[13], bytes[14], bytes[15] }
{}
constexpr std::uint64_t ipv6_address::subnet_prefix() const
{
return
static_cast<std::uint64_t>(m_bytes[0]) << 56 |
static_cast<std::uint64_t>(m_bytes[1]) << 48 |
static_cast<std::uint64_t>(m_bytes[2]) << 40 |
static_cast<std::uint64_t>(m_bytes[3]) << 32 |
static_cast<std::uint64_t>(m_bytes[4]) << 24 |
static_cast<std::uint64_t>(m_bytes[5]) << 16 |
static_cast<std::uint64_t>(m_bytes[6]) << 8 |
static_cast<std::uint64_t>(m_bytes[7]);
}
constexpr std::uint64_t ipv6_address::interface_identifier() const
{
return
static_cast<std::uint64_t>(m_bytes[8]) << 56 |
static_cast<std::uint64_t>(m_bytes[9]) << 48 |
static_cast<std::uint64_t>(m_bytes[10]) << 40 |
static_cast<std::uint64_t>(m_bytes[11]) << 32 |
static_cast<std::uint64_t>(m_bytes[12]) << 24 |
static_cast<std::uint64_t>(m_bytes[13]) << 16 |
static_cast<std::uint64_t>(m_bytes[14]) << 8 |
static_cast<std::uint64_t>(m_bytes[15]);
}
constexpr ipv6_address ipv6_address::unspecified()
{
return ipv6_address{};
}
constexpr ipv6_address ipv6_address::loopback()
{
return ipv6_address{ 0, 0, 0, 0, 0, 0, 0, 1 };
}
constexpr bool ipv6_address::operator==(const ipv6_address& other) const
{
for (int i = 0; i < 16; ++i)
{
if (m_bytes[i] != other.m_bytes[i]) return false;
}
return true;
}
constexpr bool ipv6_address::operator!=(const ipv6_address& other) const
{
return !(*this == other);
}
constexpr bool ipv6_address::operator<(const ipv6_address& other) const
{
for (int i = 0; i < 16; ++i)
{
if (m_bytes[i] != other.m_bytes[i])
return m_bytes[i] < other.m_bytes[i];
}
return false;
}
constexpr bool ipv6_address::operator>(const ipv6_address& other) const
{
return (other < *this);
}
constexpr bool ipv6_address::operator<=(const ipv6_address& other) const
{
return !(other < *this);
}
constexpr bool ipv6_address::operator>=(const ipv6_address& other) const
{
return !(*this < other);
}
}
#endif
+82
View File
@@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ipv6_address.hpp>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro
{
namespace net
{
class ipv6_endpoint
{
public:
// Construct to [::]:0
ipv6_endpoint() noexcept
: m_address()
, m_port(0)
{}
explicit ipv6_endpoint(ipv6_address address, std::uint16_t port = 0) noexcept
: m_address(address)
, m_port(port)
{}
const ipv6_address& address() const noexcept { return m_address; }
std::uint16_t port() const noexcept { return m_port; }
std::string to_string() const;
static std::optional<ipv6_endpoint> from_string(std::string_view string) noexcept;
private:
ipv6_address m_address;
std::uint16_t m_port;
};
inline bool operator==(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return a.address() == b.address() &&
a.port() == b.port();
}
inline bool operator!=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(a == b);
}
inline bool operator<(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return a.address() < b.address() ||
(a.address() == b.address() && a.port() < b.port());
}
inline bool operator>(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return b < a;
}
inline bool operator<=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(b < a);
}
inline bool operator>=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(a < b);
}
}
}
#endif
+268
View File
@@ -0,0 +1,268 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cppcoro/net/socket_accept_operation.hpp>
#include <cppcoro/net/socket_connect_operation.hpp>
#include <cppcoro/net/socket_disconnect_operation.hpp>
#include <cppcoro/net/socket_recv_operation.hpp>
#include <cppcoro/net/socket_recv_from_operation.hpp>
#include <cppcoro/net/socket_send_operation.hpp>
#include <cppcoro/net/socket_send_to_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
namespace cppcoro
{
class io_service;
namespace net
{
class socket
{
public:
/// Create a socket that can be used to communicate using TCP/IPv4 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_tcpv4(io_service& ioSvc);
/// Create a socket that can be used to communicate using TCP/IPv6 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_tcpv6(io_service& ioSvc);
/// Create a socket that can be used to communicate using UDP/IPv4 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_udpv4(io_service& ioSvc);
/// Create a socket that can be used to communicate using UDP/IPv6 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_udpv6(io_service& ioSvc);
socket(socket&& other) noexcept;
/// Closes the socket, releasing any associated resources.
///
/// If the socket still has an open connection then the connection will be
/// reset. The destructor will not block waiting for queueud data to be sent.
/// If you need to ensure that queued data is delivered then you must call
/// disconnect() and wait until the disconnect operation completes.
~socket();
socket& operator=(socket&& other) noexcept;
#if CPPCORO_OS_WINNT
/// Get the Win32 socket handle assocaited with this socket.
cppcoro::detail::win32::socket_t native_handle() noexcept { return m_handle; }
/// Query whether I/O operations that complete synchronously will skip posting
/// an I/O completion event to the I/O completion port.
///
/// The operation class implementations can use this to determine whether or not
/// it should immediately resume the coroutine on the current thread upon an
/// operation completing synchronously or whether it should suspend the coroutine
/// and wait until the I/O completion event is dispatched to an I/O thread.
bool skip_completion_on_success() noexcept { return m_skipCompletionOnSuccess; }
#endif
/// Get the address and port of the local end-point.
///
/// If the socket is not bound then this will be the unspecified end-point
/// of the socket's associated address-family.
const ip_endpoint& local_endpoint() const noexcept { return m_localEndPoint; }
/// Get the address and port of the remote end-point.
///
/// If the socket is not in the connected state then this will be the unspecified
/// end-point of the socket's associated address-family.
const ip_endpoint& remote_endpoint() const noexcept { return m_remoteEndPoint; }
/// Bind the local end of this socket to the specified local end-point.
///
/// \param localEndPoint
/// The end-point to bind to.
/// This can be either an unspecified address (in which case it binds to all available
/// interfaces) and/or an unspecified port (in which case a random port is allocated).
///
/// \throws std::system_error
/// If the socket could not be bound for some reason.
void bind(const ip_endpoint& localEndPoint);
/// Put the socket into a passive listening state that will start acknowledging
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
///
/// The backlog of connections ready to be accepted will be set to some default
/// suitable large value, depending on the network provider. If you need more
/// control over the size of the queue then use the overload of listen()
/// that accepts a 'backlog' parameter.
///
/// \throws std::system_error
/// If the socket could not be placed into a listening mode.
void listen();
/// Put the socket into a passive listening state that will start acknowledging
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
///
/// \param backlog
/// The maximum number of pending connections to allow in the queue of ready-to-accept
/// connections.
///
/// \throws std::system_error
/// If the socket could not be placed into a listening mode.
void listen(std::uint32_t backlog);
/// Connect the socket to the specified remote end-point.
///
/// The socket must be in a bound but unconnected state prior to this call.
///
/// \param remoteEndPoint
/// The IP address and port-number to connect to.
///
/// \return
/// An awaitable object that must be co_await'ed to perform the async connect
/// operation. The result of the co_await expression is type void.
[[nodiscard]]
socket_connect_operation connect(const ip_endpoint& remoteEndPoint) noexcept;
/// Connect to the specified remote end-point.
///
/// \param remoteEndPoint
/// The IP address and port of the remote end-point to connect to.
///
/// \param ct
/// A cancellation token that can be used to communicate a request to
/// later cancel the operation. If the operation is successfully
/// cancelled then it will complete by throwing a cppcoro::operation_cancelled
/// exception.
///
/// \return
/// An awaitable object that will start the connect operation when co_await'ed
/// and will suspend the coroutine, resuming it when the operation completes.
/// The result of the co_await expression has type 'void'.
[[nodiscard]]
socket_connect_operation_cancellable connect(
const ip_endpoint& remoteEndPoint,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_accept_operation accept(socket& acceptingSocket) noexcept;
[[nodiscard]]
socket_accept_operation_cancellable accept(
socket& acceptingSocket,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_disconnect_operation disconnect() noexcept;
[[nodiscard]]
socket_disconnect_operation_cancellable disconnect(cancellation_token ct) noexcept;
[[nodiscard]]
socket_send_operation send(
const void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_send_operation_cancellable send(
const void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_recv_operation recv(
void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_recv_operation_cancellable recv(
void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_recv_from_operation recv_from(
void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_recv_from_operation_cancellable recv_from(
void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_send_to_operation send_to(
const ip_endpoint& destination,
const void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_send_to_operation_cancellable send_to(
const ip_endpoint& destination,
const void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
void close_send();
void close_recv();
private:
friend class socket_accept_operation_impl;
friend class socket_connect_operation_impl;
#if CPPCORO_OS_WINNT
explicit socket(
cppcoro::detail::win32::socket_t handle,
bool skipCompletionOnSuccess) noexcept;
#endif
#if CPPCORO_OS_WINNT
cppcoro::detail::win32::socket_t m_handle;
bool m_skipCompletionOnSuccess;
#endif
ip_endpoint m_localEndPoint;
ip_endpoint m_remoteEndPoint;
};
}
}
#endif
@@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/cancellation_registration.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
# include <atomic>
# include <optional>
namespace cppcoro
{
namespace net
{
class socket;
class socket_accept_operation_impl
{
public:
socket_accept_operation_impl(
socket& listeningSocket,
socket& acceptingSocket) noexcept
: m_listeningSocket(listeningSocket)
, m_acceptingSocket(acceptingSocket)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // Structure padded due to alignment
#endif
socket& m_listeningSocket;
socket& m_acceptingSocket;
alignas(8) std::uint8_t m_addressBuffer[88];
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
class socket_accept_operation
: public cppcoro::detail::win32_overlapped_operation<socket_accept_operation>
{
public:
socket_accept_operation(
socket& listeningSocket,
socket& acceptingSocket) noexcept
: m_impl(listeningSocket, acceptingSocket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_accept_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void get_result() { m_impl.get_result(*this); }
socket_accept_operation_impl m_impl;
};
class socket_accept_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>
{
public:
socket_accept_operation_cancellable(
socket& listeningSocket,
socket& acceptingSocket,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>(std::move(ct))
, m_impl(listeningSocket, acceptingSocket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_accept_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,95 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
namespace net
{
class socket;
class socket_connect_operation_impl
{
public:
socket_connect_operation_impl(
socket& socket,
const ip_endpoint& remoteEndPoint) noexcept
: m_socket(socket)
, m_remoteEndPoint(remoteEndPoint)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
ip_endpoint m_remoteEndPoint;
};
class socket_connect_operation
: public cppcoro::detail::win32_overlapped_operation<socket_connect_operation>
{
public:
socket_connect_operation(
socket& socket,
const ip_endpoint& remoteEndPoint) noexcept
: m_impl(socket, remoteEndPoint)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_connect_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_connect_operation_impl m_impl;
};
class socket_connect_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>
{
public:
socket_connect_operation_cancellable(
socket& socket,
const ip_endpoint& remoteEndPoint,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>(std::move(ct))
, m_impl(socket, remoteEndPoint)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_connect_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,85 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
namespace net
{
class socket;
class socket_disconnect_operation_impl
{
public:
socket_disconnect_operation_impl(socket& socket) noexcept
: m_socket(socket)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
};
class socket_disconnect_operation
: public cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>
{
public:
socket_disconnect_operation(socket& socket) noexcept
: m_impl(socket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void get_result() { m_impl.get_result(*this); }
socket_disconnect_operation_impl m_impl;
};
class socket_disconnect_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>
{
public:
socket_disconnect_operation_cancellable(socket& socket, cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>(std::move(ct))
, m_impl(socket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_disconnect_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,106 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cstdint>
#include <tuple>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_recv_from_operation_impl
{
public:
socket_recv_from_operation_impl(
socket& socket,
void* buffer,
std::size_t byteCount) noexcept
: m_socket(socket)
, m_buffer(buffer, byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
std::tuple<std::size_t, ip_endpoint> get_result(
cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
static constexpr std::size_t sockaddrStorageAlignment = 4;
// Storage suitable for either SOCKADDR_IN or SOCKADDR_IN6
alignas(sockaddrStorageAlignment) std::uint8_t m_sourceSockaddrStorage[28];
int m_sourceSockaddrLength;
};
class socket_recv_from_operation
: public cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>
{
public:
socket_recv_from_operation(
socket& socket,
void* buffer,
std::size_t byteCount) noexcept
: m_impl(socket, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_recv_from_operation_impl m_impl;
};
class socket_recv_from_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>
{
public:
socket_recv_from_operation_cancellable(
socket& socket,
void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>(std::move(ct))
, m_impl(socket, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_recv_from_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_recv_operation_impl
{
public:
socket_recv_operation_impl(
socket& s,
void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_buffer(buffer, byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_recv_operation
: public cppcoro::detail::win32_overlapped_operation<socket_recv_operation>
{
public:
socket_recv_operation(
socket& s,
void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_recv_operation_impl m_impl;
};
class socket_recv_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>
{
public:
socket_recv_operation_cancellable(
socket& s,
void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>(std::move(ct))
, m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
socket_recv_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_send_operation_impl
{
public:
socket_send_operation_impl(
socket& s,
const void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_buffer(const_cast<void*>(buffer), byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_send_operation
: public cppcoro::detail::win32_overlapped_operation<socket_send_operation>
{
public:
socket_send_operation(
socket& s,
const void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_send_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_send_operation_impl m_impl;
};
class socket_send_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>
{
public:
socket_send_operation_cancellable(
socket& s,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>(std::move(ct))
, m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { return m_impl.cancel(*this); }
socket_send_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_send_to_operation_impl
{
public:
socket_send_to_operation_impl(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_destination(destination)
, m_buffer(const_cast<void*>(buffer), byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
ip_endpoint m_destination;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_send_to_operation
: public cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>
{
public:
socket_send_to_operation(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, destination, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_send_to_operation_impl m_impl;
};
class socket_send_to_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>
{
public:
socket_send_to_operation_cancellable(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>(std::move(ct))
, m_impl(s, destination, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { return m_impl.cancel(*this); }
socket_send_to_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
+147
View File
@@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
#define CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
#include <type_traits>
#include <exception>
namespace cppcoro
{
template<typename FUNC>
class scoped_lambda
{
public:
scoped_lambda(FUNC&& func)
: m_func(std::forward<FUNC>(func))
, m_cancelled(false)
{}
scoped_lambda(const scoped_lambda& other) = delete;
scoped_lambda(scoped_lambda&& other)
: m_func(std::forward<FUNC>(other.m_func))
, m_cancelled(other.m_cancelled)
{
other.cancel();
}
~scoped_lambda()
{
if (!m_cancelled)
{
m_func();
}
}
void cancel()
{
m_cancelled = true;
}
void call_now()
{
m_cancelled = true;
m_func();
}
private:
FUNC m_func;
bool m_cancelled;
};
/// A scoped lambda that executes the lambda when the object destructs
/// but only if exiting due to an exception (CALL_ON_FAILURE = true) or
/// only if not exiting due to an exception (CALL_ON_FAILURE = false).
template<typename FUNC, bool CALL_ON_FAILURE>
class conditional_scoped_lambda
{
public:
conditional_scoped_lambda(FUNC&& func)
: m_func(std::forward<FUNC>(func))
, m_uncaughtExceptionCount(std::uncaught_exceptions())
, m_cancelled(false)
{}
conditional_scoped_lambda(const conditional_scoped_lambda& other) = delete;
conditional_scoped_lambda(conditional_scoped_lambda&& other)
noexcept(std::is_nothrow_move_constructible<FUNC>::value)
: m_func(std::forward<FUNC>(other.m_func))
, m_uncaughtExceptionCount(other.m_uncaughtExceptionCount)
, m_cancelled(other.m_cancelled)
{
other.cancel();
}
~conditional_scoped_lambda() noexcept(CALL_ON_FAILURE || noexcept(std::declval<FUNC>()()))
{
if (!m_cancelled && (is_unwinding_due_to_exception() == CALL_ON_FAILURE))
{
m_func();
}
}
void cancel() noexcept
{
m_cancelled = true;
}
private:
bool is_unwinding_due_to_exception() const noexcept
{
return std::uncaught_exceptions() > m_uncaughtExceptionCount;
}
FUNC m_func;
int m_uncaughtExceptionCount;
bool m_cancelled;
};
/// Returns an object that calls the provided function when it goes out
/// of scope either normally or due to an uncaught exception unwinding
/// the stack.
///
/// \param func
/// The function to call when the scope exits.
/// The function must be noexcept.
template<typename FUNC>
auto on_scope_exit(FUNC&& func)
{
return scoped_lambda<FUNC>{ std::forward<FUNC>(func) };
}
/// Returns an object that calls the provided function when it goes out
/// of scope due to an uncaught exception unwinding the stack.
///
/// \param func
/// The function to be called if unwinding due to an exception.
/// The function must be noexcept.
template<typename FUNC>
auto on_scope_failure(FUNC&& func)
{
return conditional_scoped_lambda<FUNC, true>{ std::forward<FUNC>(func) };
}
/// Returns an object that calls the provided function when it goes out
/// of scope via normal execution (ie. not unwinding due to an exception).
///
/// \param func
/// The function to call if the scope exits normally.
/// The function does not necessarily need to be noexcept.
template<typename FUNC>
auto on_scope_success(FUNC&& func)
{
return conditional_scoped_lambda<FUNC, false>{ std::forward<FUNC>(func) };
}
}
#endif
+24
View File
@@ -0,0 +1,24 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
#define CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
#include <exception>
namespace cppcoro
{
class operation_cancelled : public std::exception
{
public:
operation_cancelled() noexcept
: std::exception()
{}
const char* what() const noexcept override { return "operation cancelled"; }
};
}
#endif
+59
View File
@@ -0,0 +1,59 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
#define CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
#include <cppcoro/readable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class read_only_file : public readable_file
{
public:
/// Open a file for read-only access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous read operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param path
/// Path of the file to open.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to read from the file.
///
/// \throw std::system_error
/// If the file could not be opened for read.
[[nodiscard]]
static read_only_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_share_mode shareMode = file_share_mode::read,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
read_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif
+66
View File
@@ -0,0 +1,66 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
#define CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
#include <cppcoro/readable_file.hpp>
#include <cppcoro/writable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class read_write_file : public readable_file, public writable_file
{
public:
/// Open a file for read-write access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous write operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param pathMode
/// Path of the file to open.
///
/// \param openMode
/// Specifies how the file should be opened and how to handle cases
/// when the file exists or doesn't exist.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to write to the file.
///
/// \throw std::system_error
/// If the file could not be opened for write.
[[nodiscard]]
static read_write_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
read_write_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif
+65
View File
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READABLE_FILE_HPP_INCLUDED
#define CPPCORO_READABLE_FILE_HPP_INCLUDED
#include <cppcoro/file.hpp>
#include <cppcoro/file_read_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
namespace cppcoro
{
class readable_file : virtual public file
{
public:
/// Read some data from the file.
///
/// Reads \a byteCount bytes from the file starting at \a offset
/// into the specified \a buffer.
///
/// \param offset
/// The offset within the file to start reading from.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the offset must be a multiple of the file-system's sector size.
///
/// \param buffer
/// The buffer to read the file contents into.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the address of the start of the buffer must be a multiple of
/// the file-system's sector size.
///
/// \param byteCount
/// The number of bytes to read from the file.
/// If the file has been opeend using file_buffering_mode::unbuffered
/// then the byteCount must be a multiple of the file-system's sector size.
///
/// \param ct
/// An optional cancellation_token that can be used to cancel the
/// read operation before it completes.
///
/// \return
/// An object that represents the read-operation.
/// This object must be co_await'ed to start the read operation.
[[nodiscard]]
file_read_operation read(
std::uint64_t offset,
void* buffer,
std::size_t byteCount) const noexcept;
[[nodiscard]]
file_read_operation_cancellable read(
std::uint64_t offset,
void* buffer,
std::size_t byteCount,
cancellation_token ct) const noexcept;
protected:
using file::file;
};
}
#endif
+345
View File
@@ -0,0 +1,345 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
#define CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
#include <cppcoro/generator.hpp>
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <utility>
#include <cassert>
#include <functional>
namespace cppcoro
{
template<typename T>
class [[nodiscard]] recursive_generator
{
public:
class promise_type final
{
public:
promise_type() noexcept
: m_value(nullptr)
, m_exception(nullptr)
, m_root(this)
, m_parentOrLeaf(this)
{}
promise_type(const promise_type&) = delete;
promise_type(promise_type&&) = delete;
auto get_return_object() noexcept
{
return recursive_generator<T>{ *this };
}
cppcoro::suspend_always initial_suspend() noexcept
{
return {};
}
cppcoro::suspend_always final_suspend() noexcept
{
return {};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept {}
cppcoro::suspend_always yield_value(T& value) noexcept
{
m_value = std::addressof(value);
return {};
}
cppcoro::suspend_always yield_value(T&& value) noexcept
{
m_value = std::addressof(value);
return {};
}
auto yield_value(recursive_generator&& generator) noexcept
{
return yield_value(generator);
}
auto yield_value(recursive_generator& generator) noexcept
{
struct awaitable
{
awaitable(promise_type* childPromise)
: m_childPromise(childPromise)
{}
bool await_ready() noexcept
{
return this->m_childPromise == nullptr;
}
void await_suspend(cppcoro::coroutine_handle<promise_type>) noexcept
{}
void await_resume()
{
if (this->m_childPromise != nullptr)
{
this->m_childPromise->throw_if_exception();
}
}
private:
promise_type* m_childPromise;
};
if (generator.m_promise != nullptr)
{
m_root->m_parentOrLeaf = generator.m_promise;
generator.m_promise->m_root = m_root;
generator.m_promise->m_parentOrLeaf = this;
generator.m_promise->resume();
if (!generator.m_promise->is_complete())
{
return awaitable{ generator.m_promise };
}
m_root->m_parentOrLeaf = this;
}
return awaitable{ nullptr };
}
// Don't allow any use of 'co_await' inside the recursive_generator coroutine.
template<typename U>
cppcoro::suspend_never await_transform(U&& value) = delete;
void destroy() noexcept
{
cppcoro::coroutine_handle<promise_type>::from_promise(*this).destroy();
}
void throw_if_exception()
{
if (m_exception != nullptr)
{
std::rethrow_exception(std::move(m_exception));
}
}
bool is_complete() noexcept
{
return cppcoro::coroutine_handle<promise_type>::from_promise(*this).done();
}
T& value() noexcept
{
assert(this == m_root);
assert(!is_complete());
return *(m_parentOrLeaf->m_value);
}
void pull() noexcept
{
assert(this == m_root);
assert(!m_parentOrLeaf->is_complete());
m_parentOrLeaf->resume();
while (m_parentOrLeaf != this && m_parentOrLeaf->is_complete())
{
m_parentOrLeaf = m_parentOrLeaf->m_parentOrLeaf;
m_parentOrLeaf->resume();
}
}
private:
void resume() noexcept
{
cppcoro::coroutine_handle<promise_type>::from_promise(*this).resume();
}
std::add_pointer_t<T> m_value;
std::exception_ptr m_exception;
promise_type* m_root;
// If this is the promise of the root generator then this field
// is a pointer to the leaf promise.
// For non-root generators this is a pointer to the parent promise.
promise_type* m_parentOrLeaf;
};
recursive_generator() noexcept
: m_promise(nullptr)
{}
recursive_generator(promise_type& promise) noexcept
: m_promise(&promise)
{}
recursive_generator(recursive_generator&& other) noexcept
: m_promise(other.m_promise)
{
other.m_promise = nullptr;
}
recursive_generator(const recursive_generator& other) = delete;
recursive_generator& operator=(const recursive_generator& other) = delete;
~recursive_generator()
{
if (m_promise != nullptr)
{
m_promise->destroy();
}
}
recursive_generator& operator=(recursive_generator&& other) noexcept
{
if (this != &other)
{
if (m_promise != nullptr)
{
m_promise->destroy();
}
m_promise = other.m_promise;
other.m_promise = nullptr;
}
return *this;
}
class iterator
{
public:
using iterator_category = std::input_iterator_tag;
// What type should we use for counting elements of a potentially infinite sequence?
using difference_type = std::ptrdiff_t;
using value_type = std::remove_reference_t<T>;
using reference = std::conditional_t<std::is_reference_v<T>, T, T&>;
using pointer = std::add_pointer_t<T>;
iterator() noexcept
: m_promise(nullptr)
{}
explicit iterator(promise_type* promise) noexcept
: m_promise(promise)
{}
bool operator==(const iterator& other) const noexcept
{
return m_promise == other.m_promise;
}
bool operator!=(const iterator& other) const noexcept
{
return m_promise != other.m_promise;
}
iterator& operator++()
{
assert(m_promise != nullptr);
assert(!m_promise->is_complete());
m_promise->pull();
if (m_promise->is_complete())
{
auto* temp = m_promise;
m_promise = nullptr;
temp->throw_if_exception();
}
return *this;
}
void operator++(int)
{
(void)operator++();
}
reference operator*() const noexcept
{
assert(m_promise != nullptr);
return static_cast<reference>(m_promise->value());
}
pointer operator->() const noexcept
{
return std::addressof(operator*());
}
private:
promise_type* m_promise;
};
iterator begin()
{
if (m_promise != nullptr)
{
m_promise->pull();
if (!m_promise->is_complete())
{
return iterator(m_promise);
}
m_promise->throw_if_exception();
}
return iterator(nullptr);
}
iterator end() noexcept
{
return iterator(nullptr);
}
void swap(recursive_generator& other) noexcept
{
std::swap(m_promise, other.m_promise);
}
private:
friend class promise_type;
promise_type* m_promise;
};
template<typename T>
void swap(recursive_generator<T>& a, recursive_generator<T>& b) noexcept
{
a.swap(b);
}
// Note: When applying fmap operator to a recursive_generator we just yield a non-recursive
// generator since we generally won't be using the result in a recursive context.
template<typename FUNC, typename T>
generator<std::invoke_result_t<FUNC&, typename recursive_generator<T>::iterator::reference>> fmap(FUNC func, recursive_generator<T> source)
{
for (auto&& value : source)
{
co_yield std::invoke(func, static_cast<decltype(value)>(value));
}
}
}
#endif
+129
View File
@@ -0,0 +1,129 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_RESUME_ON_HPP_INCLUDED
#define CPPCORO_RESUME_ON_HPP_INCLUDED
#include <cppcoro/task.hpp>
#include <cppcoro/async_generator.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/get_awaiter.hpp>
#include <exception>
#include <type_traits>
namespace cppcoro
{
template<typename SCHEDULER>
struct resume_on_transform
{
explicit resume_on_transform(SCHEDULER& s) noexcept
: scheduler(s)
{}
SCHEDULER& scheduler;
};
template<typename SCHEDULER>
resume_on_transform<SCHEDULER> resume_on(SCHEDULER& scheduler) noexcept
{
return resume_on_transform<SCHEDULER>(scheduler);
}
template<typename T, typename SCHEDULER>
decltype(auto) operator|(T&& value, resume_on_transform<SCHEDULER> transform)
{
return resume_on(transform.scheduler, std::forward<T>(value));
}
template<
typename SCHEDULER,
typename AWAITABLE,
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<AWAIT_RESULT>
{
bool rescheduled = false;
std::exception_ptr ex;
try
{
// We manually get the awaiter here so that we can keep
// it alive across the call to `scheduler.schedule()`
// just in case the result is a reference to a value
// in the awaiter that would otherwise be a temporary
// and destructed before the value could be returned.
auto&& awaiter = detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable));
auto&& result = co_await static_cast<decltype(awaiter)>(awaiter);
// Flag as rescheduled before scheduling in case it is the
// schedule() operation that throws an exception as we don't
// want to attempt to schedule twice if scheduling fails.
rescheduled = true;
co_await scheduler.schedule();
co_return static_cast<decltype(result)>(result);
}
catch (...)
{
ex = std::current_exception();
}
// We still want to resume on the scheduler even in the presence
// of an exception.
if (!rescheduled)
{
co_await scheduler.schedule();
}
std::rethrow_exception(ex);
}
template<
typename SCHEDULER,
typename AWAITABLE,
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<>
{
std::exception_ptr ex;
try
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
catch (...)
{
ex = std::current_exception();
}
// NOTE: We're assuming that `schedule()` operation is noexcept
// here. If it were to throw what would we do if 'ex' was non-null?
// Presumably we'd treat it the same as throwing an exception while
// unwinding and call std::terminate()?
co_await scheduler.schedule();
if (ex)
{
std::rethrow_exception(ex);
}
}
template<typename SCHEDULER, typename T>
async_generator<T> resume_on(SCHEDULER& scheduler, async_generator<T> source)
{
for (auto iter = co_await source.begin(); iter != source.end(); co_await ++iter)
{
auto& value = *iter;
co_await scheduler.schedule();
co_yield value;
}
}
}
#endif
+124
View File
@@ -0,0 +1,124 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
#define CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/coroutine.hpp>
#include <array>
#include <cassert>
#include <algorithm>
#include <utility>
namespace cppcoro
{
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
/// This is a scheduler class that schedules coroutines in a round-robin
/// fashion once N coroutines have been scheduled to it.
///
/// Only supports access from a single thread at a time so
///
/// This implementation was inspired by Gor Nishanov's CppCon 2018 talk
/// about nano-coroutines.
///
/// The implementation relies on symmetric transfer and noop_coroutine()
/// and so only works with a relatively recent version of Clang and does
/// not yet work with MSVC.
template<size_t N>
class round_robin_scheduler
{
static_assert(
N >= 2,
"Round robin scheduler must be configured to support at least two coroutines");
class schedule_operation
{
public:
explicit schedule_operation(round_robin_scheduler& s) noexcept : m_scheduler(s) {}
bool await_ready() noexcept
{
return false;
}
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_scheduler.exchange_next(awaitingCoroutine);
}
void await_resume() noexcept {}
private:
round_robin_scheduler& m_scheduler;
};
friend class schedule_operation;
public:
round_robin_scheduler() noexcept
: m_index(0)
, m_noop(cppcoro::noop_coroutine())
{
for (size_t i = 0; i < N - 1; ++i)
{
m_coroutines[i] = m_noop();
}
}
~round_robin_scheduler()
{
// All tasks should have been joined before calling destructor.
assert(std::all_of(
m_coroutines.begin(),
m_coroutines.end(),
[&](auto h) { return h == m_noop; }));
}
schedule_operation schedule() noexcept
{
return schedule_operation{ *this };
}
/// Resume any queued coroutines until there are no more coroutines.
void drain() noexcept
{
size_t countRemaining = N - 1;
do
{
auto nextToResume = exchange_next(m_noop);
if (nextToResume != m_noop)
{
nextToResume.resume();
countRemaining = N - 1;
}
else
{
--countRemaining;
}
} while (countRemaining > 0);
}
private:
cppcoro::coroutine_handle exchange_next(
cppcoro::coroutine_handle<> coroutine) noexcept
{
auto coroutineToResume = std::exchange(
m_scheduler.m_coroutines[m_scheduler.m_index],
awaitingCoroutine);
m_scheduler.m_index = m_scheduler.m_index < (N - 2) ? m_scheduler.m_index + 1 : 0;
return coroutineToResume;
}
size_t m_index;
const cppcoro::coroutine_handle<> m_noop;
std::array<cppcoro::coroutine_handle<>, N - 1> m_coroutines;
};
#endif
}
#endif
+69
View File
@@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SCHEDULE_ON_HPP_INCLUDED
#define CPPCORO_SCHEDULE_ON_HPP_INCLUDED
#include <cppcoro/task.hpp>
#include <cppcoro/shared_task.hpp>
#include <cppcoro/async_generator.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
namespace cppcoro
{
template<typename SCHEDULER>
struct schedule_on_transform
{
explicit schedule_on_transform(SCHEDULER& scheduler) noexcept
: scheduler(scheduler)
{}
SCHEDULER& scheduler;
};
template<typename SCHEDULER>
schedule_on_transform<SCHEDULER> schedule_on(SCHEDULER& scheduler)
{
return schedule_on_transform<SCHEDULER>{ scheduler };
}
template<typename T, typename SCHEDULER>
decltype(auto) operator|(T&& value, schedule_on_transform<SCHEDULER> transform)
{
return schedule_on(transform.scheduler, std::forward<T>(value));
}
template<typename SCHEDULER, typename AWAITABLE>
auto schedule_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_await scheduler.schedule();
co_return co_await std::move(awaitable);
}
template<typename T, typename SCHEDULER>
async_generator<T> schedule_on(SCHEDULER& scheduler, async_generator<T> source)
{
// Transfer exection to the scheduler before the implicit calls to
// 'co_await begin()' or subsequent calls to `co_await iterator::operator++()`
// below. This ensures that all calls to the generator's coroutine_handle<>::resume()
// are executed on the execution context of the scheduler.
co_await scheduler.schedule();
const auto itEnd = source.end();
auto it = co_await source.begin();
while (it != itEnd)
{
co_yield *it;
co_await scheduler.schedule();
(void)co_await ++it;
}
}
}
#endif
+470
View File
@@ -0,0 +1,470 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
#define CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/sequence_traits.hpp>
#include <cppcoro/detail/manual_lifetime.hpp>
#include <atomic>
#include <cassert>
#include <cstdint>
#include <limits>
#include <optional>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS>
class sequence_barrier_wait_operation_base;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class sequence_barrier_wait_operation;
/// A sequence barrier is a synchronisation primitive that allows a single-producer
/// and multiple-consumers to coordinate with respect to a monotonically increasing
/// sequence number.
///
/// A single producer advances the sequence number by publishing new sequence numbers in a
/// monotonically increasing order. One or more consumers can query the last-published
/// sequence number and can wait until a particular sequence number has been published.
///
/// A sequence barrier can be used to represent a cursor into a thread-safe producer/consumer
/// ring-buffer.
///
/// See the LMAX Disruptor pattern for more background:
/// https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class sequence_barrier
{
static_assert(
std::is_integral_v<SEQUENCE>,
"sequence_barrier requires an integral sequence type");
using awaiter_t = sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
public:
/// Construct a sequence barrier with the specified initial sequence number
/// as the initial value 'last_published()'.
sequence_barrier(SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
: m_lastPublished(initialSequence)
, m_awaiters(nullptr)
{}
~sequence_barrier()
{
// Shouldn't be destructing a sequence barrier if there are still waiters.
assert(m_awaiters.load(std::memory_order_relaxed) == nullptr);
}
/// Query the sequence number that was most recently published by the producer.
///
/// You can assume that all sequence numbers prior to the returned sequence number
/// have also been published. This means you can safely access all elements with
/// sequence numbers up to and including the returned sequence number without any
/// further synchronisation.
SEQUENCE last_published() const noexcept
{
return m_lastPublished.load(std::memory_order_acquire);
}
/// Wait until a particular sequence number has been published.
///
/// If the specified sequence number is not yet published then the awaiting coroutine
/// will be suspended and later resumed inside the call to publish() that publishes
/// the specified sequence number.
///
/// \param targetSequence
/// The sequence number to wait for.
///
/// \return
/// An awaitable that when co_await'ed will suspend the awaiting coroutine until
/// the specified target sequence number has been published.
/// The result of the co_await expression will be the last-known published sequence
/// number. This is guaranteed not to precede \p targetSequence but may be a sequence
/// number after \p targetSequence, which indicates that more elements have been
/// published than you were waiting for.
template<typename SCHEDULER>
[[nodiscard]]
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept;
/// Publish the specified sequence number to consumers.
///
/// This publishes all sequence numbers up to and including the specified sequence
/// number. This will resume any coroutine that was suspended waiting for a sequence
/// number that was published by this operation.
///
/// \param sequence
/// The sequence number to publish. This number must not precede the current
/// last_published() value. ie. the published sequence numbers must be monotonically
/// increasing.
void publish(SEQUENCE sequence) noexcept;
private:
friend class sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
void add_awaiter(awaiter_t* awaiter) const noexcept;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
// First cache-line is written to by the producer only
alignas(CPPCORO_CPU_CACHE_LINE)
std::atomic<SEQUENCE> m_lastPublished;
// Second cache-line is written to by both the producer and consumers
alignas(CPPCORO_CPU_CACHE_LINE)
mutable std::atomic<awaiter_t*> m_awaiters;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS>
class sequence_barrier_wait_operation_base
{
public:
explicit sequence_barrier_wait_operation_base(
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
SEQUENCE targetSequence) noexcept
: m_barrier(barrier)
, m_targetSequence(targetSequence)
, m_lastKnownPublished(barrier.last_published())
, m_readyToResume(false)
{}
sequence_barrier_wait_operation_base(
const sequence_barrier_wait_operation_base& other) noexcept
: m_barrier(other.m_barrier)
, m_targetSequence(other.m_targetSequence)
, m_lastKnownPublished(other.m_lastKnownPublished)
, m_readyToResume(false)
{}
bool await_ready() const noexcept
{
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
m_barrier.add_awaiter(this);
return !m_readyToResume.exchange(true, std::memory_order_acquire);
}
SEQUENCE await_resume() noexcept
{
return m_lastKnownPublished;
}
protected:
friend class sequence_barrier<SEQUENCE, TRAITS>;
void resume() noexcept
{
// This synchronises with the exchange(true, std::memory_order_acquire) in await_suspend().
if (m_readyToResume.exchange(true, std::memory_order_release))
{
resume_impl();
}
}
virtual void resume_impl() noexcept = 0;
const sequence_barrier<SEQUENCE, TRAITS>& m_barrier;
const SEQUENCE m_targetSequence;
SEQUENCE m_lastKnownPublished;
sequence_barrier_wait_operation_base* m_next;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
std::atomic<bool> m_readyToResume;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class sequence_barrier_wait_operation : public sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>
{
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
public:
sequence_barrier_wait_operation(
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
SEQUENCE targetSequence,
SCHEDULER& scheduler) noexcept
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(barrier, targetSequence)
, m_scheduler(scheduler)
{}
sequence_barrier_wait_operation(
const sequence_barrier_wait_operation& other) noexcept
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(other)
, m_scheduler(other.m_scheduler)
{}
~sequence_barrier_wait_operation()
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter.destruct();
}
if (m_isScheduleOperationCreated)
{
m_scheduleOperation.destruct();
}
}
decltype(auto) await_resume() noexcept(noexcept(m_scheduleAwaiter->await_resume()))
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter->await_resume();
}
return sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
}
private:
void resume_impl() noexcept override
{
try
{
m_scheduleOperation.construct(m_scheduler.schedule());
m_isScheduleOperationCreated = true;
m_scheduleAwaiter.construct(detail::get_awaiter(
static_cast<schedule_operation&&>(*m_scheduleOperation)));
m_isScheduleAwaiterCreated = true;
if (!m_scheduleAwaiter->await_ready())
{
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
if constexpr (std::is_void_v<await_suspend_result_t>)
{
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
return;
}
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
{
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
{
return;
}
}
else
{
// Assume it returns a coroutine_handle.
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
return;
}
}
}
catch (...)
{
// Ignore failure to reschedule and resume inline?
// Should we catch the exception and rethrow from await_resume()?
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
}
// Resume outside the catch-block.
this->m_awaitingCoroutine.resume();
}
SCHEDULER& m_scheduler;
// Can't use std::optional<T> here since T could be a reference.
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
bool m_isScheduleOperationCreated = false;
bool m_isScheduleAwaiterCreated = false;
};
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> sequence_barrier<SEQUENCE, TRAITS>::wait_until_published(
SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept
{
return sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, targetSequence, scheduler);
}
template<typename SEQUENCE, typename TRAITS>
void sequence_barrier<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
{
m_lastPublished.store(sequence, std::memory_order_seq_cst);
// Cheaper check to see if there are any awaiting coroutines.
auto* awaiters = m_awaiters.load(std::memory_order_seq_cst);
if (awaiters == nullptr)
{
return;
}
// Acquire the list of awaiters.
// Note we may be racing with add_awaiter() which could also acquire the list of waiters
// so we need to check again whether we won the race and acquired the list.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
if (awaiters == nullptr)
{
return;
}
// Check the list of awaiters for ones that are now satisfied by the sequence number
// we just published. Awaiters are added to either the 'awaitersToResume' list or to
// the 'awaitersToRequeue' list.
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
awaiter_t* awaitersToRequeue;
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
do
{
if (TRAITS::precedes(sequence, awaiters->m_targetSequence))
{
// Target sequence not reached. Append to 'requeue' list.
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
}
else
{
// Target sequence reached. Append to 'resume' list.
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
} while (awaiters != nullptr);
// Null-terminate the two lists.
*awaitersToRequeueTail = nullptr;
*awaitersToResumeTail = nullptr;
if (awaitersToRequeue != nullptr)
{
awaiter_t* oldHead = nullptr;
while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToRequeue,
std::memory_order_release,
std::memory_order_relaxed))
{
*awaitersToRequeueTail = oldHead;
}
}
while (awaitersToResume != nullptr)
{
auto* next = awaitersToResume->m_next;
awaitersToResume->m_lastKnownPublished = sequence;
awaitersToResume->resume();
awaitersToResume = next;
}
}
template<typename SEQUENCE, typename TRAITS>
void sequence_barrier<SEQUENCE, TRAITS>::add_awaiter(awaiter_t* awaiter) const noexcept
{
SEQUENCE targetSequence = awaiter->m_targetSequence;
awaiter_t* awaitersToRequeue = awaiter;
awaiter_t** awaitersToRequeueTail = &awaiter->m_next;
SEQUENCE lastKnownPublished;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
do
{
// Enqueue the awaiter(s)
{
auto* oldHead = m_awaiters.load(std::memory_order_relaxed);
do
{
*awaitersToRequeueTail = oldHead;
} while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToRequeue,
std::memory_order_seq_cst,
std::memory_order_relaxed));
}
// Check that the sequence we were waiting for wasn't published while
// we were enqueueing the waiter.
// This needs to be seq_cst memory order to ensure that in the case that the producer
// publishes a new sequence number concurrently with this call that we either see
// their write to m_lastPublished after enqueueing our awaiter, or they see our
// write to m_awaiters after their write to m_lastPublished.
lastKnownPublished = m_lastPublished.load(std::memory_order_seq_cst);
if (TRAITS::precedes(lastKnownPublished, targetSequence))
{
// None of the the awaiters we enqueued have been satisfied yet.
break;
}
// Reset the requeue list to empty
awaitersToRequeueTail = &awaitersToRequeue;
// At least one of the awaiters we just enqueued is now satisfied by a concurrently
// published sequence number. The producer thread may not have seen our write to m_awaiters
// so we need to try to re-acquire the list of awaiters to ensure that the waiters that
// are now satisfied are woken up.
auto* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
auto minDiff = std::numeric_limits<typename TRAITS::difference_type>::max();
while (awaiters != nullptr)
{
const auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
if (diff > 0)
{
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
minDiff = diff < minDiff ? diff : minDiff;
}
else
{
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
}
// Null-terminate the list of awaiters to requeue.
*awaitersToRequeueTail = nullptr;
// Calculate the earliest target sequence required by any of the awaiters to requeue.
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
} while (awaitersToRequeue != nullptr);
// Null-terminate the list of awaiters to resume
*awaitersToResumeTail = nullptr;
// Resume the awaiters that are ready
while (awaitersToResume != nullptr)
{
auto* next = awaitersToResume->m_next;
awaitersToResume->m_lastKnownPublished = lastKnownPublished;
awaitersToResume->resume();
awaitersToResume = next;
}
}
}
#endif
+107
View File
@@ -0,0 +1,107 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
#define CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
#include <cppcoro/sequence_traits.hpp>
#include <algorithm>
#include <iterator>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS = sequence_traits<SEQUENCE>>
class sequence_range
{
public:
using value_type = SEQUENCE;
using difference_type = typename TRAITS::difference_type;
using size_type = typename TRAITS::size_type;
class const_iterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = SEQUENCE;
using difference_type = typename TRAITS::difference_type;
using reference = const SEQUENCE&;
using pointer = const SEQUENCE*;
explicit constexpr const_iterator(SEQUENCE value) noexcept : m_value(value) {}
const SEQUENCE& operator*() const noexcept { return m_value; }
const SEQUENCE* operator->() const noexcept { return std::addressof(m_value); }
const_iterator& operator++() noexcept { ++m_value; return *this; }
const_iterator& operator--() noexcept { --m_value; return *this; }
const_iterator operator++(int) noexcept { return const_iterator(m_value++); }
const_iterator operator--(int) noexcept { return const_iterator(m_value--); }
constexpr difference_type operator-(const_iterator other) const noexcept { return TRAITS::difference(m_value, other.m_value); }
constexpr const_iterator operator-(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value - delta) }; }
constexpr const_iterator operator+(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value + delta) }; }
constexpr bool operator==(const_iterator other) const noexcept { return m_value == other.m_value; }
constexpr bool operator!=(const_iterator other) const noexcept { return m_value != other.m_value; }
private:
SEQUENCE m_value;
};
constexpr sequence_range() noexcept
: m_begin()
, m_end()
{}
constexpr sequence_range(SEQUENCE begin, SEQUENCE end) noexcept
: m_begin(begin)
, m_end(end)
{}
constexpr const_iterator begin() const noexcept { return const_iterator(m_begin); }
constexpr const_iterator end() const noexcept { return const_iterator(m_end); }
constexpr SEQUENCE front() const noexcept { return m_begin; }
constexpr SEQUENCE back() const noexcept { return m_end - 1; }
constexpr size_type size() const noexcept
{
return static_cast<size_type>(TRAITS::difference(m_end, m_begin));
}
constexpr bool empty() const noexcept
{
return m_begin == m_end;
}
constexpr SEQUENCE operator[](size_type index) const noexcept
{
return m_begin + index;
}
constexpr sequence_range first(size_type count) const noexcept
{
return sequence_range{ m_begin, static_cast<SEQUENCE>(m_begin + std::min(size(), count)) };
}
constexpr sequence_range skip(size_type count) const noexcept
{
return sequence_range{ m_begin + std::min(size(), count), m_end };
}
private:
SEQUENCE m_begin;
SEQUENCE m_end;
};
}
#endif
+33
View File
@@ -0,0 +1,33 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
#define CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
#include <type_traits>
namespace cppcoro
{
template<typename SEQUENCE>
struct sequence_traits
{
using value_type = SEQUENCE;
using difference_type = std::make_signed_t<SEQUENCE>;
using size_type = std::make_unsigned_t<SEQUENCE>;
static constexpr value_type initial_sequence = static_cast<value_type>(-1);
static constexpr difference_type difference(value_type a, value_type b)
{
return static_cast<difference_type>(a - b);
}
static constexpr bool precedes(value_type a, value_type b)
{
return difference(a, b) < 0;
}
};
}
#endif
+511
View File
@@ -0,0 +1,511 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
#define CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/broken_promise.hpp>
#include <cppcoro/task.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
#include <atomic>
#include <exception>
#include <utility>
#include <type_traits>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename T>
class shared_task;
namespace detail
{
struct shared_task_waiter
{
cppcoro::coroutine_handle<> m_continuation;
shared_task_waiter* m_next;
};
class shared_task_promise_base
{
friend struct final_awaiter;
struct final_awaiter
{
bool await_ready() const noexcept { return false; }
template<typename PROMISE>
void await_suspend(cppcoro::coroutine_handle<PROMISE> h) noexcept
{
shared_task_promise_base& promise = h.promise();
// Exchange operation needs to be 'release' so that subsequent awaiters have
// visibility of the result. Also needs to be 'acquire' so we have visibility
// of writes to the waiters list.
void* const valueReadyValue = &promise;
void* waiters = promise.m_waiters.exchange(valueReadyValue, std::memory_order_acq_rel);
if (waiters != nullptr)
{
shared_task_waiter* waiter = static_cast<shared_task_waiter*>(waiters);
while (waiter->m_next != nullptr)
{
// Read the m_next pointer before resuming the coroutine
// since resuming the coroutine may destroy the shared_task_waiter value.
auto* next = waiter->m_next;
waiter->m_continuation.resume();
waiter = next;
}
// Resume last waiter in tail position to allow it to potentially
// be compiled as a tail-call.
waiter->m_continuation.resume();
}
}
void await_resume() noexcept {}
};
public:
shared_task_promise_base() noexcept
: m_refCount(1)
, m_waiters(&this->m_waiters)
, m_exception(nullptr)
{}
cppcoro::suspend_always initial_suspend() noexcept { return {}; }
final_awaiter final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
bool is_ready() const noexcept
{
const void* const valueReadyValue = this;
return m_waiters.load(std::memory_order_acquire) == valueReadyValue;
}
void add_ref() noexcept
{
m_refCount.fetch_add(1, std::memory_order_relaxed);
}
/// Decrement the reference count.
///
/// \return
/// true if successfully detached, false if this was the last
/// reference to the coroutine, in which case the caller must
/// call destroy() on the coroutine handle.
bool try_detach() noexcept
{
return m_refCount.fetch_sub(1, std::memory_order_acq_rel) != 1;
}
/// Try to enqueue a waiter to the list of waiters.
///
/// \param waiter
/// Pointer to the state from the waiter object.
/// Must have waiter->m_coroutine member populated with the coroutine
/// handle of the awaiting coroutine.
///
/// \param coroutine
/// Coroutine handle for this promise object.
///
/// \return
/// true if the waiter was successfully queued, in which case
/// waiter->m_coroutine will be resumed when the task completes.
/// false if the coroutine was already completed and the awaiting
/// coroutine can continue without suspending.
bool try_await(shared_task_waiter* waiter, cppcoro::coroutine_handle<> coroutine)
{
void* const valueReadyValue = this;
void* const notStartedValue = &this->m_waiters;
constexpr void* startedNoWaitersValue = static_cast<shared_task_waiter*>(nullptr);
// NOTE: If the coroutine is not yet started then the first waiter
// will start the coroutine before enqueuing itself up to the list
// of suspended waiters waiting for completion. We split this into
// two steps to allow the first awaiter to return without suspending.
// This avoids recursively resuming the first waiter inside the call to
// coroutine.resume() in the case that the coroutine completes
// synchronously, which could otherwise lead to stack-overflow if
// the awaiting coroutine awaited many synchronously-completing
// tasks in a row.
// Start the coroutine if not already started.
void* oldWaiters = m_waiters.load(std::memory_order_acquire);
if (oldWaiters == notStartedValue &&
m_waiters.compare_exchange_strong(
oldWaiters,
startedNoWaitersValue,
std::memory_order_relaxed))
{
// Start the task executing.
coroutine.resume();
oldWaiters = m_waiters.load(std::memory_order_acquire);
}
// Enqueue the waiter into the list of waiting coroutines.
do
{
if (oldWaiters == valueReadyValue)
{
// Coroutine already completed, don't suspend.
return false;
}
waiter->m_next = static_cast<shared_task_waiter*>(oldWaiters);
} while (!m_waiters.compare_exchange_weak(
oldWaiters,
static_cast<void*>(waiter),
std::memory_order_release,
std::memory_order_acquire));
return true;
}
protected:
bool completed_with_unhandled_exception()
{
return m_exception != nullptr;
}
void rethrow_if_unhandled_exception()
{
if (m_exception != nullptr)
{
std::rethrow_exception(m_exception);
}
}
private:
std::atomic<std::uint32_t> m_refCount;
// Value is either
// - nullptr - indicates started, no waiters
// - this - indicates value is ready
// - &this->m_waiters - indicates coroutine not started
// - other - pointer to head item in linked-list of waiters.
// values are of type 'cppcoro::shared_task_waiter'.
// indicates that the coroutine has been started.
std::atomic<void*> m_waiters;
std::exception_ptr m_exception;
};
template<typename T>
class shared_task_promise : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
~shared_task_promise()
{
if (this->is_ready() && !this->completed_with_unhandled_exception())
{
reinterpret_cast<T*>(&m_valueStorage)->~T();
}
}
shared_task<T> get_return_object() noexcept;
template<
typename VALUE,
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
void return_value(VALUE&& value)
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
{
new (&m_valueStorage) T(std::forward<VALUE>(value));
}
T& result()
{
this->rethrow_if_unhandled_exception();
return *reinterpret_cast<T*>(&m_valueStorage);
}
private:
// Not using std::aligned_storage here due to bug in MSVC 2015 Update 2
// that means it doesn't work for types with alignof(T) > 8.
// See MS-Connect bug #2658635.
alignas(T) char m_valueStorage[sizeof(T)];
};
template<>
class shared_task_promise<void> : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
shared_task<void> get_return_object() noexcept;
void return_void() noexcept
{}
void result()
{
this->rethrow_if_unhandled_exception();
}
};
template<typename T>
class shared_task_promise<T&> : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
shared_task<T&> get_return_object() noexcept;
void return_value(T& value) noexcept
{
m_value = std::addressof(value);
}
T& result()
{
this->rethrow_if_unhandled_exception();
return *m_value;
}
private:
T* m_value;
};
}
template<typename T = void>
class [[nodiscard]] shared_task
{
public:
using promise_type = detail::shared_task_promise<T>;
using value_type = T;
private:
struct awaitable_base
{
cppcoro::coroutine_handle<promise_type> m_coroutine;
detail::shared_task_waiter m_waiter;
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
bool await_ready() const noexcept
{
return !m_coroutine || m_coroutine.promise().is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept
{
m_waiter.m_continuation = awaiter;
return m_coroutine.promise().try_await(&m_waiter, m_coroutine);
}
};
public:
shared_task() noexcept
: m_coroutine(nullptr)
{}
explicit shared_task(cppcoro::coroutine_handle<promise_type> coroutine)
: m_coroutine(coroutine)
{
// Don't increment the ref-count here since it has already been
// initialised to 2 (one for shared_task and one for coroutine)
// in the shared_task_promise constructor.
}
shared_task(shared_task&& other) noexcept
: m_coroutine(other.m_coroutine)
{
other.m_coroutine = nullptr;
}
shared_task(const shared_task& other) noexcept
: m_coroutine(other.m_coroutine)
{
if (m_coroutine)
{
m_coroutine.promise().add_ref();
}
}
~shared_task()
{
destroy();
}
shared_task& operator=(shared_task&& other) noexcept
{
if (&other != this)
{
destroy();
m_coroutine = other.m_coroutine;
other.m_coroutine = nullptr;
}
return *this;
}
shared_task& operator=(const shared_task& other) noexcept
{
if (m_coroutine != other.m_coroutine)
{
destroy();
m_coroutine = other.m_coroutine;
if (m_coroutine)
{
m_coroutine.promise().add_ref();
}
}
return *this;
}
void swap(shared_task& other) noexcept
{
std::swap(m_coroutine, other.m_coroutine);
}
/// \brief
/// Query if the task result is complete.
///
/// Awaiting a task that is ready will not block.
bool is_ready() const noexcept
{
return !m_coroutine || m_coroutine.promise().is_ready();
}
auto operator co_await() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return this->m_coroutine.promise().result();
}
};
return awaitable{ m_coroutine };
}
/// \brief
/// Returns an awaitable that will await completion of the task without
/// attempting to retrieve the result.
auto when_ready() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
void await_resume() const noexcept {}
};
return awaitable{ m_coroutine };
}
private:
template<typename U>
friend bool operator==(const shared_task<U>&, const shared_task<U>&) noexcept;
void destroy() noexcept
{
if (m_coroutine)
{
if (!m_coroutine.promise().try_detach())
{
m_coroutine.destroy();
}
}
}
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
template<typename T>
bool operator==(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
{
return lhs.m_coroutine == rhs.m_coroutine;
}
template<typename T>
bool operator!=(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
{
return !(lhs == rhs);
}
template<typename T>
void swap(shared_task<T>& a, shared_task<T>& b) noexcept
{
a.swap(b);
}
namespace detail
{
template<typename T>
shared_task<T> shared_task_promise<T>::get_return_object() noexcept
{
return shared_task<T>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
template<typename T>
shared_task<T&> shared_task_promise<T&>::get_return_object() noexcept
{
return shared_task<T&>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
inline shared_task<void> shared_task_promise<void>::get_return_object() noexcept
{
return shared_task<void>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
}
template<typename AWAITABLE>
auto make_shared_task(AWAITABLE awaitable)
-> shared_task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_return co_await static_cast<AWAITABLE&&>(awaitable);
}
}
#endif
@@ -0,0 +1,101 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
#include <cassert>
namespace cppcoro
{
class single_consumer_async_auto_reset_event
{
public:
single_consumer_async_auto_reset_event(bool initiallySet = false) noexcept
: m_state(initiallySet ? this : nullptr)
{}
void set() noexcept
{
void* oldValue = m_state.exchange(this, std::memory_order_release);
if (oldValue != nullptr && oldValue != this)
{
// There was a waiting coroutine that we now need to resume.
auto handle = *static_cast<cppcoro::coroutine_handle<>*>(oldValue);
// We also need to transition the state back to 'not set' before
// resuming the coroutine. This operation needs to be 'acquire'
// so that it synchronises with other calls to .set() that execute
// concurrently with this call and execute the above m_state.exchange(this)
// operation with 'release' semantics.
// This needs to be an exchange() instead of a store() so that it can have
// 'acquire' semantics.
(void)m_state.exchange(nullptr, std::memory_order_acquire);
// Finally, resume the waiting coroutine.
handle.resume();
}
}
auto operator co_await() const noexcept
{
class awaiter
{
public:
awaiter(const single_consumer_async_auto_reset_event& event) noexcept
: m_event(event)
{}
bool await_ready() const noexcept { return false; }
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
void* oldValue = nullptr;
if (!m_event.m_state.compare_exchange_strong(
oldValue,
&m_awaitingCoroutine,
std::memory_order_release,
std::memory_order_relaxed))
{
// This will only fail if the event was already 'set'
// In which case we can just reset back to 'not set'
// Need to use exchange() rather than store() here so we can make this
// operation an 'acquire' operation so that we get visibility of all
// writes prior to all preceding calls to .set().
assert(oldValue == &m_event);
(void)m_event.m_state.exchange(nullptr, std::memory_order_acquire);
return false;
}
return true;
}
void await_resume() noexcept {}
private:
const single_consumer_async_auto_reset_event& m_event;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
return awaiter{ *this };
}
private:
// nullptr - not set, no waiter
// this - set
// other - not set, pointer is address of a coroutine_handle<> to resume.
mutable std::atomic<void*> m_state;
};
}
#endif
+128
View File
@@ -0,0 +1,128 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
#define CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
#include <atomic>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
/// \brief
/// A manual-reset event that supports only a single awaiting
/// coroutine at a time.
///
/// You can co_await the event to suspend the current coroutine until
/// some thread calls set(). If the event is already set then the
/// coroutine will not be suspended and will continue execution.
/// If the event was not yet set then the coroutine will be resumed
/// on the thread that calls set() within the call to set().
///
/// Callers must ensure that only one coroutine is executing a
/// co_await statement at any point in time.
class single_consumer_event
{
public:
/// \brief
/// Construct a new event, initialising to either 'set' or 'not set' state.
///
/// \param initiallySet
/// If true then initialises the event to the 'set' state.
/// Otherwise, initialised the event to the 'not set' state.
single_consumer_event(bool initiallySet = false) noexcept
: m_state(initiallySet ? state::set : state::not_set)
{}
/// Query if this event has been set.
bool is_set() const noexcept
{
return m_state.load(std::memory_order_acquire) == state::set;
}
/// \brief
/// Transition this event to the 'set' state if it is not already set.
///
/// If there was a coroutine awaiting the event then it will be resumed
/// inside this call.
void set()
{
const state oldState = m_state.exchange(state::set, std::memory_order_acq_rel);
if (oldState == state::not_set_consumer_waiting)
{
m_awaiter.resume();
}
}
/// \brief
/// Transition this event to the 'non set' state if it was in the set state.
void reset() noexcept
{
state oldState = state::set;
m_state.compare_exchange_strong(oldState, state::not_set, std::memory_order_relaxed);
}
/// \brief
/// Wait until the event becomes set.
///
/// If the event is already set then the awaiting coroutine will not be suspended
/// and will continue execution. If the event was not yet set then the coroutine
/// will be suspended and will be later resumed inside a subsequent call to set()
/// on the thread that calls set().
auto operator co_await() noexcept
{
class awaiter
{
public:
awaiter(single_consumer_event& event) : m_event(event) {}
bool await_ready() const noexcept
{
return m_event.is_set();
}
bool await_suspend(cppcoro::coroutine_handle<> awaiter)
{
m_event.m_awaiter = awaiter;
state oldState = state::not_set;
return m_event.m_state.compare_exchange_strong(
oldState,
state::not_set_consumer_waiting,
std::memory_order_release,
std::memory_order_acquire);
}
void await_resume() noexcept {}
private:
single_consumer_event& m_event;
};
return awaiter{ *this };
}
private:
enum class state
{
not_set,
not_set_consumer_waiting,
set
};
// TODO: Merge these two fields into a single std::atomic<std::uintptr_t>
// by encoding 'not_set' as 0 (nullptr), 'set' as 1 and
// 'not_set_consumer_waiting' as a coroutine handle pointer.
std::atomic<state> m_state;
cppcoro::coroutine_handle<> m_awaiter;
};
}
#endif
@@ -0,0 +1,246 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
#define CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/sequence_barrier.hpp>
#include <cppcoro/sequence_range.hpp>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_one_operation;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_operation;
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class single_producer_sequencer
{
public:
using size_type = typename sequence_range<SEQUENCE, TRAITS>::size_type;
single_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
: m_consumerBarrier(consumerBarrier)
, m_bufferSize(bufferSize)
, m_nextToClaim(initialSequence + 1)
, m_producerBarrier(initialSequence)
{}
/// Claim a slot in the ring buffer asynchronously.
///
/// \return
/// Returns an operation that when awaited will suspend the coroutine until
/// a slot is available for writing in the ring buffer. The result of the
/// co_await expression will be the sequence number of the slot.
/// The caller must publish() the claimed sequence number once they have written to
/// the ring-buffer.
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_one(SCHEDULER& scheduler) noexcept;
/// Claim one or more contiguous slots in the ring-buffer.
///
/// Use this method over many calls to claim_one() when you have multiple elements to
/// enqueue. This will claim as many slots as are available up to the specified count
/// but may claim as few as one slot if only one slot is available.
///
/// \param count
/// The maximum number of slots to claim.
///
/// \return
/// Returns an awaitable object that when awaited returns a sequence_range that contains
/// the range of sequence numbers that were claimed. Once you have written element values
/// to all of the claimed slots you must publish() the sequence range in order to make
/// the elements available to consumers.
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER> claim_up_to(
std::size_t count, SCHEDULER& scheduler) noexcept;
/// Publish the specified sequence number.
///
/// This also implies that all prior sequence numbers have already been published.
void publish(SEQUENCE sequence) noexcept
{
m_producerBarrier.publish(sequence);
}
/// Publish a contiguous range of sequence numbers.
///
/// You must have already published all prior sequence numbers.
///
/// This is equivalent to just publishing the last sequence number in the range.
void publish(const sequence_range<SEQUENCE, TRAITS>& sequences) noexcept
{
m_producerBarrier.publish(sequences.back());
}
/// Query what the last-published sequence number is.
///
/// You can assume that all prior sequence numbers are also published.
SEQUENCE last_published() const noexcept
{
return m_producerBarrier.last_published();
}
/// Asynchronously wait until the specified sequence number is published.
///
/// \param targetSequence
/// The sequence number to wait for.
///
/// \return
/// Returns an Awaitable type that, when awaited, will suspend the awaiting coroutine until the
/// specified sequence number has been published.
///
/// The result of the 'co_await barrier.wait_until_published(seq)' expression will be the
/// last-published sequence number, which is guaranteed to be at least 'seq' but may be some
/// subsequent sequence number if additional items were published while waiting for the
/// the requested sequence number to be published.
template<typename SCHEDULER>
[[nodiscard]]
auto wait_until_published(SEQUENCE targetSequence, SCHEDULER& scheduler) const noexcept
{
return m_producerBarrier.wait_until_published(targetSequence, scheduler);
}
private:
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class single_producer_sequencer_claim_operation;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class single_producer_sequencer_claim_one_operation;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
const std::size_t m_bufferSize;
alignas(CPPCORO_CPU_CACHE_LINE)
SEQUENCE m_nextToClaim;
sequence_barrier<SEQUENCE, TRAITS> m_producerBarrier;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_one_operation
{
public:
single_producer_sequencer_claim_one_operation(
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SCHEDULER& scheduler) noexcept
: m_consumerWaitOperation(
sequencer.m_consumerBarrier,
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
scheduler)
, m_sequencer(sequencer)
{}
bool await_ready() const noexcept
{
return m_consumerWaitOperation.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
}
SEQUENCE await_resume() const noexcept
{
return m_sequencer.m_nextToClaim++;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_operation
{
public:
explicit single_producer_sequencer_claim_operation(
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
std::size_t count,
SCHEDULER& scheduler) noexcept
: m_consumerWaitOperation(
sequencer.m_consumerBarrier,
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
scheduler)
, m_sequencer(sequencer)
, m_count(count)
{}
bool await_ready() const noexcept
{
return m_consumerWaitOperation.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
}
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
{
const SEQUENCE lastAvailableSequence =
static_cast<SEQUENCE>(m_consumerWaitOperation.await_resume() + m_sequencer.m_bufferSize);
const SEQUENCE begin = m_sequencer.m_nextToClaim;
const std::size_t availableCount = static_cast<std::size_t>(lastAvailableSequence - begin) + 1;
const std::size_t countToClaim = std::min(m_count, availableCount);
const SEQUENCE end = static_cast<SEQUENCE>(begin + countToClaim);
m_sequencer.m_nextToClaim = end;
return sequence_range<SEQUENCE, TRAITS>(begin, end);
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
std::size_t m_count;
};
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
single_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
{
return single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
single_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
{
return single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, count, scheduler);
}
}
#endif
+116
View File
@@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
#define CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
#include <atomic>
#include <cstdint>
#include <memory>
#include <thread>
#include <vector>
#include <mutex>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class static_thread_pool
{
public:
/// Initialise to a number of threads equal to the number of cores
/// on the current machine.
static_thread_pool();
/// Construct a thread pool with the specified number of threads.
///
/// \param threadCount
/// The number of threads in the pool that will be used to execute work.
explicit static_thread_pool(std::uint32_t threadCount);
~static_thread_pool();
class schedule_operation
{
public:
schedule_operation(static_thread_pool* tp) noexcept : m_threadPool(tp) {}
bool await_ready() noexcept { return false; }
void await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept;
void await_resume() noexcept {}
private:
friend class static_thread_pool;
static_thread_pool* m_threadPool;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
schedule_operation* m_next;
};
std::uint32_t thread_count() const noexcept { return m_threadCount; }
[[nodiscard]]
schedule_operation schedule() noexcept { return schedule_operation{ this }; }
private:
friend class schedule_operation;
void run_worker_thread(std::uint32_t threadIndex) noexcept;
void shutdown();
void schedule_impl(schedule_operation* operation) noexcept;
void remote_enqueue(schedule_operation* operation) noexcept;
bool has_any_queued_work_for(std::uint32_t threadIndex) noexcept;
bool approx_has_any_queued_work_for(std::uint32_t threadIndex) const noexcept;
bool is_shutdown_requested() const noexcept;
void notify_intent_to_sleep(std::uint32_t threadIndex) noexcept;
void try_clear_intent_to_sleep(std::uint32_t threadIndex) noexcept;
schedule_operation* try_global_dequeue() noexcept;
/// Try to steal a task from another thread.
///
/// \return
/// A pointer to the operation that was stolen if one could be stolen
/// from another thread. Otherwise returns nullptr if none of the other
/// threads had any tasks that could be stolen.
schedule_operation* try_steal_from_other_thread(std::uint32_t thisThreadIndex) noexcept;
void wake_one_thread() noexcept;
class thread_state;
static thread_local thread_state* s_currentState;
static thread_local static_thread_pool* s_currentThreadPool;
const std::uint32_t m_threadCount;
const std::unique_ptr<thread_state[]> m_threadStates;
std::vector<std::thread> m_threads;
std::atomic<bool> m_stopRequested;
std::mutex m_globalQueueMutex;
std::atomic<schedule_operation*> m_globalQueueHead;
//alignas(std::hardware_destructive_interference_size)
std::atomic<schedule_operation*> m_globalQueueTail;
//alignas(std::hardware_destructive_interference_size)
std::atomic<std::uint32_t> m_sleepingThreadCount;
};
}
#endif
+50
View File
@@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SYNC_WAIT_HPP_INCLUDED
#define CPPCORO_SYNC_WAIT_HPP_INCLUDED
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
#include <cppcoro/detail/sync_wait_task.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cstdint>
#include <atomic>
#include <span>
namespace cppcoro
{
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait();
return task.result();
}
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable, io_service& srv, std::chrono::system_clock::duration step)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait({ &srv, 1 }, step);
return task.result();
}
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable, std::span<io_service> srvs, std::chrono::system_clock::duration step)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait(srvs, step);
return task.result();
}
}
#endif
+481
View File
@@ -0,0 +1,481 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_TASK_HPP_INCLUDED
#define CPPCORO_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/broken_promise.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
#include <atomic>
#include <exception>
#include <utility>
#include <type_traits>
#include <cstdint>
#include <cassert>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename T> class task;
namespace detail
{
class task_promise_base
{
friend struct final_awaitable;
struct final_awaitable
{
bool await_ready() const noexcept { return false; }
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
template<typename PROMISE>
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<PROMISE> coro) noexcept
{
return coro.promise().m_continuation;
}
#else
// HACK: Need to add CPPCORO_NOINLINE to await_suspend() method
// to avoid MSVC 2017.8 from spilling some local variables in
// await_suspend() onto the coroutine frame in some cases.
// Without this, some tests in async_auto_reset_event_tests.cpp
// were crashing under x86 optimised builds.
template<typename PROMISE>
CPPCORO_NOINLINE
void await_suspend(cppcoro::coroutine_handle<PROMISE> coroutine) noexcept
{
task_promise_base& promise = coroutine.promise();
// Use 'release' memory semantics in case we finish before the
// awaiter can suspend so that the awaiting thread sees our
// writes to the resulting value.
// Use 'acquire' memory semantics in case the caller registered
// the continuation before we finished. Ensure we see their write
// to m_continuation.
if (promise.m_state.exchange(true, std::memory_order_acq_rel))
{
promise.m_continuation.resume();
}
}
#endif
void await_resume() noexcept {}
};
public:
task_promise_base() noexcept
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
: m_state(false)
#endif
{}
auto initial_suspend() noexcept
{
return cppcoro::suspend_always{};
}
auto final_suspend() noexcept
{
return final_awaitable{};
}
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
void set_continuation(cppcoro::coroutine_handle<> continuation) noexcept
{
m_continuation = continuation;
}
#else
bool try_set_continuation(cppcoro::coroutine_handle<> continuation)
{
m_continuation = continuation;
return !m_state.exchange(true, std::memory_order_acq_rel);
}
#endif
private:
cppcoro::coroutine_handle<> m_continuation;
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
// Initially false. Set to true when either a continuation is registered
// or when the coroutine has run to completion. Whichever operation
// successfully transitions from false->true got there first.
std::atomic<bool> m_state;
#endif
};
template<typename T>
class task_promise final : public task_promise_base
{
public:
task_promise() noexcept {}
~task_promise()
{
switch (m_resultType)
{
case result_type::value:
m_value.~T();
break;
case result_type::exception:
m_exception.~exception_ptr();
break;
default:
break;
}
}
task<T> get_return_object() noexcept;
void unhandled_exception() noexcept
{
::new (static_cast<void*>(std::addressof(m_exception))) std::exception_ptr(
std::current_exception());
m_resultType = result_type::exception;
}
template<
typename VALUE,
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
void return_value(VALUE&& value)
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
{
::new (static_cast<void*>(std::addressof(m_value))) T(std::forward<VALUE>(value));
m_resultType = result_type::value;
}
T& result() &
{
if (m_resultType == result_type::exception)
{
std::rethrow_exception(m_exception);
}
assert(m_resultType == result_type::value);
return m_value;
}
// HACK: Need to have co_await of task<int> return prvalue rather than
// rvalue-reference to work around an issue with MSVC where returning
// rvalue reference of a fundamental type from await_resume() will
// cause the value to be copied to a temporary. This breaks the
// sync_wait() implementation.
// See https://github.com/lewissbaker/cppcoro/issues/40#issuecomment-326864107
using rvalue_type = std::conditional_t<
std::is_arithmetic_v<T> || std::is_pointer_v<T>,
T,
T&&>;
rvalue_type result() &&
{
if (m_resultType == result_type::exception)
{
std::rethrow_exception(m_exception);
}
assert(m_resultType == result_type::value);
return std::move(m_value);
}
private:
enum class result_type { empty, value, exception };
result_type m_resultType = result_type::empty;
union
{
T m_value;
std::exception_ptr m_exception;
};
};
template<>
class task_promise<void> : public task_promise_base
{
public:
task_promise() noexcept = default;
task<void> get_return_object() noexcept;
void return_void() noexcept
{}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
std::exception_ptr m_exception;
};
template<typename T>
class task_promise<T&> : public task_promise_base
{
public:
task_promise() noexcept = default;
task<T&> get_return_object() noexcept;
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_value(T& value) noexcept
{
m_value = std::addressof(value);
}
T& result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
return *m_value;
}
private:
T* m_value = nullptr;
std::exception_ptr m_exception;
};
}
/// \brief
/// A task represents an operation that produces a result both lazily
/// and asynchronously.
///
/// When you call a coroutine that returns a task, the coroutine
/// simply captures any passed parameters and returns exeuction to the
/// caller. Execution of the coroutine body does not start until the
/// coroutine is first co_await'ed.
template<typename T = void>
class [[nodiscard]] task
{
public:
using promise_type = detail::task_promise<T>;
using value_type = T;
private:
struct awaitable_base
{
cppcoro::coroutine_handle<promise_type> m_coroutine;
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
bool await_ready() const noexcept
{
return !m_coroutine || m_coroutine.done();
}
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_coroutine.promise().set_continuation(awaitingCoroutine);
return m_coroutine;
}
#else
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
// NOTE: We are using the bool-returning version of await_suspend() here
// to work around a potential stack-overflow issue if a coroutine
// awaits many synchronously-completing tasks in a loop.
//
// We first start the task by calling resume() and then conditionally
// attach the continuation if it has not already completed. This allows us
// to immediately resume the awaiting coroutine without increasing
// the stack depth, avoiding the stack-overflow problem. However, it has
// the down-side of requiring a std::atomic to arbitrate the race between
// the coroutine potentially completing on another thread concurrently
// with registering the continuation on this thread.
//
// We can eliminate the use of the std::atomic once we have access to
// coroutine_handle-returning await_suspend() on both MSVC and Clang
// as this will provide ability to suspend the awaiting coroutine and
// resume another coroutine with a guaranteed tail-call to resume().
m_coroutine.resume();
return m_coroutine.promise().try_set_continuation(awaitingCoroutine);
}
#endif
};
public:
task() noexcept
: m_coroutine(nullptr)
{}
explicit task(cppcoro::coroutine_handle<promise_type> coroutine)
: m_coroutine(coroutine)
{}
task(task&& t) noexcept
: m_coroutine(t.m_coroutine)
{
t.m_coroutine = nullptr;
}
/// Disable copy construction/assignment.
task(const task&) = delete;
task& operator=(const task&) = delete;
/// Frees resources used by this task.
~task()
{
if (m_coroutine)
{
m_coroutine.destroy();
}
}
task& operator=(task&& other) noexcept
{
if (std::addressof(other) != this)
{
if (m_coroutine)
{
m_coroutine.destroy();
}
m_coroutine = other.m_coroutine;
other.m_coroutine = nullptr;
}
return *this;
}
/// \brief
/// Query if the task result is complete.
///
/// Awaiting a task that is ready is guaranteed not to block/suspend.
bool is_ready() const noexcept
{
return !m_coroutine || m_coroutine.done();
}
auto operator co_await() const & noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return this->m_coroutine.promise().result();
}
};
return awaitable{ m_coroutine };
}
auto operator co_await() const && noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return std::move(this->m_coroutine.promise()).result();
}
};
return awaitable{ m_coroutine };
}
/// \brief
/// Returns an awaitable that will await completion of the task without
/// attempting to retrieve the result.
auto when_ready() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
void await_resume() const noexcept {}
};
return awaitable{ m_coroutine };
}
private:
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
namespace detail
{
template<typename T>
task<T> task_promise<T>::get_return_object() noexcept
{
return task<T>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
inline task<void> task_promise<void>::get_return_object() noexcept
{
return task<void>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
template<typename T>
task<T&> task_promise<T&>::get_return_object() noexcept
{
return task<T&>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
}
template<typename AWAITABLE>
auto make_task(AWAITABLE awaitable)
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_return co_await static_cast<AWAITABLE&&>(awaitable);
}
}
#endif
+91
View File
@@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WHEN_ALL_HPP_INCLUDED
#define CPPCORO_WHEN_ALL_HPP_INCLUDED
#include <cppcoro/when_all_ready.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <cppcoro/fmap.hpp>
#include <cppcoro/detail/unwrap_reference.hpp>
#include <tuple>
#include <functional>
#include <utility>
#include <vector>
#include <type_traits>
#include <cassert>
namespace cppcoro
{
//////////
// Variadic when_all()
template<
typename... AWAITABLES,
std::enable_if_t<
std::conjunction_v<is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>,
int> = 0>
[[nodiscard]] auto when_all(AWAITABLES&&... awaitables)
{
return fmap([](auto&& taskTuple)
{
return std::apply([](auto&&... tasks) {
return std::make_tuple(static_cast<decltype(tasks)>(tasks).non_void_result()...);
}, static_cast<decltype(taskTuple)>(taskTuple));
}, when_all_ready(std::forward<AWAITABLES>(awaitables)...));
}
//////////
// when_all() with vector of awaitable
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
[[nodiscard]]
auto when_all(std::vector<AWAITABLE> awaitables)
{
return fmap([](auto&& taskVector) {
for (auto& task : taskVector)
{
task.result();
}
}, when_all_ready(std::move(awaitables)));
}
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
[[nodiscard]]
auto when_all(std::vector<AWAITABLE> awaitables)
{
using result_t = std::conditional_t<
std::is_lvalue_reference_v<RESULT>,
std::reference_wrapper<std::remove_reference_t<RESULT>>,
std::remove_reference_t<RESULT>>;
return fmap([](auto&& taskVector) {
std::vector<result_t> results;
results.reserve(taskVector.size());
for (auto& task : taskVector)
{
if constexpr (std::is_rvalue_reference_v<decltype(taskVector)>)
{
results.emplace_back(std::move(task).result());
}
else
{
results.emplace_back(task.result());
}
}
return results;
}, when_all_ready(std::move(awaitables)));
}
}
#endif
+56
View File
@@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
#define CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <cppcoro/detail/when_all_ready_awaitable.hpp>
#include <cppcoro/detail/when_all_task.hpp>
#include <cppcoro/detail/unwrap_reference.hpp>
#include <tuple>
#include <utility>
#include <vector>
#include <type_traits>
namespace cppcoro
{
template<
typename... AWAITABLES,
std::enable_if_t<std::conjunction_v<
is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>, int> = 0>
[[nodiscard]]
CPPCORO_FORCE_INLINE auto when_all_ready(AWAITABLES&&... awaitables)
{
return detail::when_all_ready_awaitable<std::tuple<detail::when_all_task<
typename awaitable_traits<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>::await_result_t>...>>(
std::make_tuple(detail::make_when_all_task(std::forward<AWAITABLES>(awaitables))...));
}
// TODO: Generalise this from vector<AWAITABLE> to arbitrary sequence of awaitable.
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t>
[[nodiscard]] auto when_all_ready(std::vector<AWAITABLE> awaitables)
{
std::vector<detail::when_all_task<RESULT>> tasks;
tasks.reserve(awaitables.size());
for (auto& awaitable : awaitables)
{
tasks.emplace_back(detail::make_when_all_task(std::move(awaitable)));
}
return detail::when_all_ready_awaitable<std::vector<detail::when_all_task<RESULT>>>(
std::move(tasks));
}
}
#endif
+71
View File
@@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WRITABLE_FILE_HPP_INCLUDED
#define CPPCORO_WRITABLE_FILE_HPP_INCLUDED
#include <cppcoro/file.hpp>
#include <cppcoro/file_write_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
namespace cppcoro
{
class writable_file : virtual public file
{
public:
/// Set the size of the file.
///
/// \param fileSize
/// The new size of the file in bytes.
void set_size(std::uint64_t fileSize);
/// Write some data to the file.
///
/// Writes \a byteCount bytes from the file starting at \a offset
/// into the specified \a buffer.
///
/// \param offset
/// The offset within the file to start writing from.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the offset must be a multiple of the file-system's sector size.
///
/// \param buffer
/// The buffer containing the data to be written to the file.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the address of the start of the buffer must be a multiple of
/// the file-system's sector size.
///
/// \param byteCount
/// The number of bytes to write to the file.
/// If the file has been opeend using file_buffering_mode::unbuffered
/// then the byteCount must be a multiple of the file-system's sector size.
///
/// \param ct
/// An optional cancellation_token that can be used to cancel the
/// write operation before it completes.
///
/// \return
/// An object that represents the write operation.
/// This object must be co_await'ed to start the write operation.
[[nodiscard]]
file_write_operation write(
std::uint64_t offset,
const void* buffer,
std::size_t byteCount) noexcept;
[[nodiscard]]
file_write_operation_cancellable write(
std::uint64_t offset,
const void* buffer,
std::size_t byteCount,
cancellation_token ct) noexcept;
protected:
using file::file;
};
}
#endif
+65
View File
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
#define CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
#include <cppcoro/writable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class write_only_file : public writable_file
{
public:
/// Open a file for write-only access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous write operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param pathMode
/// Path of the file to open.
///
/// \param openMode
/// Specifies how the file should be opened and how to handle cases
/// when the file exists or doesn't exist.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to write to the file.
///
/// \throw std::system_error
/// If the file could not be opened for write.
[[nodiscard]]
static write_only_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
write_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif