First
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user