This commit is contained in:
jeanlemotan
2024-07-02 18:13:47 +02:00
commit bbeaa887cd
173 changed files with 34365 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
---
BasedOnStyle: LLVM
---
Language: Cpp
Standard: Cpp11
ColumnLimit: 100
TabWidth: 4
IndentWidth: 4
UseTab: ForContinuationAndIndentation
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BraceWrapping: {
AfterClass: true,
AfterControlStatement: true,
AfterEnum: true,
AfterFunction: true,
AfterNamespace: true,
AfterStruct: true,
AfterUnion: true,
BeforeCatch: true,
BeforeElse: true,
IndentBraces: false,
#SplitEmptyFunctionBody: false
}
BreakBeforeInheritanceComma: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
IncludeCategories:
- Regex: '^<cppcoro/config.hpp>$'
Priority: 1
- Regex: '^<cppcoro/detail/'
Priority: 3
- Regex: '^<cppcoro/'
Priority: 2
- Regex: '^"doctest/'
Priority: 7
- Regex: '^"'
Priority: 4
- Regex: '^<experimental/'
Priority: 6
- Regex: '^<'
Priority: 5
IndentCaseLabels: true
#IndentPPDirectives: AfterHash
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpacesInSquareBrackets: false
SpacesInParentheses: false
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
SpaceAfterCStyleCast: false
SpacesInAngles: false
SpacesBeforeTrailingComments: 2
SpaceBeforeParens: ControlStatements
SpaceBeforeAssignmentOperators: true
SpaceAfterTemplateKeyword: false
+16
View File
@@ -0,0 +1,16 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.hpp,*.cpp]
indent_style = tab
indent_size = tab
tab_width = 4
[*.cake,*.py]
indent_style = space
indent_size = 2
+5
View File
@@ -0,0 +1,5 @@
build
*.cakec
*.pyc
.idea
*~
View File
+43
View File
@@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.12)
project(cppcoro LANGUAGES CXX)
if(NOT "${CMAKE_CXX_STANDARD}")
message("C++ version not set. Defaulting to CMAKE_CXX_STANDARD=20")
set(CMAKE_CXX_STANDARD 20)
endif()
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
add_subdirectory(lib)
#enable_testing()
#if(BUILD_TESTING)
# add_subdirectory(test)
#endif()
#export(EXPORT cppcoroTargets
# FILE "${PROJECT_BINARY_DIR}/cppcoro/cppcoroTargets.cmake"
# NAMESPACE cppcoro::)
#configure_file(cmake/cppcoroConfig.cmake
# "${PROJECT_BINARY_DIR}/cppcoro/cppcoroConfig.cmake"
# COPYONLY)
#set(config_package_location lib/cmake/cppcoro)
#install(DIRECTORY include/cppcoro
# DESTINATION include
# COMPONENT Devel)
#install(FILES cmake/FindCoroutines.cmake
# DESTINATION ${config_package_location}
# COMPONENT Devel)
#install(EXPORT cppcoroTargets
# FILE cppcoroTargets.cmake
# NAMESPACE cppcoro::
# DESTINATION ${config_package_location})
#install(
# FILES ${CMAKE_CURRENT_BINARY_DIR}/cppcoro/cppcoroConfig.cmake
# DESTINATION ${config_package_location}
# COMPONENT Devel)
+19
View File
@@ -0,0 +1,19 @@
Copyright 2017 Lewis Baker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3019
View File
File diff suppressed because it is too large Load Diff
View File
+282
View File
@@ -0,0 +1,282 @@
# Copyright (c) 2019-present, Facebook, Inc.
#
# This source code is licensed under the Apache License found in the
# LICENSE.txt file in the root directory of this source tree.
#[=======================================================================[.rst:
FindCoroutines
##############
This module supports the C++ standard support for coroutines. Use
the :imp-target:`std::coroutines` imported target to
Options
*******
The ``COMPONENTS`` argument to this module supports the following values:
.. find-component:: Experimental
:name: coro.Experimental
Allows the module to find the "experimental" Coroutines TS
version of the coroutines library. This is the library that should be
used with the ``std::experimental`` namespace.
.. find-component:: Final
:name: coro.Final
Finds the final C++20 standard version of coroutines.
If no components are provided, behaves as if the
:find-component:`coro.Final` component was specified.
If both :find-component:`coro.Experimental` and :find-component:`coro.Final` are
provided, first looks for ``Final``, and falls back to ``Experimental`` in case
of failure. If ``Final`` is found, :imp-target:`std::coroutines` and all
:ref:`variables <coro.variables>` will refer to the ``Final`` version.
Imported Targets
****************
.. imp-target:: std::coroutines
The ``std::coroutines`` imported target is defined when any requested
version of the C++ coroutines library has been found, whether it is
*Experimental* or *Final*.
If no version of the coroutines library is available, this target will not
be defined.
.. note::
This target has ``cxx_std_17`` as an ``INTERFACE``
:ref:`compile language standard feature <req-lang-standards>`. Linking
to this target will automatically enable C++17 if no later standard
version is already required on the linking target.
.. _coro.variables:
Variables
*********
.. variable:: CXX_COROUTINES_HAVE_COROUTINES
Set to ``TRUE`` when coroutines are supported in both the language and the
library.
.. variable:: CXX_COROUTINES_HEADER
Set to either ``coroutine`` or ``experimental/coroutine`` depending on
whether :find-component:`coro.Final` or :find-component:`coro.Experimental` was
found.
.. variable:: CXX_COROUTINES_NAMESPACE
Set to either ``std`` or ``std::experimental``
depending on whether :find-component:`coro.Final` or
:find-component:`coro.Experimental` was found.
Examples
********
Using `find_package(Coroutines)` with no component arguments:
.. code-block:: cmake
find_package(Coroutines REQUIRED)
add_executable(my-program main.cpp)
target_link_libraries(my-program PRIVATE std::coroutines)
#]=======================================================================]
if(TARGET std::coroutines)
# This module has already been processed. Don't do it again.
return()
endif()
include(CheckCXXCompilerFlag)
include(CMakePushCheckState)
include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles)
cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${Coroutines_FIND_QUIETLY})
check_cxx_compiler_flag(/await _CXX_COROUTINES_SUPPORTS_MS_FLAG)
check_cxx_compiler_flag(/await:heapelide _CXX_COROUTINES_SUPPORTS_MS_HEAPELIDE_FLAG)
check_cxx_compiler_flag(-fcoroutines-ts _CXX_COROUTINES_SUPPORTS_TS_FLAG)
check_cxx_compiler_flag(-fcoroutines _CXX_COROUTINES_SUPPORTS_CORO_FLAG)
if(_CXX_COROUTINES_SUPPORTS_MS_FLAG)
set(_CXX_COROUTINES_EXTRA_FLAGS "/await")
if(_CXX_COROUTINES_SUPPORTS_MS_HEAPELIDE_FLAG AND CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8)
list(APPEND _CXX_COROUTINES_EXTRA_FLAGS "/await:heapelide")
endif()
elseif(_CXX_COROUTINES_SUPPORTS_TS_FLAG)
set(_CXX_COROUTINES_EXTRA_FLAGS "-fcoroutines-ts")
elseif(_CXX_COROUTINES_SUPPORTS_CORO_FLAG)
set(_CXX_COROUTINES_EXTRA_FLAGS "-fcoroutines")
endif()
# Normalize and check the component list we were given
set(want_components ${Coroutines_FIND_COMPONENTS})
if(Coroutines_FIND_COMPONENTS STREQUAL "")
set(want_components Final)
endif()
# Warn on any unrecognized components
set(extra_components ${want_components})
list(REMOVE_ITEM extra_components Final Experimental)
foreach(component IN LISTS extra_components)
message(WARNING "Extraneous find_package component for Coroutines: ${component}")
endforeach()
# Detect which of Experimental and Final we should look for
set(find_experimental TRUE)
set(find_final TRUE)
if(NOT "Final" IN_LIST want_components)
set(find_final FALSE)
endif()
if(NOT "Experimental" IN_LIST want_components)
set(find_experimental FALSE)
endif()
if(find_final)
check_include_file_cxx("coroutine" _CXX_COROUTINES_HAVE_HEADER)
if(_CXX_COROUTINES_HAVE_HEADER)
check_cxx_source_compiles("#include <coroutine> \n typedef std::suspend_never blub; \nint main() {} " _CXX_COROUTINES_FINAL_HEADER_COMPILES)
set(_CXX_COROUTINES_HAVE_HEADER "${_CXX_COROUTINES_FINAL_HEADER_COMPILES}")
endif()
if(NOT _CXX_COROUTINES_HAVE_HEADER)
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${_CXX_COROUTINES_EXTRA_FLAGS}")
check_include_file_cxx("coroutine" _CXX_COROUTINES_HAVE_HEADER_WITH_FLAG)
if(_CXX_COROUTINES_HAVE_HEADER_WITH_FLAG)
check_cxx_source_compiles("#include <coroutine> \n typedef std::suspend_never blub; \nint main() {} " _CXX_COROUTINES_FINAL_HEADER_COMPILES_WITH_FLAG)
set(_CXX_COROUTINES_HAVE_HEADER_WITH_FLAG "${_CXX_COROUTINES_FINAL_HEADER_COMPILES_WITH_FLAG}")
endif()
set(_CXX_COROUTINES_HAVE_HEADER "${_CXX_COROUTINES_HAVE_HEADER_WITH_FLAG}")
cmake_pop_check_state()
endif()
mark_as_advanced(_CXX_COROUTINES_HAVE_HEADER)
if(_CXX_COROUTINES_HAVE_HEADER)
# We found the non-experimental header. Don't bother looking for the
# experimental one.
set(find_experimental FALSE)
endif()
else()
set(_CXX_COROUTINES_HAVE_HEADER FALSE)
endif()
if(find_experimental)
check_include_file_cxx("experimental/coroutine" _CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER)
if(NOT _CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER)
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${_CXX_COROUTINES_EXTRA_FLAGS}")
check_include_file_cxx("experimental/coroutine" _CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER_WITH_FLAG)
set(_CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER "${_CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER_WITH_FLAG}")
cmake_pop_check_state()
endif()
mark_as_advanced(_CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER)
else()
set(_CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER FALSE)
endif()
if(_CXX_COROUTINES_HAVE_HEADER)
set(_have_coro TRUE)
set(_coro_header coroutine)
set(_coro_namespace std)
elseif(_CXX_COROUTINES_HAVE_EXPERIMENTAL_HEADER)
set(_have_coro TRUE)
set(_coro_header experimental/coroutine)
set(_coro_namespace std::experimental)
else()
set(_have_coro FALSE)
endif()
set(CXX_COROUTINES_HAVE_COROUTINES ${_have_coro} CACHE BOOL "TRUE if we have the C++ coroutines feature")
set(CXX_COROUTINES_HEADER ${_coro_header} CACHE STRING "The header that should be included to obtain the coroutines APIs")
set(CXX_COROUTINES_NAMESPACE ${_coro_namespace} CACHE STRING "The C++ namespace that contains the coroutines APIs")
set(_found FALSE)
if(CXX_COROUTINES_HAVE_COROUTINES)
# We have some coroutines library available. Do link checks
string(CONFIGURE [[
#include <utility>
#include <@CXX_COROUTINES_HEADER@>
struct present {
struct promise_type {
int result;
present get_return_object() { return present{*this}; }
@CXX_COROUTINES_NAMESPACE@::suspend_never initial_suspend() { return {}; }
@CXX_COROUTINES_NAMESPACE@::suspend_always final_suspend() noexcept { return {}; }
void return_value(int i) { result = i; }
void unhandled_exception() {}
};
friend struct promise_type;
present(present&& that) : coro_(std::exchange(that.coro_, {})) {}
~present() { if(coro_) coro_.destroy(); }
bool await_ready() const { return true; }
void await_suspend(@CXX_COROUTINES_NAMESPACE@::coroutine_handle<>) const {}
int await_resume() const { return coro_.promise().result; }
private:
present(promise_type& promise)
: coro_(@CXX_COROUTINES_NAMESPACE@::coroutine_handle<promise_type>::from_promise(promise)) {}
@CXX_COROUTINES_NAMESPACE@::coroutine_handle<promise_type> coro_;
};
present f(int n) {
if (n < 2)
co_return 1;
else
co_return n * co_await f(n - 1);
}
int main() {
return f(5).await_resume() != 120;
}
]] code @ONLY)
# Try to compile a simple coroutines program without any compiler flags
check_cxx_source_compiles("${code}" CXX_COROUTINES_NO_AWAIT_NEEDED)
set(can_link ${CXX_COROUTINES_NO_AWAIT_NEEDED})
if(NOT CXX_COROUTINES_NO_AWAIT_NEEDED)
# Add the -fcoroutines-ts (or /await) flag
set(CMAKE_REQUIRED_FLAGS "${_CXX_COROUTINES_EXTRA_FLAGS}")
check_cxx_source_compiles("${code}" CXX_COROUTINES_AWAIT_NEEDED)
set(can_link "${CXX_COROUTINES_AWAIT_NEEDED}")
endif()
if(can_link)
add_library(std::coroutines INTERFACE IMPORTED)
set(_found TRUE)
if(CXX_COROUTINES_NO_AWAIT_NEEDED)
# Nothing to add...
elseif(CXX_COROUTINES_AWAIT_NEEDED)
target_compile_options(std::coroutines INTERFACE ${_CXX_COROUTINES_EXTRA_FLAGS})
endif()
else()
set(CXX_COROUTINES_HAVE_COROUTINES FALSE)
endif()
endif()
cmake_pop_check_state()
set(Coroutines_FOUND ${_found} CACHE BOOL "TRUE if we can compile and link a program using std::coroutines" FORCE)
if(Coroutines_FIND_REQUIRED AND NOT Coroutines_FOUND)
message(FATAL_ERROR "Cannot compile simple program using std::coroutines. Is C++17 or later activated?")
endif()
+6
View File
@@ -0,0 +1,6 @@
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
include(CMakeFindDependencyMacro)
find_dependency(Coroutines QUIET REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/cppcoroTargets.cmake")
@@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_auto_reset_event_operation;
/// An async auto-reset event is a coroutine synchronisation abstraction
/// that allows one or more coroutines to wait until some thread calls
/// set() on the event.
///
/// When a coroutine awaits a 'set' event the event is automatically
/// reset back to the 'not set' state, thus the name 'auto reset' event.
class async_auto_reset_event
{
public:
/// Initialise the event to either 'set' or 'not set' state.
async_auto_reset_event(bool initiallySet = false) noexcept;
~async_auto_reset_event();
/// Wait for the event to enter the 'set' state.
///
/// If the event is already 'set' then the event is set to the 'not set'
/// state and the awaiting coroutine continues without suspending.
/// Otherwise, the coroutine is suspended and later resumed when some
/// thread calls 'set()'.
///
/// Note that the coroutine may be resumed inside a call to 'set()'
/// or inside another thread's call to 'operator co_await()'.
async_auto_reset_event_operation operator co_await() const noexcept;
/// Set the state of the event to 'set'.
///
/// If there are pending coroutines awaiting the event then one
/// pending coroutine is resumed and the state is immediately
/// set back to the 'not set' state.
///
/// This operation is a no-op if the event was already 'set'.
void set() noexcept;
/// Set the state of the event to 'not-set'.
///
/// This is a no-op if the state was already 'not set'.
void reset() noexcept;
private:
friend class async_auto_reset_event_operation;
void resume_waiters(std::uint64_t initialState) const noexcept;
// Bits 0-31 - Set count
// Bits 32-63 - Waiter count
mutable std::atomic<std::uint64_t> m_state;
mutable std::atomic<async_auto_reset_event_operation*> m_newWaiters;
mutable async_auto_reset_event_operation* m_waiters;
};
class async_auto_reset_event_operation
{
public:
async_auto_reset_event_operation() noexcept;
explicit async_auto_reset_event_operation(const async_auto_reset_event& event) noexcept;
async_auto_reset_event_operation(const async_auto_reset_event_operation& other) noexcept;
bool await_ready() const noexcept { return m_event == nullptr; }
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class async_auto_reset_event;
const async_auto_reset_event* m_event;
async_auto_reset_event_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
std::atomic<std::uint32_t> m_refCount;
};
}
#endif
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_LATCH_HPP_INCLUDED
#define CPPCORO_ASYNC_LATCH_HPP_INCLUDED
#include <cppcoro/async_manual_reset_event.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_latch
{
public:
/// Construct the latch with the specified initial count.
///
/// \param initialCount
/// The initial count of the latch. The latch will become signalled once
/// \c this->count_down() has been called \p initialCount times.
/// The latch will be immediately signalled on construction if this
/// parameter is zero or negative.
async_latch(std::ptrdiff_t initialCount) noexcept
: m_count(initialCount)
, m_event(initialCount <= 0)
{}
/// Query if the latch has become signalled.
///
/// The latch is marked as signalled once the count reaches zero.
bool is_ready() const noexcept { return m_event.is_set(); }
/// Decrement the count by n.
///
/// Any coroutines awaiting this latch will be resumed once the count
/// reaches zero. ie. when this method has been called at least 'initialCount'
/// times.
///
/// Any awaiting coroutines that are currently suspended waiting for the
/// latch to become signalled will be resumed inside the last call to this
/// method (ie. the call that decrements the count to zero).
///
/// \param n
/// The amount to decrement the count by.
void count_down(std::ptrdiff_t n = 1) noexcept
{
if (m_count.fetch_sub(n, std::memory_order_acq_rel) <= n)
{
m_event.set();
}
}
/// Allows the latch to be awaited within a coroutine.
///
/// If the latch is already signalled (ie. the count has been decremented
/// to zero) then the awaiting coroutine will continue without suspending.
/// Otherwise, the coroutine will suspend and will later be resumed inside
/// a call to `count_down()`.
auto operator co_await() const noexcept
{
return m_event.operator co_await();
}
private:
std::atomic<std::ptrdiff_t> m_count;
async_manual_reset_event m_event;
};
}
#endif
@@ -0,0 +1,104 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
class async_manual_reset_event_operation;
/// An async manual-reset event is a coroutine synchronisation abstraction
/// that allows one or more coroutines to wait until some thread calls
/// set() on the event.
///
/// When a coroutine awaits a 'set' event the coroutine continues without
/// suspending. Otherwise, if it awaits a 'not set' event the coroutine is
/// suspended and is later resumed inside the call to 'set()'.
///
/// \seealso async_auto_reset_event
class async_manual_reset_event
{
public:
/// Initialise the event to either 'set' or 'not set' state.
///
/// \param initiallySet
/// If 'true' then initialises the event to the 'set' state, otherwise
/// initialises the event to the 'not set' state.
async_manual_reset_event(bool initiallySet = false) noexcept;
~async_manual_reset_event();
/// Wait for the event to enter the 'set' state.
///
/// If the event is already 'set' then the coroutine continues without
/// suspending.
///
/// Otherwise, the coroutine is suspended and later resumed when some
/// thread calls 'set()'. The coroutine will be resumed inside the next
/// call to 'set()'.
async_manual_reset_event_operation operator co_await() const noexcept;
/// Query if the event is currently in the 'set' state.
bool is_set() const noexcept;
/// Set the state of the event to 'set'.
///
/// If there are pending coroutines awaiting the event then all
/// pending coroutines are resumed within this call.
/// Any coroutines that subsequently await the event will continue
/// without suspending.
///
/// This operation is a no-op if the event was already 'set'.
void set() noexcept;
/// Set the state of the event to 'not-set'.
///
/// Any coroutines that subsequently await the event will suspend
/// until some thread calls 'set()'.
///
/// This is a no-op if the state was already 'not set'.
void reset() noexcept;
private:
friend class async_manual_reset_event_operation;
// This variable has 3 states:
// - this - The state is 'set'.
// - nullptr - The state is 'not set' with no waiters.
// - other - The state is 'not set'.
// Points to an 'async_manual_reset_event_operation' that is
// the head of a linked-list of waiters.
mutable std::atomic<void*> m_state;
};
class async_manual_reset_event_operation
{
public:
explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept;
bool await_ready() const noexcept;
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class async_manual_reset_event;
const async_manual_reset_event& m_event;
async_manual_reset_event_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
};
}
#endif
+200
View File
@@ -0,0 +1,200 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
#include <mutex> // for std::adopt_lock_t
namespace cppcoro
{
class async_mutex_lock;
class async_mutex_lock_operation;
class async_mutex_scoped_lock_operation;
/// \brief
/// A mutex that can be locked asynchronously using 'co_await'.
///
/// Ownership of the mutex is not tied to any particular thread.
/// This allows the coroutine owning the lock to transition from
/// one thread to another while holding a lock.
///
/// Implementation is lock-free, using only std::atomic values for
/// synchronisation. Awaiting coroutines are suspended without blocking
/// the current thread if the lock could not be acquired synchronously.
class async_mutex
{
public:
/// \brief
/// Construct to a mutex that is not currently locked.
async_mutex() noexcept;
/// Destroys the mutex.
///
/// Behaviour is undefined if there are any outstanding coroutines
/// still waiting to acquire the lock.
~async_mutex();
/// \brief
/// Attempt to acquire a lock on the mutex without blocking.
///
/// \return
/// true if the lock was acquired, false if the mutex was already locked.
/// The caller is responsible for ensuring unlock() is called on the mutex
/// to release the lock if the lock was acquired by this call.
bool try_lock() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.lock_async()'
/// expression has type 'void'.
async_mutex_lock_operation lock_async() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously, returning an object that
/// will call unlock() automatically when it goes out of scope.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.scoped_lock_async()'
/// expression returns an 'async_mutex_lock' object that will call
/// this->mutex() when it destructs.
async_mutex_scoped_lock_operation scoped_lock_async() noexcept;
/// \brief
/// Unlock the mutex.
///
/// Must only be called by the current lock-holder.
///
/// If there are lock operations waiting to acquire the
/// mutex then the next lock operation in the queue will
/// be resumed inside this call.
void unlock();
private:
friend class async_mutex_lock_operation;
static constexpr std::uintptr_t not_locked = 1;
// assume == reinterpret_cast<std::uintptr_t>(static_cast<void*>(nullptr))
static constexpr std::uintptr_t locked_no_waiters = 0;
// This field provides synchronisation for the mutex.
//
// It can have three kinds of values:
// - not_locked
// - locked_no_waiters
// - a pointer to the head of a singly linked list of recently
// queued async_mutex_lock_operation objects. This list is
// in most-recently-queued order as new items are pushed onto
// the front of the list.
std::atomic<std::uintptr_t> m_state;
// Linked list of async lock operations that are waiting to acquire
// the mutex. These operations will acquire the lock in the order
// they appear in this list. Waiters in this list will acquire the
// mutex before waiters added to the m_newWaiters list.
async_mutex_lock_operation* m_waiters;
};
/// \brief
/// An object that holds onto a mutex lock for its lifetime and
/// ensures that the mutex is unlocked when it is destructed.
///
/// It is equivalent to a std::lock_guard object but requires
/// that the result of co_await async_mutex::lock_async() is
/// passed to the constructor rather than passing the async_mutex
/// object itself.
class async_mutex_lock
{
public:
explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
: m_mutex(&mutex)
{}
async_mutex_lock(async_mutex_lock&& other) noexcept
: m_mutex(other.m_mutex)
{
other.m_mutex = nullptr;
}
async_mutex_lock(const async_mutex_lock& other) = delete;
async_mutex_lock& operator=(const async_mutex_lock& other) = delete;
// Releases the lock.
~async_mutex_lock()
{
if (m_mutex != nullptr)
{
m_mutex->unlock();
}
}
private:
async_mutex* m_mutex;
};
class async_mutex_lock_operation
{
public:
explicit async_mutex_lock_operation(async_mutex& mutex) noexcept
: m_mutex(mutex)
{}
bool await_ready() const noexcept { return false; }
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
protected:
friend class async_mutex;
async_mutex& m_mutex;
private:
async_mutex_lock_operation* m_next;
cppcoro::coroutine_handle<> m_awaiter;
};
class async_mutex_scoped_lock_operation : public async_mutex_lock_operation
{
public:
using async_mutex_lock_operation::async_mutex_lock_operation;
[[nodiscard]]
async_mutex_lock await_resume() const noexcept
{
return async_mutex_lock{ m_mutex, std::adopt_lock };
}
};
}
#endif
+102
View File
@@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
#define CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
#include <cppcoro/on_scope_exit.hpp>
#include <atomic>
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <cassert>
namespace cppcoro
{
class async_scope
{
public:
async_scope() noexcept
: m_count(1u)
{}
~async_scope()
{
// scope must be co_awaited before it destructs.
assert(m_continuation);
}
template<typename AWAITABLE>
void spawn(AWAITABLE&& awaitable)
{
[](async_scope* scope, std::decay_t<AWAITABLE> awaitable) -> oneway_task
{
scope->on_work_started();
auto decrementOnCompletion = on_scope_exit([scope] { scope->on_work_finished(); });
co_await std::move(awaitable);
}(this, std::forward<AWAITABLE>(awaitable));
}
[[nodiscard]] auto join() noexcept
{
class awaiter
{
async_scope* m_scope;
public:
awaiter(async_scope* scope) noexcept : m_scope(scope) {}
bool await_ready() noexcept
{
return m_scope->m_count.load(std::memory_order_acquire) == 0;
}
bool await_suspend(cppcoro::coroutine_handle<> continuation) noexcept
{
m_scope->m_continuation = continuation;
return m_scope->m_count.fetch_sub(1u, std::memory_order_acq_rel) > 1u;
}
void await_resume() noexcept
{}
};
return awaiter{ this };
}
private:
void on_work_finished() noexcept
{
if (m_count.fetch_sub(1u, std::memory_order_acq_rel) == 1)
{
m_continuation.resume();
}
}
void on_work_started() noexcept
{
assert(m_count.load(std::memory_order_relaxed) != 0);
m_count.fetch_add(1, std::memory_order_relaxed);
}
struct oneway_task
{
struct promise_type
{
cppcoro::suspend_never initial_suspend() noexcept { return {}; }
cppcoro::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
oneway_task get_return_object() { return {}; }
void return_void() {}
};
};
std::atomic<size_t> m_count;
cppcoro::coroutine_handle<> m_continuation;
};
}
#endif
+27
View File
@@ -0,0 +1,27 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
#define CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
#include <cppcoro/detail/get_awaiter.hpp>
#include <type_traits>
namespace cppcoro
{
template<typename T, typename = void>
struct awaitable_traits
{};
template<typename T>
struct awaitable_traits<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
{
using awaiter_t = decltype(cppcoro::detail::get_awaiter(std::declval<T>()));
using await_result_t = decltype(std::declval<awaiter_t>().await_resume());
};
}
#endif
+24
View File
@@ -0,0 +1,24 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
#define CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
#include <stdexcept>
namespace cppcoro
{
/// \brief
/// Exception thrown when you attempt to retrieve the result of
/// a task that has been detached from its promise/coroutine.
class broken_promise : public std::logic_error
{
public:
broken_promise()
: std::logic_error("broken promise")
{}
};
}
#endif
@@ -0,0 +1,87 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
#define CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
#include <cppcoro/cancellation_token.hpp>
#include <functional>
#include <utility>
#include <type_traits>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
namespace detail
{
class cancellation_state;
struct cancellation_registration_list_chunk;
struct cancellation_registration_state;
}
class cancellation_registration
{
public:
/// Registers the callback to be executed when cancellation is requested
/// on the cancellation_token.
///
/// The callback will be executed if cancellation is requested for the
/// specified cancellation token. If cancellation has already been requested
/// then the callback will be executed immediately, before the constructor
/// returns. If cancellation has not yet been requested then the callback
/// will be executed on the first thread to request cancellation inside
/// the call to cancellation_source::request_cancellation().
///
/// \param token
/// The cancellation token to register the callback with.
///
/// \param callback
/// The callback to be executed when cancellation is requested on the
/// the cancellation_token. Note that callback must not throw an exception
/// if called when cancellation is requested otherwise std::terminate()
/// will be called.
///
/// \throw std::bad_alloc
/// If registration failed due to insufficient memory available.
template<
typename FUNC,
typename = std::enable_if_t<std::is_constructible_v<std::function<void()>, FUNC&&>>>
cancellation_registration(cancellation_token token, FUNC&& callback)
: m_callback(std::forward<FUNC>(callback))
{
register_callback(std::move(token));
}
cancellation_registration(const cancellation_registration& other) = delete;
cancellation_registration& operator=(const cancellation_registration& other) = delete;
/// Deregisters the callback.
///
/// After the destructor returns it is guaranteed that the callback
/// will not be subsequently called during a call to request_cancellation()
/// on the cancellation_source.
///
/// This may block if cancellation has been requested on another thread
/// is it will need to wait until this callback has finished executing
/// before the callback can be destroyed.
~cancellation_registration();
private:
friend class detail::cancellation_state;
friend struct detail::cancellation_registration_state;
void register_callback(cancellation_token&& token);
detail::cancellation_state* m_state;
std::function<void()> m_callback;
detail::cancellation_registration_list_chunk* m_chunk;
std::uint32_t m_entryIndex;
};
}
#endif
+71
View File
@@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
#define CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
namespace cppcoro
{
class cancellation_token;
namespace detail
{
class cancellation_state;
}
class cancellation_source
{
public:
/// Construct to a new cancellation source.
cancellation_source();
/// Create a new reference to the same underlying cancellation
/// source as \p other.
cancellation_source(const cancellation_source& other) noexcept;
cancellation_source(cancellation_source&& other) noexcept;
~cancellation_source();
cancellation_source& operator=(const cancellation_source& other) noexcept;
cancellation_source& operator=(cancellation_source&& other) noexcept;
/// Query if this cancellation source can be cancelled.
///
/// A cancellation source object will not be cancellable if it has
/// previously been moved into another cancellation_source instance
/// or was copied from a cancellation_source that was not cancellable.
bool can_be_cancelled() const noexcept;
/// Obtain a cancellation token that can be used to query if
/// cancellation has been requested on this source.
///
/// The cancellation token can be passed into functions that you
/// may want to later be able to request cancellation.
cancellation_token token() const noexcept;
/// Request cancellation of operations that were passed an associated
/// cancellation token.
///
/// Any cancellation callback registered via a cancellation_registration
/// object will be called inside this function by the first thread to
/// call this method.
///
/// This operation is a no-op if can_be_cancelled() returns false.
void request_cancellation();
/// Query if some thread has called 'request_cancellation()' on this
/// cancellation_source.
bool is_cancellation_requested() const noexcept;
private:
detail::cancellation_state* m_state;
};
}
#endif
+72
View File
@@ -0,0 +1,72 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
#define CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
namespace cppcoro
{
class cancellation_source;
class cancellation_registration;
namespace detail
{
class cancellation_state;
}
class cancellation_token
{
public:
/// Construct to a cancellation token that can't be cancelled.
cancellation_token() noexcept;
/// Copy another cancellation token.
///
/// New token will refer to the same underlying state.
cancellation_token(const cancellation_token& other) noexcept;
cancellation_token(cancellation_token&& other) noexcept;
~cancellation_token();
cancellation_token& operator=(const cancellation_token& other) noexcept;
cancellation_token& operator=(cancellation_token&& other) noexcept;
void swap(cancellation_token& other) noexcept;
/// Query if it is possible that this operation will be cancelled
/// or not.
///
/// Cancellable operations may be able to take more efficient code-paths
/// if they don't need to handle cancellation requests.
bool can_be_cancelled() const noexcept;
/// Query if some thread has requested cancellation on an associated
/// cancellation_source object.
bool is_cancellation_requested() const noexcept;
/// Throws cppcoro::operation_cancelled exception if cancellation
/// has been requested for the associated operation.
void throw_if_cancellation_requested() const;
private:
friend class cancellation_source;
friend class cancellation_registration;
cancellation_token(detail::cancellation_state* state) noexcept;
detail::cancellation_state* m_state;
};
inline void swap(cancellation_token& a, cancellation_token& b) noexcept
{
a.swap(b);
}
}
#endif
+166
View File
@@ -0,0 +1,166 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CONFIG_HPP_INCLUDED
#define CPPCORO_CONFIG_HPP_INCLUDED
/////////////////////////////////////////////////////////////////////////////
// Compiler Detection
#if defined(_MSC_VER)
# define CPPCORO_COMPILER_MSVC _MSC_FULL_VER
#else
# define CPPCORO_COMPILER_MSVC 0
#endif
#if defined(__clang__)
# define CPPCORO_COMPILER_CLANG (__clang_major__ * 10000 + \
__clang_minor__ * 100 + \
__clang_patchlevel__)
#else
# define CPPCORO_COMPILER_CLANG 0
#endif
#if defined(__GNUC__)
# define CPPCORO_COMPILER_GCC (__GNUC__ * 10000 + \
__GNUC_MINOR__ * 100 + \
__GNUC_PATCHLEVEL__)
#else
# define CPPCORO_COMPILER_GCC 0
#endif
/// \def CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
/// Defined to 1 if the compiler supports returning a coroutine_handle from
/// the await_suspend() method as a way of transferring execution
/// to another coroutine with a guaranteed tail-call.
#if CPPCORO_COMPILER_CLANG
# if __clang_major__ >= 7
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 1
# endif
#endif
#ifndef CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 0
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_ASSUME(X) __assume(X)
#else
# define CPPCORO_ASSUME(X)
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_NOINLINE __declspec(noinline)
#elif CPPCORO_COMPILER_CLANG || CPPCORO_COMPILER_GCC
# define CPPCORO_NOINLINE __attribute__((noinline))
#else
# define CPPCORO_NOINLINE
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_FORCE_INLINE __forceinline
#elif CPPCORO_COMPILER_CLANG
# define CPPCORO_FORCE_INLINE __attribute__((always_inline))
#else
# define CPPCORO_FORCE_INLINE inline
#endif
/////////////////////////////////////////////////////////////////////////////
// OS Detection
/// \def CPPCORO_OS_WINNT
/// Defined to non-zero if the target platform is a WindowsNT variant.
/// 0x0500 - Windows 2000
/// 0x0501 - Windows XP/Server 2003
/// 0x0502 - Windows XP SP2/Server 2003 SP1
/// 0x0600 - Windows Vista/Server 2008
/// 0x0601 - Windows 7
/// 0x0602 - Windows 8
/// 0x0603 - Windows 8.1
/// 0x0A00 - Windows 10
#if defined(_WIN32_WINNT) || defined(_WIN32)
# if !defined(_WIN32_WINNT)
// Default to targeting Windows 10 if not defined.
# define _WIN32_WINNT 0x0A00
# endif
# define CPPCORO_OS_WINNT _WIN32_WINNT
#else
# define CPPCORO_OS_WINNT 0
#endif
#if defined(__linux__)
# define CPPCORO_OS_LINUX 1
#else
# define CPPCORO_OS_LINUX 0
#endif
/////////////////////////////////////////////////////////////////////////////
// CPU Detection
/// \def CPPCORO_CPU_X86
/// Defined to 1 if target CPU is of x86 family.
#if CPPCORO_COMPILER_MSVC
# if defined(_M_IX86)
# define CPPCORO_CPU_X86 1
# endif
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
# if defined(__i386__)
# define CPPCORO_CPU_X86 1
# endif
#endif
#if !defined(CPPCORO_CPU_X86)
# define CPPCORO_CPU_X86 0
#endif
/// \def CPPCORO_CPU_X64
/// Defined to 1 if the target CPU is x64 family.
#if CPPCORO_COMPILER_MSVC
# if defined(_M_X64)
# define CPPCORO_CPU_X64 1
# endif
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
# if defined(__x86_64__)
# define CPPCORO_CPU_X64 1
# endif
#endif
#if !defined(CPPCORO_CPU_X64)
# define CPPCORO_CPU_X64 0
#endif
/// \def CPPCORO_CPU_32BIT
/// Defined if compiling for a 32-bit CPU architecture.
#if CPPCORO_CPU_X86
# define CPPCORO_CPU_32BIT 1
#else
# define CPPCORO_CPU_32BIT 0
#endif
/// \def CPPCORO_CPU_64BIT
/// Defined if compiling for a 64-bit CPU architecture.
#if CPPCORO_CPU_X64
# define CPPCORO_CPU_64BIT 1
#else
# define CPPCORO_CPU_64BIT 0
#endif
#if CPPCORO_COMPILER_MSVC
# define CPPCORO_CPU_CACHE_LINE std::hardware_destructive_interference_size
#else
// On most architectures we can assume a 64-byte cache line.
# define CPPCORO_CPU_CACHE_LINE 64
#endif
#if CPPCORO_COMPILER_MSVC
#if __has_include(<coroutine>)
#include <yvals_core.h>
#ifdef __cpp_lib_coroutine
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
#endif
#endif
#else
#if __has_include(<coroutine>)
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
#endif
#endif
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef CPPCORO_COROUTINE_HPP_INCLUDED
#define CPPCORO_COROUTINE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#ifdef CPPCORO_COROHEADER_FOUND_AND_USABLE
#include <coroutine>
namespace cppcoro {
using std::coroutine_handle;
using std::suspend_always;
using std::noop_coroutine;
using std::suspend_never;
}
#elif __has_include(<experimental/coroutine>)
#include <experimental/coroutine>
namespace cppcoro {
using std::experimental::coroutine_handle;
using std::experimental::suspend_always;
using std::experimental::suspend_never;
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
using std::experimental::noop_coroutine;
#endif
}
#else
#error Cppcoro requires a C++20 compiler with coroutine support
#endif
#endif
+22
View File
@@ -0,0 +1,22 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_ANY_HPP_INCLUDED
#define CPPCORO_DETAIL_ANY_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
// Helper type that can be cast-to from any type.
struct any
{
template<typename T>
any(T&&) noexcept
{}
};
}
}
#endif
+49
View File
@@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
#define CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
#include <cppcoro/detail/is_awaiter.hpp>
#include <cppcoro/detail/any.hpp>
namespace cppcoro
{
namespace detail
{
template<typename T>
auto get_awaiter_impl(T&& value, int)
noexcept(noexcept(static_cast<T&&>(value).operator co_await()))
-> decltype(static_cast<T&&>(value).operator co_await())
{
return static_cast<T&&>(value).operator co_await();
}
template<typename T>
auto get_awaiter_impl(T&& value, long)
noexcept(noexcept(operator co_await(static_cast<T&&>(value))))
-> decltype(operator co_await(static_cast<T&&>(value)))
{
return operator co_await(static_cast<T&&>(value));
}
template<
typename T,
std::enable_if_t<cppcoro::detail::is_awaiter<T&&>::value, int> = 0>
T&& get_awaiter_impl(T&& value, cppcoro::detail::any) noexcept
{
return static_cast<T&&>(value);
}
template<typename T>
auto get_awaiter(T&& value)
noexcept(noexcept(detail::get_awaiter_impl(static_cast<T&&>(value), 123)))
-> decltype(detail::get_awaiter_impl(static_cast<T&&>(value), 123))
{
return detail::get_awaiter_impl(static_cast<T&&>(value), 123);
}
}
}
#endif
+55
View File
@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
#define CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
#include <type_traits>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
namespace detail
{
template<typename T>
struct is_coroutine_handle
: std::false_type
{};
template<typename PROMISE>
struct is_coroutine_handle<cppcoro::coroutine_handle<PROMISE>>
: std::true_type
{};
// NOTE: We're accepting a return value of coroutine_handle<P> here
// which is an extension supported by Clang which is not yet part of
// the C++ coroutines TS.
template<typename T>
struct is_valid_await_suspend_return_value : std::disjunction<
std::is_void<T>,
std::is_same<T, bool>,
is_coroutine_handle<T>>
{};
template<typename T, typename = std::void_t<>>
struct is_awaiter : std::false_type {};
// NOTE: We're testing whether await_suspend() will be callable using an
// arbitrary coroutine_handle here by checking if it supports being passed
// a coroutine_handle<void>. This may result in a false-result for some
// types which are only awaitable within a certain context.
template<typename T>
struct is_awaiter<T, std::void_t<
decltype(std::declval<T>().await_ready()),
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>())),
decltype(std::declval<T>().await_resume())>> :
std::conjunction<
std::is_constructible<bool, decltype(std::declval<T>().await_ready())>,
detail::is_valid_await_suspend_return_value<
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>()))>>
{};
}
}
#endif
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
#include <chrono>
#include <span>
#include <cppcoro/config.hpp>
#if CPPCORO_OS_LINUX || (CPPCORO_OS_WINNT >= 0x0602)
# include <atomic>
# include <cstdint>
#elif CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#else
# include <mutex>
# include <condition_variable>
#endif
namespace cppcoro
{
class io_service;
namespace detail
{
class lightweight_manual_reset_event
{
public:
lightweight_manual_reset_event(bool initiallySet = false);
~lightweight_manual_reset_event();
void set() noexcept;
void reset() noexcept;
void wait() noexcept;
void wait(std::span<io_service> srvs, std::chrono::system_clock::duration step) noexcept;
private:
#if CPPCORO_OS_LINUX
std::atomic<int> m_value;
#elif CPPCORO_OS_WINNT >= 0x0602
// Windows 8 or newer we can use WaitOnAddress()
std::atomic<std::uint8_t> m_value;
#elif CPPCORO_OS_WINNT
// Before Windows 8 we need to use a WIN32 manual reset event.
cppcoro::detail::win32::handle_t m_eventHandle;
#else
// For other platforms that don't have a native futex
// or manual reset event we can just use a std::mutex
// and std::condition_variable to perform the wait.
// Not so lightweight, but should be portable to all platforms.
std::mutex m_mutex;
std::condition_variable m_cv;
bool m_isSet;
#endif
};
}
}
#endif
+120
View File
@@ -0,0 +1,120 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
#define CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
#include <type_traits>
#include <memory>
namespace cppcoro::detail
{
template<typename T>
struct manual_lifetime
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
template<typename... Args>
std::enable_if_t<std::is_constructible_v<T, Args&&...>> construct(Args&&... args)
noexcept(std::is_nothrow_constructible_v<T, Args&&...>)
{
::new (static_cast<void*>(std::addressof(m_value))) T(static_cast<Args&&>(args)...);
}
void destruct() noexcept(std::is_nothrow_destructible_v<T>)
{
m_value.~T();
}
std::add_pointer_t<T> operator->() noexcept { return std::addressof(**this); }
std::add_pointer_t<const T> operator->() const noexcept { return std::addressof(**this); }
T& operator*() & noexcept { return m_value; }
const T& operator*() const & noexcept { return m_value; }
T&& operator*() && noexcept { return static_cast<T&&>(m_value); }
const T&& operator*() const && noexcept { return static_cast<const T&&>(m_value); }
private:
union {
T m_value;
};
};
template<typename T>
struct manual_lifetime<T&>
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
void construct(T& value) noexcept
{
m_value = std::addressof(value);
}
void destruct() noexcept {}
T* operator->() noexcept { return m_value; }
const T* operator->() const noexcept { return m_value; }
T& operator*() noexcept { return *m_value; }
const T& operator*() const noexcept { return *m_value; }
private:
T* m_value;
};
template<typename T>
struct manual_lifetime<T&&>
{
public:
manual_lifetime() noexcept {}
~manual_lifetime() noexcept {}
manual_lifetime(const manual_lifetime&) = delete;
manual_lifetime(manual_lifetime&&) = delete;
manual_lifetime& operator=(const manual_lifetime&) = delete;
manual_lifetime& operator=(manual_lifetime&&) = delete;
void construct(T&& value) noexcept
{
m_value = std::addressof(value);
}
void destruct() noexcept {}
T* operator->() noexcept { return m_value; }
const T* operator->() const noexcept { return m_value; }
T& operator*() & noexcept { return *m_value; }
const T& operator*() const & noexcept { return *m_value; }
T&& operator*() && noexcept { return static_cast<T&&>(*m_value); }
const T&& operator*() const && noexcept { return static_cast<const T&&>(*m_value); }
private:
T* m_value;
};
template<>
struct manual_lifetime<void>
{
void construct() noexcept {}
void destruct() noexcept {}
void operator*() const noexcept {}
};
}
#endif
@@ -0,0 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
#define CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
template<typename T>
struct remove_rvalue_reference
{
using type = T;
};
template<typename T>
struct remove_rvalue_reference<T&&>
{
using type = T;
};
template<typename T>
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;
}
}
#endif
+300
View File
@@ -0,0 +1,300 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
#define CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
#include <cppcoro/coroutine.hpp>
#include <cassert>
#include <exception>
#include <utility>
namespace cppcoro
{
namespace detail
{
template<typename RESULT>
class sync_wait_task;
template<typename RESULT>
class sync_wait_task_promise final
{
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<RESULT>>;
public:
using reference = RESULT&&;
sync_wait_task_promise() noexcept
{}
void start(detail::lightweight_manual_reset_event& event)
{
m_event = &event;
coroutine_handle_t::from_promise(*this).resume();
}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coroutine) const noexcept
{
coroutine.promise().m_event->set();
}
void await_resume() noexcept {}
};
return completion_notifier{};
}
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
// See comment in make_sync_wait_task below.
template<typename Awaitable>
Awaitable&& await_transform(Awaitable&& awaitable)
{
return static_cast<Awaitable&&>(awaitable);
}
struct get_promise_t {};
static constexpr get_promise_t get_promise = {};
auto await_transform(get_promise_t)
{
class awaiter
{
public:
awaiter(sync_wait_task_promise* promise) noexcept : m_promise(promise) {}
bool await_ready() noexcept {
return true;
}
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
sync_wait_task_promise& await_resume() noexcept
{
return *m_promise;
}
private:
sync_wait_task_promise* m_promise;
};
return awaiter{ this };
}
#endif
auto yield_value(reference result) noexcept
{
m_result = std::addressof(result);
return final_suspend();
}
void return_void() noexcept
{
// The coroutine should have either yielded a value or thrown
// an exception in which case it should have bypassed return_void().
assert(false);
}
void unhandled_exception()
{
m_exception = std::current_exception();
}
reference result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
return static_cast<reference>(*m_result);
}
private:
detail::lightweight_manual_reset_event* m_event;
std::remove_reference_t<RESULT>* m_result;
std::exception_ptr m_exception;
};
template<>
class sync_wait_task_promise<void>
{
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<void>>;
public:
sync_wait_task_promise() noexcept
{}
void start(detail::lightweight_manual_reset_event& event)
{
m_event = &event;
coroutine_handle_t::from_promise(*this).resume();
}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coroutine) const noexcept
{
coroutine.promise().m_event->set();
}
void await_resume() noexcept {}
};
return completion_notifier{};
}
void return_void() {}
void unhandled_exception()
{
m_exception = std::current_exception();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
detail::lightweight_manual_reset_event* m_event;
std::exception_ptr m_exception;
};
template<typename RESULT>
class sync_wait_task final
{
public:
using promise_type = sync_wait_task_promise<RESULT>;
using coroutine_handle_t = cppcoro::coroutine_handle<promise_type>;
sync_wait_task(coroutine_handle_t coroutine) noexcept
: m_coroutine(coroutine)
{}
sync_wait_task(sync_wait_task&& other) noexcept
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
{}
~sync_wait_task()
{
if (m_coroutine) m_coroutine.destroy();
}
sync_wait_task(const sync_wait_task&) = delete;
sync_wait_task& operator=(const sync_wait_task&) = delete;
void start(lightweight_manual_reset_event& event) noexcept
{
m_coroutine.promise().start(event);
}
decltype(auto) result()
{
return m_coroutine.promise().result();
}
private:
coroutine_handle_t m_coroutine;
};
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Work around bug in MSVC where passing a parameter by universal reference
// results in an error when passed a move-only type, complaining that the copy-constructor
// has been deleted. The parameter should be passed by reference and the compiler should
// notcalling the copy-constructor for the argument
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
{
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield an never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await sync_wait_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
//co_yield co_await std::forward<AWAITABLE>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
#else
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE&& awaitable)
{
co_yield co_await std::forward<AWAITABLE>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
sync_wait_task<void> make_sync_wait_task(AWAITABLE&& awaitable)
{
co_await std::forward<AWAITABLE>(awaitable);
}
#endif
}
}
#endif
@@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
#define CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
#include <functional>
namespace cppcoro
{
namespace detail
{
template<typename T>
struct unwrap_reference
{
using type = T;
};
template<typename T>
struct unwrap_reference<std::reference_wrapper<T>>
{
using type = T;
};
template<typename T>
using unwrap_reference_t = typename unwrap_reference<T>::type;
}
}
#endif
+16
View File
@@ -0,0 +1,16 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
#define CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
namespace cppcoro
{
namespace detail
{
struct void_value {};
}
}
#endif
@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
namespace detail
{
class when_all_counter
{
public:
when_all_counter(std::size_t count) noexcept
: m_count(count + 1)
, m_awaitingCoroutine(nullptr)
{}
bool is_ready() const noexcept
{
// We consider this complete if we're asking whether it's ready
// after a coroutine has already been registered.
return static_cast<bool>(m_awaitingCoroutine);
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1;
}
void notify_awaitable_completed() noexcept
{
if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1)
{
m_awaitingCoroutine.resume();
}
}
protected:
std::atomic<std::size_t> m_count;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
}
}
#endif
@@ -0,0 +1,258 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
#include <cppcoro/detail/when_all_counter.hpp>
#include <cppcoro/coroutine.hpp>
#include <tuple>
namespace cppcoro
{
namespace detail
{
template<typename TASK_CONTAINER>
class when_all_ready_awaitable;
template<>
class when_all_ready_awaitable<std::tuple<>>
{
public:
constexpr when_all_ready_awaitable() noexcept {}
explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {}
constexpr bool await_ready() const noexcept { return true; }
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
std::tuple<> await_resume() const noexcept { return {}; }
};
template<typename... TASKS>
class when_all_ready_awaitable<std::tuple<TASKS...>>
{
public:
explicit when_all_ready_awaitable(TASKS&&... tasks)
noexcept(std::conjunction_v<std::is_nothrow_move_constructible<TASKS>...>)
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(tasks)...)
{}
explicit when_all_ready_awaitable(std::tuple<TASKS...>&& tasks)
noexcept(std::is_nothrow_move_constructible_v<std::tuple<TASKS...>>)
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(tasks))
{}
when_all_ready_awaitable(when_all_ready_awaitable&& other) noexcept
: m_counter(sizeof...(TASKS))
, m_tasks(std::move(other.m_tasks))
{}
auto operator co_await() & noexcept
{
struct awaiter
{
awaiter(when_all_ready_awaitable& awaitable) noexcept
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
std::tuple<TASKS...>& await_resume() noexcept
{
return m_awaitable.m_tasks;
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
auto operator co_await() && noexcept
{
struct awaiter
{
awaiter(when_all_ready_awaitable& awaitable) noexcept
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
std::tuple<TASKS...>&& await_resume() noexcept
{
return std::move(m_awaitable.m_tasks);
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
private:
bool is_ready() const noexcept
{
return m_counter.is_ready();
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
start_tasks(std::make_integer_sequence<std::size_t, sizeof...(TASKS)>{});
return m_counter.try_await(awaitingCoroutine);
}
template<std::size_t... INDICES>
void start_tasks(std::integer_sequence<std::size_t, INDICES...>) noexcept
{
(void)std::initializer_list<int>{
(std::get<INDICES>(m_tasks).start(m_counter), 0)...
};
}
when_all_counter m_counter;
std::tuple<TASKS...> m_tasks;
};
template<typename TASK_CONTAINER>
class when_all_ready_awaitable
{
public:
explicit when_all_ready_awaitable(TASK_CONTAINER&& tasks) noexcept
: m_counter(tasks.size())
, m_tasks(std::forward<TASK_CONTAINER>(tasks))
{}
when_all_ready_awaitable(when_all_ready_awaitable&& other)
noexcept(std::is_nothrow_move_constructible_v<TASK_CONTAINER>)
: m_counter(other.m_tasks.size())
, m_tasks(std::move(other.m_tasks))
{}
when_all_ready_awaitable(const when_all_ready_awaitable&) = delete;
when_all_ready_awaitable& operator=(const when_all_ready_awaitable&) = delete;
auto operator co_await() & noexcept
{
class awaiter
{
public:
awaiter(when_all_ready_awaitable& awaitable)
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
TASK_CONTAINER& await_resume() noexcept
{
return m_awaitable.m_tasks;
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
auto operator co_await() && noexcept
{
class awaiter
{
public:
awaiter(when_all_ready_awaitable& awaitable)
: m_awaitable(awaitable)
{}
bool await_ready() const noexcept
{
return m_awaitable.is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_awaitable.try_await(awaitingCoroutine);
}
TASK_CONTAINER&& await_resume() noexcept
{
return std::move(m_awaitable.m_tasks);
}
private:
when_all_ready_awaitable& m_awaitable;
};
return awaiter{ *this };
}
private:
bool is_ready() const noexcept
{
return m_counter.is_ready();
}
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
for (auto&& task : m_tasks)
{
task.start(m_counter);
}
return m_counter.try_await(awaitingCoroutine);
}
when_all_counter m_counter;
TASK_CONTAINER m_tasks;
};
}
}
#endif
+357
View File
@@ -0,0 +1,357 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
#define CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/when_all_counter.hpp>
#include <cppcoro/detail/void_value.hpp>
#include <cppcoro/coroutine.hpp>
#include <cassert>
#include <exception>
namespace cppcoro
{
namespace detail
{
template<typename TASK_CONTAINER>
class when_all_ready_awaitable;
template<typename RESULT>
class when_all_task;
template<typename RESULT>
class when_all_task_promise final
{
public:
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<RESULT>>;
when_all_task_promise() noexcept
{}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coro) const noexcept
{
coro.promise().m_counter->notify_awaitable_completed();
}
void await_resume() const noexcept {}
};
return completion_notifier{};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept
{
// We should have either suspended at co_yield point or
// an exception was thrown before running off the end of
// the coroutine.
assert(false);
}
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
// See comment in make_when_all_task below.
template<typename Awaitable>
Awaitable&& await_transform(Awaitable&& awaitable)
{
return static_cast<Awaitable&&>(awaitable);
}
struct get_promise_t {};
static constexpr get_promise_t get_promise = {};
auto await_transform(get_promise_t)
{
class awaiter
{
public:
awaiter(when_all_task_promise* promise) noexcept : m_promise(promise) {}
bool await_ready() noexcept {
return true;
}
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
when_all_task_promise& await_resume() noexcept
{
return *m_promise;
}
private:
when_all_task_promise* m_promise;
};
return awaiter{ this };
}
#endif
auto yield_value(RESULT&& result) noexcept
{
m_result = std::addressof(result);
return final_suspend();
}
void start(when_all_counter& counter) noexcept
{
m_counter = &counter;
coroutine_handle_t::from_promise(*this).resume();
}
RESULT& result() &
{
rethrow_if_exception();
return *m_result;
}
RESULT&& result() &&
{
rethrow_if_exception();
return std::forward<RESULT>(*m_result);
}
private:
void rethrow_if_exception()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
when_all_counter* m_counter;
std::exception_ptr m_exception;
std::add_pointer_t<RESULT> m_result;
};
template<>
class when_all_task_promise<void> final
{
public:
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<void>>;
when_all_task_promise() noexcept
{}
auto get_return_object() noexcept
{
return coroutine_handle_t::from_promise(*this);
}
cppcoro::suspend_always initial_suspend() noexcept
{
return{};
}
auto final_suspend() noexcept
{
class completion_notifier
{
public:
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle_t coro) const noexcept
{
coro.promise().m_counter->notify_awaitable_completed();
}
void await_resume() const noexcept {}
};
return completion_notifier{};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept
{
}
void start(when_all_counter& counter) noexcept
{
m_counter = &counter;
coroutine_handle_t::from_promise(*this).resume();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
when_all_counter* m_counter;
std::exception_ptr m_exception;
};
template<typename RESULT>
class when_all_task final
{
public:
using promise_type = when_all_task_promise<RESULT>;
using coroutine_handle_t = typename promise_type::coroutine_handle_t;
when_all_task(coroutine_handle_t coroutine) noexcept
: m_coroutine(coroutine)
{}
when_all_task(when_all_task&& other) noexcept
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
{}
~when_all_task()
{
if (m_coroutine) m_coroutine.destroy();
}
when_all_task(const when_all_task&) = delete;
when_all_task& operator=(const when_all_task&) = delete;
decltype(auto) result() &
{
return m_coroutine.promise().result();
}
decltype(auto) result() &&
{
return std::move(m_coroutine.promise()).result();
}
decltype(auto) non_void_result() &
{
if constexpr (std::is_void_v<decltype(this->result())>)
{
this->result();
return void_value{};
}
else
{
return this->result();
}
}
decltype(auto) non_void_result() &&
{
if constexpr (std::is_void_v<decltype(this->result())>)
{
std::move(*this).result();
return void_value{};
}
else
{
return std::move(*this).result();
}
}
private:
template<typename TASK_CONTAINER>
friend class when_all_ready_awaitable;
void start(when_all_counter& counter) noexcept
{
m_coroutine.promise().start(counter);
}
coroutine_handle_t m_coroutine;
};
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
{
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield an never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
#else
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
#endif
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
when_all_task<void> make_when_all_task(AWAITABLE awaitable)
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
{
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
// to completely ignore the co_yield and never calls promise.yield_value().
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
// rather than before the 'co_yield'.
// This bug is present in VS 2017.7 and VS 2017.8.
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
co_await promise.yield_value(co_await awaitable.get());
#else
co_yield co_await awaitable.get();
#endif
}
template<
typename AWAITABLE,
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
when_all_task<void> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
{
co_await awaitable.get();
}
}
}
#endif
+179
View File
@@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WIN32_HPP_INCLUDED
#define CPPCORO_DETAIL_WIN32_HPP_INCLUDED
#include <cppcoro/config.hpp>
#if !CPPCORO_OS_WINNT
# error <cppcoro/detail/win32.hpp> is only supported on the Windows platform.
#endif
#include <utility>
#include <cstdint>
struct _OVERLAPPED;
namespace cppcoro
{
namespace detail
{
namespace win32
{
using handle_t = void*;
using ulongptr_t = std::uintptr_t;
using longptr_t = std::intptr_t;
using dword_t = unsigned long;
using socket_t = std::uintptr_t;
using ulong_t = unsigned long;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4201) // Non-standard anonymous struct/union
#endif
/// Structure needs to correspond exactly to the builtin
/// _OVERLAPPED structure from Windows.h.
struct overlapped
{
ulongptr_t Internal;
ulongptr_t InternalHigh;
union
{
struct
{
dword_t Offset;
dword_t OffsetHigh;
};
void* Pointer;
};
handle_t hEvent;
};
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
struct wsabuf
{
constexpr wsabuf() noexcept
: len(0)
, buf(nullptr)
{}
constexpr wsabuf(void* ptr, std::size_t size)
: len(size <= ulong_t(-1) ? ulong_t(size) : ulong_t(-1))
, buf(static_cast<char*>(ptr))
{}
ulong_t len;
char* buf;
};
struct io_state : win32::overlapped
{
using callback_type = void(
io_state* state,
win32::dword_t errorCode,
win32::dword_t numberOfBytesTransferred,
win32::ulongptr_t completionKey);
io_state(callback_type* callback = nullptr) noexcept
: io_state(std::uint64_t(0), callback)
{}
io_state(void* pointer, callback_type* callback) noexcept
: m_callback(callback)
{
this->Internal = 0;
this->InternalHigh = 0;
this->Pointer = pointer;
this->hEvent = nullptr;
}
io_state(std::uint64_t offset, callback_type* callback) noexcept
: m_callback(callback)
{
this->Internal = 0;
this->InternalHigh = 0;
this->Offset = static_cast<dword_t>(offset);
this->OffsetHigh = static_cast<dword_t>(offset >> 32);
this->hEvent = nullptr;
}
callback_type* m_callback;
};
class safe_handle
{
public:
safe_handle()
: m_handle(nullptr)
{}
explicit safe_handle(handle_t handle)
: m_handle(handle)
{}
safe_handle(const safe_handle& other) = delete;
safe_handle(safe_handle&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
~safe_handle()
{
close();
}
safe_handle& operator=(safe_handle handle) noexcept
{
swap(handle);
return *this;
}
constexpr handle_t handle() const { return m_handle; }
/// Calls CloseHandle() and sets the handle to NULL.
void close() noexcept;
void swap(safe_handle& other) noexcept
{
std::swap(m_handle, other.m_handle);
}
bool operator==(const safe_handle& other) const
{
return m_handle == other.m_handle;
}
bool operator!=(const safe_handle& other) const
{
return m_handle != other.m_handle;
}
bool operator==(handle_t handle) const
{
return m_handle == handle;
}
bool operator!=(handle_t handle) const
{
return m_handle != handle;
}
private:
handle_t m_handle;
};
}
}
}
#endif
@@ -0,0 +1,376 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
#define CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/operation_cancelled.hpp>
#include <cppcoro/detail/win32.hpp>
#include <optional>
#include <system_error>
#include <cppcoro/coroutine.hpp>
#include <cassert>
namespace cppcoro
{
namespace detail
{
class win32_overlapped_operation_base
: protected detail::win32::io_state
{
public:
win32_overlapped_operation_base(
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
win32_overlapped_operation_base(
void* pointer,
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(pointer, callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
win32_overlapped_operation_base(
std::uint64_t offset,
detail::win32::io_state::callback_type* callback) noexcept
: detail::win32::io_state(offset, callback)
, m_errorCode(0)
, m_numberOfBytesTransferred(0)
{}
_OVERLAPPED* get_overlapped() noexcept
{
return reinterpret_cast<_OVERLAPPED*>(
static_cast<detail::win32::overlapped*>(this));
}
std::size_t get_result()
{
if (m_errorCode != 0)
{
throw std::system_error{
static_cast<int>(m_errorCode),
std::system_category()
};
}
return m_numberOfBytesTransferred;
}
detail::win32::dword_t m_errorCode;
detail::win32::dword_t m_numberOfBytesTransferred;
};
template<typename OPERATION>
class win32_overlapped_operation
: protected win32_overlapped_operation_base
{
protected:
win32_overlapped_operation() noexcept
: win32_overlapped_operation_base(
&win32_overlapped_operation::on_operation_completed)
{}
win32_overlapped_operation(void* pointer) noexcept
: win32_overlapped_operation_base(
pointer,
&win32_overlapped_operation::on_operation_completed)
{}
win32_overlapped_operation(std::uint64_t offset) noexcept
: win32_overlapped_operation_base(
offset,
&win32_overlapped_operation::on_operation_completed)
{}
public:
bool await_ready() const noexcept { return false; }
CPPCORO_NOINLINE
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
{
static_assert(std::is_base_of_v<win32_overlapped_operation, OPERATION>);
m_awaitingCoroutine = awaitingCoroutine;
return static_cast<OPERATION*>(this)->try_start();
}
decltype(auto) await_resume()
{
return static_cast<OPERATION*>(this)->get_result();
}
private:
static void on_operation_completed(
detail::win32::io_state* ioState,
detail::win32::dword_t errorCode,
detail::win32::dword_t numberOfBytesTransferred,
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
{
auto* operation = static_cast<win32_overlapped_operation*>(ioState);
operation->m_errorCode = errorCode;
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
operation->m_awaitingCoroutine.resume();
}
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
template<typename OPERATION>
class win32_overlapped_operation_cancellable
: protected win32_overlapped_operation_base
{
// ERROR_OPERATION_ABORTED value from <Windows.h>
static constexpr detail::win32::dword_t error_operation_aborted = 995L;
protected:
win32_overlapped_operation_cancellable(cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(&win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
void* pointer,
cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(pointer, &win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
std::uint64_t offset,
cancellation_token&& ct) noexcept
: win32_overlapped_operation_base(offset, &win32_overlapped_operation_cancellable::on_operation_completed)
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
, m_cancellationToken(std::move(ct))
{
m_errorCode = error_operation_aborted;
}
win32_overlapped_operation_cancellable(
win32_overlapped_operation_cancellable&& other) noexcept
: win32_overlapped_operation_base(std::move(other))
, m_state(other.m_state.load(std::memory_order_relaxed))
, m_cancellationToken(std::move(other.m_cancellationToken))
{
assert(m_errorCode == other.m_errorCode);
assert(m_numberOfBytesTransferred == other.m_numberOfBytesTransferred);
}
public:
bool await_ready() const noexcept
{
return m_state.load(std::memory_order_relaxed) == state::completed;
}
CPPCORO_NOINLINE
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
{
static_assert(std::is_base_of_v<win32_overlapped_operation_cancellable, OPERATION>);
m_awaitingCoroutine = awaitingCoroutine;
// TRICKY: Register cancellation callback before starting the operation
// in case the callback registration throws due to insufficient
// memory. We need to make sure that the logic that occurs after
// starting the operation is noexcept, otherwise we run into the
// problem of not being able to cancel the started operation and
// the dilemma of what to do with the exception.
//
// However, doing this means that the cancellation callback may run
// prior to returning below so in the case that cancellation may
// occur we defer setting the state to 'started' until after
// the operation has finished starting. The cancellation callback
// will only attempt to request cancellation of the operation with
// CancelIoEx() once the state has been set to 'started'.
const bool canBeCancelled = m_cancellationToken.can_be_cancelled();
if (canBeCancelled)
{
m_cancellationCallback.emplace(
std::move(m_cancellationToken),
[this] { this->on_cancellation_requested(); });
}
else
{
m_state.store(state::started, std::memory_order_relaxed);
}
// Now start the operation.
const bool willCompleteAsynchronously = static_cast<OPERATION*>(this)->try_start();
if (!willCompleteAsynchronously)
{
// Operation completed synchronously, resume awaiting coroutine immediately.
return false;
}
if (canBeCancelled)
{
// Need to flag that the operation has finished starting now.
// However, the operation may have completed concurrently on
// another thread, transitioning directly from not_started -> complete.
// Or it may have had the cancellation callback execute and transition
// from not_started -> cancellation_requested. We use a compare-exchange
// to determine a winner between these potential racing cases.
state oldState = state::not_started;
if (!m_state.compare_exchange_strong(
oldState,
state::started,
std::memory_order_release,
std::memory_order_acquire))
{
if (oldState == state::cancellation_requested)
{
// Request the operation be cancelled.
// Note that it may have already completed on a background
// thread by now so this request for cancellation may end up
// being ignored.
static_cast<OPERATION*>(this)->cancel();
if (!m_state.compare_exchange_strong(
oldState,
state::started,
std::memory_order_release,
std::memory_order_acquire))
{
assert(oldState == state::completed);
return false;
}
}
else
{
assert(oldState == state::completed);
return false;
}
}
}
return true;
}
decltype(auto) await_resume()
{
// Free memory used by the cancellation callback now that the operation
// has completed rather than waiting until the operation object destructs.
// eg. If the operation is passed to when_all() then the operation object
// may not be destructed until all of the operations complete.
m_cancellationCallback.reset();
if (m_errorCode == error_operation_aborted)
{
throw operation_cancelled{};
}
return static_cast<OPERATION*>(this)->get_result();
}
private:
enum class state
{
not_started,
started,
cancellation_requested,
completed
};
void on_cancellation_requested() noexcept
{
auto oldState = m_state.load(std::memory_order_acquire);
if (oldState == state::not_started)
{
// This callback is running concurrently with await_suspend().
// The call to start the operation may not have returned yet so
// we can't safely request cancellation of it. Instead we try to
// notify the await_suspend() thread by transitioning the state
// to state::cancellation_requested so that the await_suspend()
// thread can request cancellation after it has finished starting
// the operation.
const bool transferredCancelResponsibility =
m_state.compare_exchange_strong(
oldState,
state::cancellation_requested,
std::memory_order_release,
std::memory_order_acquire);
if (transferredCancelResponsibility)
{
return;
}
}
// No point requesting cancellation if the operation has already completed.
if (oldState != state::completed)
{
static_cast<OPERATION*>(this)->cancel();
}
}
static void on_operation_completed(
detail::win32::io_state* ioState,
detail::win32::dword_t errorCode,
detail::win32::dword_t numberOfBytesTransferred,
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
{
auto* operation = static_cast<win32_overlapped_operation_cancellable*>(ioState);
operation->m_errorCode = errorCode;
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
auto state = operation->m_state.load(std::memory_order_acquire);
if (state == state::started)
{
operation->m_state.store(state::completed, std::memory_order_relaxed);
operation->m_awaitingCoroutine.resume();
}
else
{
// We are racing with await_suspend() call suspending.
// Try to mark it as completed using an atomic exchange and look
// at the previous value to determine whether the coroutine suspended
// first (in which case we resume it now) or we marked it as completed
// first (in which case await_suspend() will return false and immediately
// resume the coroutine).
state = operation->m_state.exchange(
state::completed,
std::memory_order_acq_rel);
if (state == state::started)
{
// The await_suspend() method returned (or will return) 'true' and so
// we need to resume the coroutine.
operation->m_awaitingCoroutine.resume();
}
}
}
std::atomic<state> m_state;
cppcoro::cancellation_token m_cancellationToken;
std::optional<cppcoro::cancellation_registration> m_cancellationCallback;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
}
}
#endif
+54
View File
@@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_HPP_INCLUDED
#define CPPCORO_FILE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class io_service;
class file
{
public:
file(file&& other) noexcept = default;
virtual ~file();
/// Get the size of the file in bytes.
std::uint64_t size() const;
protected:
#if CPPCORO_OS_WINNT
file(detail::win32::safe_handle&& fileHandle) noexcept;
static detail::win32::safe_handle open(
detail::win32::dword_t fileAccess,
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode,
file_share_mode shareMode,
file_buffering_mode bufferingMode);
detail::win32::safe_handle m_fileHandle;
#endif
};
}
#endif
+32
View File
@@ -0,0 +1,32 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
#define CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_buffering_mode
{
default_ = 0,
sequential = 1,
random_access = 2,
unbuffered = 4,
write_through = 8,
temporary = 16
};
constexpr file_buffering_mode operator&(file_buffering_mode a, file_buffering_mode b)
{
return static_cast<file_buffering_mode>(
static_cast<int>(a) & static_cast<int>(b));
}
constexpr file_buffering_mode operator|(file_buffering_mode a, file_buffering_mode b)
{
return static_cast<file_buffering_mode>(static_cast<int>(a) | static_cast<int>(b));
}
}
#endif
+40
View File
@@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
#define CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_open_mode
{
/// Open an existing file.
///
/// If file does not already exist when opening the file then raises
/// an exception.
open_existing,
/// Create a new file, overwriting an existing file if one exists.
///
/// If a file exists at the path then it is overwitten with a new file.
/// If no file exists at the path then a new one is created.
create_always,
/// Create a new file.
///
/// If the file already exists then raises an exception.
create_new,
/// Open the existing file if one exists, otherwise create a new empty
/// file.
create_or_open,
/// Open the existing file, truncating the file size to zero.
///
/// If the file does not exist then raises an exception.
truncate_existing
};
}
#endif
+99
View File
@@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
#define CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <atomic>
#include <optional>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
class file_read_operation_impl
{
public:
file_read_operation_impl(
detail::win32::handle_t fileHandle,
void* buffer,
std::size_t byteCount) noexcept
: m_fileHandle(fileHandle)
, m_buffer(buffer)
, m_byteCount(byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
detail::win32::handle_t m_fileHandle;
void* m_buffer;
std::size_t m_byteCount;
};
class file_read_operation
: public cppcoro::detail::win32_overlapped_operation<file_read_operation>
{
public:
file_read_operation(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
void* buffer,
std::size_t byteCount) noexcept
: cppcoro::detail::win32_overlapped_operation<file_read_operation>(fileOffset)
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<file_read_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
file_read_operation_impl m_impl;
};
class file_read_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>
{
public:
file_read_operation_cancellable(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
void* buffer,
std::size_t byteCount,
cancellation_token&& cancellationToken) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>(
fileOffset, std::move(cancellationToken))
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
file_read_operation_impl m_impl;
};
#endif
}
#endif
+45
View File
@@ -0,0 +1,45 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
#define CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
namespace cppcoro
{
enum class file_share_mode
{
/// Don't allow any other processes to open the file concurrently.
none = 0,
/// Allow other processes to open the file in read-only mode
/// concurrently with this process opening the file.
read = 1,
/// Allow other processes to open the file in write-only mode
/// concurrently with this process opening the file.
write = 2,
/// Allow other processes to open the file in read and/or write mode
/// concurrently with this process opening the file.
read_write = read | write,
/// Allow other processes to delete the file while this process
/// has the file open.
delete_ = 4
};
constexpr file_share_mode operator|(file_share_mode a, file_share_mode b)
{
return static_cast<file_share_mode>(
static_cast<int>(a) | static_cast<int>(b));
}
constexpr file_share_mode operator&(file_share_mode a, file_share_mode b)
{
return static_cast<file_share_mode>(
static_cast<int>(a) & static_cast<int>(b));
}
}
#endif
+98
View File
@@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
#define CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <atomic>
#include <optional>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
class file_write_operation_impl
{
public:
file_write_operation_impl(
detail::win32::handle_t fileHandle,
const void* buffer,
std::size_t byteCount) noexcept
: m_fileHandle(fileHandle)
, m_buffer(buffer)
, m_byteCount(byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
detail::win32::handle_t m_fileHandle;
const void* m_buffer;
std::size_t m_byteCount;
};
class file_write_operation
: public cppcoro::detail::win32_overlapped_operation<file_write_operation>
{
public:
file_write_operation(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
const void* buffer,
std::size_t byteCount) noexcept
: cppcoro::detail::win32_overlapped_operation<file_write_operation>(fileOffset)
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<file_write_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
file_write_operation_impl m_impl;
};
class file_write_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>
{
public:
file_write_operation_cancellable(
detail::win32::handle_t fileHandle,
std::uint64_t fileOffset,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>(fileOffset, std::move(ct))
, m_impl(fileHandle, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
file_write_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef CPPCORO_FILESYSTEM_HPP_INCLUDED
#define CPPCORO_FILESYSTEM_HPP_INCLUDED
#if __has_include(<filesystem>)
#include <filesystem>
namespace cppcoro {
namespace filesystem = std::filesystem;
}
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace cppcoro {
namespace filesystem = std::experimental::filesystem;
}
#else
#error Cppcoro requires a C++20 compiler with filesystem support
#endif
#endif
+169
View File
@@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_FMAP_HPP_INCLUDED
#define CPPCORO_FMAP_HPP_INCLUDED
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <utility>
#include <type_traits>
#include <functional>
namespace cppcoro
{
namespace detail
{
template<typename FUNC, typename AWAITABLE>
class fmap_awaiter
{
using awaiter_t = typename awaitable_traits<AWAITABLE&&>::awaiter_t;
FUNC&& m_func;
awaiter_t m_awaiter;
public:
fmap_awaiter(FUNC&& func, AWAITABLE&& awaitable)
noexcept(
std::is_nothrow_move_constructible_v<awaiter_t> &&
noexcept(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable))))
: m_func(static_cast<FUNC&&>(func))
, m_awaiter(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable)))
{}
decltype(auto) await_ready()
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_ready()))
{
return static_cast<awaiter_t&&>(m_awaiter).await_ready();
}
template<typename PROMISE>
decltype(auto) await_suspend(cppcoro::coroutine_handle<PROMISE> coro)
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro))))
{
return static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro));
}
template<
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
decltype(auto) await_resume()
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func))))
{
static_cast<awaiter_t&&>(m_awaiter).await_resume();
return std::invoke(static_cast<FUNC&&>(m_func));
}
template<
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
decltype(auto) await_resume()
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func), static_cast<awaiter_t&&>(m_awaiter).await_resume())))
{
return std::invoke(
static_cast<FUNC&&>(m_func),
static_cast<awaiter_t&&>(m_awaiter).await_resume());
}
};
template<typename FUNC, typename AWAITABLE>
class fmap_awaitable
{
static_assert(!std::is_lvalue_reference_v<FUNC>);
static_assert(!std::is_lvalue_reference_v<AWAITABLE>);
public:
template<
typename FUNC_ARG,
typename AWAITABLE_ARG,
std::enable_if_t<
std::is_constructible_v<FUNC, FUNC_ARG&&> &&
std::is_constructible_v<AWAITABLE, AWAITABLE_ARG&&>, int> = 0>
explicit fmap_awaitable(FUNC_ARG&& func, AWAITABLE_ARG&& awaitable)
noexcept(
std::is_nothrow_constructible_v<FUNC, FUNC_ARG&&> &&
std::is_nothrow_constructible_v<AWAITABLE, AWAITABLE_ARG&&>)
: m_func(static_cast<FUNC_ARG&&>(func))
, m_awaitable(static_cast<AWAITABLE_ARG&&>(awaitable))
{}
auto operator co_await() const &
{
return fmap_awaiter<const FUNC&, const AWAITABLE&>(m_func, m_awaitable);
}
auto operator co_await() &
{
return fmap_awaiter<FUNC&, AWAITABLE&>(m_func, m_awaitable);
}
auto operator co_await() &&
{
return fmap_awaiter<FUNC&&, AWAITABLE&&>(
static_cast<FUNC&&>(m_func),
static_cast<AWAITABLE&&>(m_awaitable));
}
private:
FUNC m_func;
AWAITABLE m_awaitable;
};
}
template<typename FUNC>
struct fmap_transform
{
explicit fmap_transform(FUNC&& f)
noexcept(std::is_nothrow_move_constructible_v<FUNC>)
: func(std::forward<FUNC>(f))
{}
FUNC func;
};
template<
typename FUNC,
typename AWAITABLE,
std::enable_if_t<cppcoro::is_awaitable_v<AWAITABLE>, int> = 0>
auto fmap(FUNC&& func, AWAITABLE&& awaitable)
{
return detail::fmap_awaitable<
std::remove_cv_t<std::remove_reference_t<FUNC>>,
std::remove_cv_t<std::remove_reference_t<AWAITABLE>>>(
std::forward<FUNC>(func),
std::forward<AWAITABLE>(awaitable));
}
template<typename FUNC>
auto fmap(FUNC&& func)
{
return fmap_transform<FUNC>{ std::forward<FUNC>(func) };
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, fmap_transform<FUNC>&& transform)
{
// Use ADL for finding fmap() overload.
return fmap(std::forward<FUNC>(transform.func), std::forward<T>(value));
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, const fmap_transform<FUNC>& transform)
{
// Use ADL for finding fmap() overload.
return fmap(transform.func, std::forward<T>(value));
}
template<typename T, typename FUNC>
decltype(auto) operator|(T&& value, fmap_transform<FUNC>& transform)
{
// Use ADL for finding fmap() overload.
return fmap(transform.func, std::forward<T>(value));
}
}
#endif
+260
View File
@@ -0,0 +1,260 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_GENERATOR_HPP_INCLUDED
#define CPPCORO_GENERATOR_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <utility>
#include <exception>
#include <iterator>
#include <functional>
namespace cppcoro
{
template<typename T>
class generator;
namespace detail
{
template<typename T>
class generator_promise
{
public:
using value_type = std::remove_reference_t<T>;
using reference_type = std::conditional_t<std::is_reference_v<T>, T, T&>;
using pointer_type = value_type*;
generator_promise() = default;
generator<T> get_return_object() noexcept;
constexpr cppcoro::suspend_always initial_suspend() const noexcept { return {}; }
constexpr cppcoro::suspend_always final_suspend() const noexcept { return {}; }
template<
typename U = T,
std::enable_if_t<!std::is_rvalue_reference<U>::value, int> = 0>
cppcoro::suspend_always yield_value(std::remove_reference_t<T>& value) noexcept
{
m_value = std::addressof(value);
return {};
}
cppcoro::suspend_always yield_value(std::remove_reference_t<T>&& value) noexcept
{
m_value = std::addressof(value);
return {};
}
void unhandled_exception()
{
m_exception = std::current_exception();
}
void return_void()
{
}
reference_type value() const noexcept
{
return static_cast<reference_type>(*m_value);
}
// Don't allow any use of 'co_await' inside the generator coroutine.
template<typename U>
cppcoro::suspend_never await_transform(U&& value) = delete;
void rethrow_if_exception()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
pointer_type m_value;
std::exception_ptr m_exception;
};
struct generator_sentinel {};
template<typename T>
class generator_iterator
{
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
public:
using iterator_category = std::input_iterator_tag;
// What type should we use for counting elements of a potentially infinite sequence?
using difference_type = std::ptrdiff_t;
using value_type = typename generator_promise<T>::value_type;
using reference = typename generator_promise<T>::reference_type;
using pointer = typename generator_promise<T>::pointer_type;
// Iterator needs to be default-constructible to satisfy the Range concept.
generator_iterator() noexcept
: m_coroutine(nullptr)
{}
explicit generator_iterator(coroutine_handle coroutine) noexcept
: m_coroutine(coroutine)
{}
friend bool operator==(const generator_iterator& it, generator_sentinel) noexcept
{
return !it.m_coroutine || it.m_coroutine.done();
}
friend bool operator!=(const generator_iterator& it, generator_sentinel s) noexcept
{
return !(it == s);
}
friend bool operator==(generator_sentinel s, const generator_iterator& it) noexcept
{
return (it == s);
}
friend bool operator!=(generator_sentinel s, const generator_iterator& it) noexcept
{
return it != s;
}
generator_iterator& operator++()
{
m_coroutine.resume();
if (m_coroutine.done())
{
m_coroutine.promise().rethrow_if_exception();
}
return *this;
}
// Need to provide post-increment operator to implement the 'Range' concept.
void operator++(int)
{
(void)operator++();
}
reference operator*() const noexcept
{
return m_coroutine.promise().value();
}
pointer operator->() const noexcept
{
return std::addressof(operator*());
}
private:
coroutine_handle m_coroutine;
};
}
template<typename T>
class [[nodiscard]] generator
{
public:
using promise_type = detail::generator_promise<T>;
using iterator = detail::generator_iterator<T>;
generator() noexcept
: m_coroutine(nullptr)
{}
generator(generator&& other) noexcept
: m_coroutine(other.m_coroutine)
{
other.m_coroutine = nullptr;
}
generator(const generator& other) = delete;
~generator()
{
if (m_coroutine)
{
m_coroutine.destroy();
}
}
generator& operator=(generator other) noexcept
{
swap(other);
return *this;
}
iterator begin()
{
if (m_coroutine)
{
m_coroutine.resume();
if (m_coroutine.done())
{
m_coroutine.promise().rethrow_if_exception();
}
}
return iterator{ m_coroutine };
}
detail::generator_sentinel end() noexcept
{
return detail::generator_sentinel{};
}
void swap(generator& other) noexcept
{
std::swap(m_coroutine, other.m_coroutine);
}
private:
friend class detail::generator_promise<T>;
explicit generator(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
template<typename T>
void swap(generator<T>& a, generator<T>& b)
{
a.swap(b);
}
namespace detail
{
template<typename T>
generator<T> generator_promise<T>::get_return_object() noexcept
{
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
return generator<T>{ coroutine_handle::from_promise(*this) };
}
}
template<typename FUNC, typename T>
generator<std::invoke_result_t<FUNC&, typename generator<T>::iterator::reference>> fmap(FUNC func, generator<T> source)
{
for (auto&& value : source)
{
co_yield std::invoke(func, static_cast<decltype(value)>(value));
}
}
}
#endif
+25
View File
@@ -0,0 +1,25 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
#define CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class inline_scheduler
{
public:
inline_scheduler() noexcept = default;
cppcoro::suspend_never schedule() const noexcept
{
return {};
}
};
}
#endif
+321
View File
@@ -0,0 +1,321 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_IO_SERVICE_HPP_INCLUDED
#define CPPCORO_IO_SERVICE_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/cancellation_registration.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
#include <optional>
#include <chrono>
#include <cstdint>
#include <atomic>
#include <utility>
#include <mutex>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class io_service
{
public:
class schedule_operation;
class timed_schedule_operation;
/// Initialises the io_service.
///
/// Does not set a concurrency hint. All threads that enter the
/// event loop will actively process events.
io_service();
/// Initialise the io_service with a concurrency hint.
///
/// \param concurrencyHint
/// Specifies the target maximum number of I/O threads to be
/// actively processing events.
/// Note that the number of active threads may temporarily go
/// above this number.
io_service(std::uint32_t concurrencyHint);
~io_service();
io_service(io_service&& other) = delete;
io_service(const io_service& other) = delete;
io_service& operator=(io_service&& other) = delete;
io_service& operator=(const io_service& other) = delete;
/// Returns an operation that when awaited suspends the awaiting
/// coroutine and reschedules it for resumption on an I/O thread
/// associated with this io_service.
[[nodiscard]]
schedule_operation schedule() noexcept;
/// Returns an operation that when awaited will suspend the
/// awaiting coroutine for the specified delay. Once the delay
/// has elapsed, the coroutine will resume execution on an
/// I/O thread associated with this io_service.
///
/// \param delay
/// The amount of time to delay scheduling resumption of the coroutine
/// on an I/O thread. There is no guarantee that the coroutine will
/// be resumed exactly after this delay.
///
/// \param cancellationToken [optional]
/// A cancellation token that can be used to communicate a request to
/// cancel the delayed schedule operation and schedule it for resumption
/// immediately.
/// The co_await operation will throw cppcoro::operation_cancelled if
/// cancellation was requested before the coroutine could be resumed.
template<typename REP, typename PERIOD>
[[nodiscard]]
timed_schedule_operation schedule_after(
const std::chrono::duration<REP, PERIOD>& delay,
cancellation_token cancellationToken = {}) noexcept;
/// Process events until the io_service is stopped.
///
/// \return
/// The number of events processed during this call.
std::uint64_t process_events();
/// Process events until either the io_service is stopped or
/// there are no more pending events in the queue.
///
/// \return
/// The number of events processed during this call.
std::uint64_t process_pending_events();
/// Block until either one event is processed or the io_service is stopped.
///
/// \return
/// The number of events processed during this call.
/// This will either be 0 or 1.
std::uint64_t process_one_event();
/// Process one event if there are any events pending, otherwise if there
/// are no events pending or the io_service is stopped then return immediately.
///
/// \return
/// The number of events processed during this call.
/// This will either be 0 or 1.
std::uint64_t process_one_pending_event();
/// Shut down the io_service.
///
/// This will cause any threads currently in a call to one of the process_xxx() methods
/// to return from that call once they finish processing the current event.
///
/// This call does not wait until all threads have exited the event loop so you
/// must use other synchronisation mechanisms to wait for those threads.
void stop() noexcept;
/// Reset an io_service to prepare it for resuming processing of events.
///
/// Call this after a call to stop() to allow calls to process_xxx() methods
/// to process events.
///
/// After calling stop() you should ensure that all threads have returned from
/// calls to process_xxx() methods before calling reset().
void reset();
bool is_stop_requested() const noexcept;
void notify_work_started() noexcept;
void notify_work_finished() noexcept;
#if CPPCORO_OS_WINNT
detail::win32::handle_t native_iocp_handle() noexcept;
void ensure_winsock_initialised();
#endif
private:
class timer_thread_state;
class timer_queue;
friend class schedule_operation;
friend class timed_schedule_operation;
void schedule_impl(schedule_operation* operation) noexcept;
void try_reschedule_overflow_operations() noexcept;
bool try_enter_event_loop() noexcept;
void exit_event_loop() noexcept;
bool try_process_one_event(bool waitForEvent);
void post_wake_up_event() noexcept;
timer_thread_state* ensure_timer_thread_started();
static constexpr std::uint32_t stop_requested_flag = 1;
static constexpr std::uint32_t active_thread_count_increment = 2;
// Bit 0: stop_requested_flag
// Bit 1-31: count of active threads currently running the event loop
std::atomic<std::uint32_t> m_threadState;
std::atomic<std::uint32_t> m_workCount;
#if CPPCORO_OS_WINNT
detail::win32::safe_handle m_iocpHandle;
std::atomic<bool> m_winsockInitialised;
std::mutex m_winsockInitialisationMutex;
#endif
// Head of a linked-list of schedule operations that are
// ready to run but that failed to be queued to the I/O
// completion port (eg. due to low memory).
std::atomic<schedule_operation*> m_scheduleOperations;
std::atomic<timer_thread_state*> m_timerState;
};
class io_service::schedule_operation
{
public:
schedule_operation(io_service& service) noexcept
: m_service(service)
{}
bool await_ready() const noexcept { return false; }
void await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
private:
friend class io_service;
friend class io_service::timed_schedule_operation;
io_service& m_service;
cppcoro::coroutine_handle<> m_awaiter;
schedule_operation* m_next;
};
class io_service::timed_schedule_operation
{
public:
timed_schedule_operation(
io_service& service,
std::chrono::high_resolution_clock::time_point resumeTime,
cppcoro::cancellation_token cancellationToken) noexcept;
timed_schedule_operation(timed_schedule_operation&& other) noexcept;
~timed_schedule_operation();
timed_schedule_operation& operator=(timed_schedule_operation&& other) = delete;
timed_schedule_operation(const timed_schedule_operation& other) = delete;
timed_schedule_operation& operator=(const timed_schedule_operation& other) = delete;
bool await_ready() const noexcept;
void await_suspend(cppcoro::coroutine_handle<> awaiter);
void await_resume();
private:
friend class io_service::timer_queue;
friend class io_service::timer_thread_state;
io_service::schedule_operation m_scheduleOperation;
std::chrono::high_resolution_clock::time_point m_resumeTime;
cppcoro::cancellation_token m_cancellationToken;
std::optional<cppcoro::cancellation_registration> m_cancellationRegistration;
timed_schedule_operation* m_next;
std::atomic<std::uint32_t> m_refCount;
};
class io_work_scope
{
public:
explicit io_work_scope(io_service& service) noexcept
: m_service(&service)
{
service.notify_work_started();
}
io_work_scope(const io_work_scope& other) noexcept
: m_service(other.m_service)
{
if (m_service != nullptr)
{
m_service->notify_work_started();
}
}
io_work_scope(io_work_scope&& other) noexcept
: m_service(other.m_service)
{
other.m_service = nullptr;
}
~io_work_scope()
{
if (m_service != nullptr)
{
m_service->notify_work_finished();
}
}
void swap(io_work_scope& other) noexcept
{
std::swap(m_service, other.m_service);
}
io_work_scope& operator=(io_work_scope other) noexcept
{
swap(other);
return *this;
}
io_service& service() noexcept
{
return *m_service;
}
private:
io_service* m_service;
};
inline void swap(io_work_scope& a, io_work_scope& b)
{
a.swap(b);
}
}
template<typename REP, typename RATIO>
cppcoro::io_service::timed_schedule_operation
cppcoro::io_service::schedule_after(
const std::chrono::duration<REP, RATIO>& duration,
cppcoro::cancellation_token cancellationToken) noexcept
{
return timed_schedule_operation{
*this,
std::chrono::high_resolution_clock::now() + duration,
std::move(cancellationToken)
};
}
#endif
+26
View File
@@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_IS_AWAITABLE_HPP_INCLUDED
#define CPPCORO_IS_AWAITABLE_HPP_INCLUDED
#include <cppcoro/detail/get_awaiter.hpp>
#include <type_traits>
namespace cppcoro
{
template<typename T, typename = std::void_t<>>
struct is_awaitable : std::false_type {};
template<typename T>
struct is_awaitable<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
: std::true_type
{};
template<typename T>
constexpr bool is_awaitable_v = is_awaitable<T>::value;
}
#endif
@@ -0,0 +1,829 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
#define CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/sequence_barrier.hpp>
#include <cppcoro/sequence_range.hpp>
#include <cppcoro/sequence_traits.hpp>
#include <cppcoro/detail/manual_lifetime.hpp>
#include <atomic>
#include <cstdint>
#include <cassert>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_operation;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_operation;
template<typename SEQUENCE, typename TRAITS>
class multi_producer_sequencer_wait_operation_base;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_wait_operation;
/// A multi-producer sequencer is a thread-synchronisation primitive that can be
/// used to synchronise access to a ring-buffer of power-of-two size where you
/// have multiple producers concurrently claiming slots in the ring-buffer and
/// publishing items.
///
/// When a writer wants to write to a slot in the buffer it first atomically
/// increments a counter by the number of slots it wishes to allocate.
/// It then waits until all of those slots have become available and then
/// returns the range of sequence numbers allocated back to the caller.
/// The caller then writes to those slots and when done publishes them by
/// writing the sequence numbers published to each of the slots to the
/// corresponding element of an array of equal size to the ring buffer.
/// When a reader wants to check if the next sequence number is available
/// it then simply needs to read from the corresponding slot in this array
/// to check if the value stored there is equal to the sequence number it
/// is wanting to read.
///
/// This means concurrent writers are wait-free when there is space available
/// in the ring buffer, requiring a single atomic fetch-add operation as the
/// only contended write operation. All other writes are to memory locations
/// owned by a particular writer. Concurrent writers can publish items out of
/// order so that one writer does not hold up other writers until the ring
/// buffer fills up.
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class multi_producer_sequencer
{
public:
multi_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence = TRAITS::initial_sequence);
/// The size of the circular buffer. This will be a power-of-two.
std::size_t buffer_size() const noexcept { return m_sequenceMask + 1; }
/// Lookup the last-known-published sequence number after the specified
/// sequence number.
SEQUENCE last_published_after(SEQUENCE lastKnownPublished) const noexcept;
/// Wait until the specified target sequence number has been published.
///
/// Returns an awaitable type that when co_awaited will suspend the awaiting
/// coroutine until the specified 'targetSequence' number and all prior sequence
/// numbers have been published.
template<typename SCHEDULER>
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) const noexcept;
/// Query if there are currently any slots available for claiming.
///
/// Note that this return-value is only approximate if you have multiple producers
/// since immediately after returning true another thread may have claimed the
/// last available slot.
bool any_available() const noexcept;
/// Claim a single slot in the buffer and wait until that slot becomes available.
///
/// Returns an Awaitable type that yields the sequence number of the slot that
/// was claimed.
///
/// Once the producer has claimed a slot then they are free to write to that
/// slot within the ring buffer. Once the value has been initialised the item
/// must be published by calling the .publish() method, passing the sequence
/// number.
template<typename SCHEDULER>
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_one(SCHEDULER& scheduler) noexcept;
/// Claim a contiguous range of sequence numbers corresponding to slots within
/// a ring-buffer.
///
/// This will claim at most the specified count of sequence numbers but may claim
/// fewer if there are only fewer entries available in the buffer. But will claim
/// at least one sequence number.
///
/// Returns an awaitable that will yield a sequence_range object containing the
/// sequence numbers that were claimed.
///
/// The caller is responsible for ensuring that they publish every element of the
/// returned sequence range by calling .publish().
template<typename SCHEDULER>
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept;
/// Publish the element with the specified sequence number, making it available
/// to consumers.
///
/// Note that different sequence numbers may be published by different producer
/// threads out of order. A sequence number will not become available to consumers
/// until all preceding sequence numbers have also been published.
///
/// \param sequence
/// The sequence number of the elemnt to publish
/// This sequence number must have been previously acquired via a call to 'claim_one()'
/// or 'claim_up_to()'.
void publish(SEQUENCE sequence) noexcept;
/// Publish a contiguous range of sequence numbers, making each of them available
/// to consumers.
///
/// This is equivalent to calling publish(seq) for each sequence number, seq, in
/// the specified range, but is more efficient since it only checks to see if
/// there are coroutines that need to be woken up once.
void publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept;
private:
template<typename SEQUENCE2, typename TRAITS2>
friend class multi_producer_sequencer_wait_operation_base;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class multi_producer_sequencer_claim_operation;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class multi_producer_sequencer_claim_one_operation;
void resume_ready_awaiters() noexcept;
void add_awaiter(multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
const std::size_t m_sequenceMask;
const std::unique_ptr<std::atomic<SEQUENCE>[]> m_published;
alignas(CPPCORO_CPU_CACHE_LINE)
std::atomic<SEQUENCE> m_nextToClaim;
alignas(CPPCORO_CPU_CACHE_LINE)
mutable std::atomic<multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>*> m_awaiters;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_awaiter
{
public:
multi_producer_sequencer_claim_awaiter(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
const sequence_range<SEQUENCE, TRAITS>& claimedRange,
SCHEDULER& scheduler) noexcept
: m_barrierWait(consumerBarrier, claimedRange.back() - bufferSize, scheduler)
, m_claimedRange(claimedRange)
{}
bool await_ready() const noexcept
{
return m_barrierWait.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_barrierWait.await_suspend(awaitingCoroutine);
}
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
{
return m_claimedRange;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_barrierWait;
sequence_range<SEQUENCE, TRAITS> m_claimedRange;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_operation
{
public:
multi_producer_sequencer_claim_operation(
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
std::size_t count,
SCHEDULER& scheduler) noexcept
: m_sequencer(sequencer)
, m_count(count < sequencer.buffer_size() ? count : sequencer.buffer_size())
, m_scheduler(scheduler)
{
}
multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
{
// We wait until the awaitable is actually co_await'ed before we claim the
// range of elements. If we claimed them earlier, then it may be possible for
// the caller to fail to co_await the result eg. due to an exception, which
// would leave the sequence numbers unable to be published and would eventually
// deadlock consumers that waited on them.
//
// TODO: We could try and acquire only as many as are available if fewer than
// m_count elements are available. This would complicate the logic here somewhat
// as we'd need to use a compare-exchange instead.
const SEQUENCE first = m_sequencer.m_nextToClaim.fetch_add(m_count, std::memory_order_relaxed);
return multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
m_sequencer.m_consumerBarrier,
m_sequencer.buffer_size(),
sequence_range<SEQUENCE, TRAITS>{ first, first + m_count },
m_scheduler
};
}
private:
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
std::size_t m_count;
SCHEDULER& m_scheduler;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_awaiter
{
public:
multi_producer_sequencer_claim_one_awaiter(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE claimedSequence,
SCHEDULER& scheduler) noexcept
: m_waitOp(consumerBarrier, claimedSequence - bufferSize, scheduler)
, m_claimedSequence(claimedSequence)
{}
bool await_ready() const noexcept
{
return m_waitOp.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_waitOp.await_suspend(awaitingCoroutine);
}
SEQUENCE await_resume() noexcept
{
return m_claimedSequence;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_waitOp;
SEQUENCE m_claimedSequence;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_claim_one_operation
{
public:
multi_producer_sequencer_claim_one_operation(
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SCHEDULER& scheduler) noexcept
: m_sequencer(sequencer)
, m_scheduler(scheduler)
{}
multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
{
return multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
m_sequencer.m_consumerBarrier,
m_sequencer.buffer_size(),
m_sequencer.m_nextToClaim.fetch_add(1, std::memory_order_relaxed),
m_scheduler
};
}
private:
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
SCHEDULER& m_scheduler;
};
template<typename SEQUENCE, typename TRAITS>
class multi_producer_sequencer_wait_operation_base
{
public:
multi_producer_sequencer_wait_operation_base(
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished) noexcept
: m_sequencer(sequencer)
, m_targetSequence(targetSequence)
, m_lastKnownPublished(lastKnownPublished)
, m_readyToResume(false)
{}
multi_producer_sequencer_wait_operation_base(
const multi_producer_sequencer_wait_operation_base& other) noexcept
: m_sequencer(other.m_sequencer)
, m_targetSequence(other.m_targetSequence)
, m_lastKnownPublished(other.m_lastKnownPublished)
, m_readyToResume(false)
{}
bool await_ready() const noexcept
{
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
m_sequencer.add_awaiter(this);
// Mark the waiter as ready to resume.
// If it was already marked as ready-to-resume within the call to add_awaiter() or
// on another thread then this exchange() will return true. In this case we want to
// resume immediately and continue execution by returning false.
return !m_readyToResume.exchange(true, std::memory_order_acquire);
}
SEQUENCE await_resume() noexcept
{
return m_lastKnownPublished;
}
protected:
friend class multi_producer_sequencer<SEQUENCE, TRAITS>;
void resume(SEQUENCE lastKnownPublished) noexcept
{
m_lastKnownPublished = lastKnownPublished;
if (m_readyToResume.exchange(true, std::memory_order_release))
{
resume_impl();
}
}
virtual void resume_impl() noexcept = 0;
const multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
SEQUENCE m_targetSequence;
SEQUENCE m_lastKnownPublished;
multi_producer_sequencer_wait_operation_base* m_next;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
std::atomic<bool> m_readyToResume;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class multi_producer_sequencer_wait_operation :
public multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>
{
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
public:
multi_producer_sequencer_wait_operation(
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) noexcept
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(sequencer, targetSequence, lastKnownPublished)
, m_scheduler(scheduler)
{}
multi_producer_sequencer_wait_operation(
const multi_producer_sequencer_wait_operation& other) noexcept
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(other)
, m_scheduler(other.m_scheduler)
{}
~multi_producer_sequencer_wait_operation()
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter.destruct();
}
if (m_isScheduleOperationCreated)
{
m_scheduleOperation.destruct();
}
}
SEQUENCE await_resume() noexcept(noexcept(m_scheduleOperation->await_resume()))
{
if (m_isScheduleOperationCreated)
{
m_scheduleOperation->await_resume();
}
return multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
}
private:
void resume_impl() noexcept override
{
try
{
m_scheduleOperation.construct(m_scheduler.schedule());
m_isScheduleOperationCreated = true;
m_scheduleAwaiter.construct(detail::get_awaiter(
static_cast<schedule_operation&&>(*m_scheduleOperation)));
m_isScheduleAwaiterCreated = true;
if (!m_scheduleAwaiter->await_ready())
{
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
if constexpr (std::is_void_v<await_suspend_result_t>)
{
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
return;
}
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
{
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
{
return;
}
}
else
{
// Assume it returns a coroutine_handle.
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
return;
}
}
}
catch (...)
{
// Ignore failure to reschedule and resume inline?
// Should we catch the exception and rethrow from await_resume()?
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
}
// Resume outside the catch-block.
this->m_awaitingCoroutine.resume();
}
SCHEDULER& m_scheduler;
// Can't use std::optional<T> here since T could be a reference.
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
bool m_isScheduleOperationCreated = false;
bool m_isScheduleAwaiterCreated = false;
};
template<typename SEQUENCE, typename TRAITS>
multi_producer_sequencer<SEQUENCE, TRAITS>::multi_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence)
: m_consumerBarrier(consumerBarrier)
, m_sequenceMask(bufferSize - 1)
, m_published(std::make_unique<std::atomic<SEQUENCE>[]>(bufferSize))
, m_nextToClaim(initialSequence + 1)
, m_awaiters(nullptr)
{
// bufferSize must be a positive power-of-two
assert(bufferSize > 0 && (bufferSize & (bufferSize - 1)) == 0);
// but must be no larger than the max diff value.
using diff_t = typename TRAITS::difference_type;
using unsigned_diff_t = std::make_unsigned_t<diff_t>;
constexpr unsigned_diff_t maxSize = static_cast<unsigned_diff_t>(std::numeric_limits<diff_t>::max());
assert(bufferSize <= maxSize);
SEQUENCE seq = initialSequence - (bufferSize - 1);
do
{
#ifdef __cpp_lib_atomic_value_initialization
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
#else // ^^^ __cpp_lib_atomic_value_initialization // !__cpp_lib_atomic_value_initialization vvv
std::atomic_init(&m_published[seq & m_sequenceMask], seq);
#endif // !__cpp_lib_atomic_value_initialization
} while (seq++ != initialSequence);
}
template<typename SEQUENCE, typename TRAITS>
SEQUENCE multi_producer_sequencer<SEQUENCE, TRAITS>::last_published_after(
SEQUENCE lastKnownPublished) const noexcept
{
const auto mask = m_sequenceMask;
SEQUENCE seq = lastKnownPublished + 1;
while (m_published[seq & mask].load(std::memory_order_acquire) == seq)
{
lastKnownPublished = seq++;
}
return lastKnownPublished;
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::wait_until_published(
SEQUENCE targetSequence,
SEQUENCE lastKnownPublished,
SCHEDULER& scheduler) const noexcept
{
return multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>{
*this, targetSequence, lastKnownPublished, scheduler
};
}
template<typename SEQUENCE, typename TRAITS>
bool multi_producer_sequencer<SEQUENCE, TRAITS>::any_available() const noexcept
{
return TRAITS::precedes(
m_nextToClaim.load(std::memory_order_relaxed),
m_consumerBarrier.last_published() + buffer_size());
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
{
return multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
{
return multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, count, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
{
m_published[sequence & m_sequenceMask].store(sequence, std::memory_order_seq_cst);
// Resume any waiters that might have been satisfied by this publish operation.
resume_ready_awaiters();
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept
{
if (range.empty())
{
return;
}
// Publish all but the first sequence number using relaxed atomics.
// No consumer should be reading those subsequent sequence numbers until they've seen
// that the first sequence number in the range is published.
for (SEQUENCE seq : range.skip(1))
{
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
}
// Now publish the first sequence number with seq_cst semantics.
m_published[range.front() & m_sequenceMask].store(range.front(), std::memory_order_seq_cst);
// Resume any waiters that might have been satisfied by this publish operation.
resume_ready_awaiters();
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::resume_ready_awaiters() noexcept
{
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
awaiter_t* awaiters = m_awaiters.load(std::memory_order_seq_cst);
if (awaiters == nullptr)
{
// No awaiters
return;
}
// There were some awaiters. Try to acquire the list of waiters with an
// atomic exchange as we might be racing with other consumers/producers.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_seq_cst);
if (awaiters == nullptr)
{
// Didn't acquire the list
// Some other thread is now responsible for resuming them. Our job is done.
return;
}
SEQUENCE lastKnownPublished;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
awaiter_t* awaitersToRequeue;
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
do
{
using diff_t = typename TRAITS::difference_type;
lastKnownPublished = last_published_after(awaiters->m_lastKnownPublished);
// First scan the list of awaiters and split them into 'requeue' and 'resume' lists.
auto minDiff = std::numeric_limits<diff_t>::max();
do
{
auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
if (diff > 0)
{
// Not ready yet.
minDiff = diff < minDiff ? diff : minDiff;
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
}
else
{
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters->m_lastKnownPublished = lastKnownPublished;
awaiters = awaiters->m_next;
} while (awaiters != nullptr);
// Null-terinate the requeue list
*awaitersToRequeueTail = nullptr;
if (awaitersToRequeue != nullptr)
{
// Requeue the waiters that are not ready yet.
awaiter_t* oldHead = nullptr;
while (!m_awaiters.compare_exchange_weak(oldHead, awaitersToRequeue, std::memory_order_seq_cst, std::memory_order_relaxed))
{
*awaitersToRequeueTail = oldHead;
}
// Reset the awaitersToRequeue list
awaitersToRequeueTail = &awaitersToRequeue;
const SEQUENCE earliestTargetSequence = lastKnownPublished + minDiff;
// Now we need to check again to see if any of the waiters we just enqueued
// is now satisfied by a concurrent call to publish().
//
// We need to be a bit more careful here since we are no longer holding any
// awaiters and so producers/consumers may advance the sequence number arbitrarily
// far. If the sequence number advances more than buffer_size() ahead of the
// earliestTargetSequence then the m_published[] array may have sequence numbers
// that have advanced beyond earliestTargetSequence, potentially even wrapping
// sequence numbers around to then be preceding where they were before. If this
// happens then we don't need to worry about resuming any awaiters that were waiting
// for 'earliestTargetSequence' since some other thread has already resumed them.
// So the only case we need to worry about here is when all m_published entries for
// sequence numbers in range [lastKnownPublished + 1, earliestTargetSequence] have
// published sequence numbers that match the range.
const auto sequenceMask = m_sequenceMask;
SEQUENCE seq = lastKnownPublished + 1;
while (m_published[seq & sequenceMask].load(std::memory_order_seq_cst) == seq)
{
lastKnownPublished = seq;
if (seq == earliestTargetSequence)
{
// At least one of the awaiters we just published is now satisfied.
// Reacquire the list of awaiters and continue around the outer loop.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
break;
}
++seq;
}
}
} while (awaiters != nullptr);
// Null-terminate list of awaiters to resume.
*awaitersToResumeTail = nullptr;
while (awaitersToResume != nullptr)
{
awaiter_t* next = awaitersToResume->m_next;
awaitersToResume->resume(lastKnownPublished);
awaitersToResume = next;
}
}
template<typename SEQUENCE, typename TRAITS>
void multi_producer_sequencer<SEQUENCE, TRAITS>::add_awaiter(
multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept
{
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
SEQUENCE targetSequence = awaiter->m_targetSequence;
SEQUENCE lastKnownPublished = awaiter->m_lastKnownPublished;
awaiter_t* awaitersToEnqueue = awaiter;
awaiter_t** awaitersToEnqueueTail = &awaiter->m_next;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
const SEQUENCE sequenceMask = m_sequenceMask;
do
{
// Enqueue the awaiters.
{
awaiter_t* oldHead = m_awaiters.load(std::memory_order_relaxed);
do
{
*awaitersToEnqueueTail = oldHead;
} while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToEnqueue,
std::memory_order_seq_cst,
std::memory_order_relaxed));
}
// Reset list of waiters
awaitersToEnqueueTail = &awaitersToEnqueue;
// Check to see if the last-known published sequence number has advanced
// while we were enqueuing the awaiters. Need to use seq_cst memory order
// here to ensure that if there are concurrent calls to publish() that would
// wake up any of the awaiters we just enqueued that either we will see their
// write to m_published slots or they will see our write to m_awaiters.
//
// Note also, that we are assuming that the last-known published sequence is
// not going to advance more than buffer_size() ahead of targetSequence since
// there is at least one consumer that won't be resumed and so thus can't
// publish the sequence number it's waiting for to its sequence_barrier and so
// producers won't be able to claim its slot in the buffer.
//
// TODO: Check whether we can weaken the memory order here to just use 'seq_cst' on the
// first .load() and then use 'acquire' on subsequent .load().
while (m_published[(lastKnownPublished + 1) & sequenceMask].load(std::memory_order_seq_cst) == (lastKnownPublished + 1))
{
++lastKnownPublished;
}
if (!TRAITS::precedes(lastKnownPublished, targetSequence))
{
// At least one awaiter we just enqueued has now been satisified.
// To ensure it is woken up we need to reacquire the list of awaiters and resume
awaiter_t* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
using diff_t = typename TRAITS::difference_type;
diff_t minDiff = std::numeric_limits<diff_t>::max();
while (awaiters != nullptr)
{
diff_t diff = TRAITS::difference(targetSequence, lastKnownPublished);
if (diff > 0)
{
// Not yet ready.
minDiff = diff < minDiff ? diff : minDiff;
*awaitersToEnqueueTail = awaiters;
awaitersToEnqueueTail = &awaiters->m_next;
awaiters->m_lastKnownPublished = lastKnownPublished;
}
else
{
// Now ready.
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
}
// Calculate the earliest sequence number that any awaiters in the
// awaitersToEnqueue list are waiting for. We'll use this next time
// around the loop.
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
}
// Null-terminate list of awaiters to enqueue.
*awaitersToEnqueueTail = nullptr;
} while (awaitersToEnqueue != nullptr);
// Null-terminate awaiters to resume.
*awaitersToResumeTail = nullptr;
// Finally, resume any awaiters we've found that are ready to go.
while (awaitersToResume != nullptr)
{
// Read m_next before calling .resume() as resuming could destroy the awaiter.
awaiter_t* next = awaitersToResume->m_next;
awaitersToResume->resume(lastKnownPublished);
awaitersToResume = next;
}
}
}
#endif
+147
View File
@@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
#include <cppcoro/net/ipv4_address.hpp>
#include <cppcoro/net/ipv6_address.hpp>
#include <cassert>
#include <optional>
#include <string>
namespace cppcoro
{
namespace net
{
class ip_address
{
public:
// Constructs to IPv4 address 0.0.0.0
ip_address() noexcept;
ip_address(ipv4_address address) noexcept;
ip_address(ipv6_address address) noexcept;
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
const ipv4_address& to_ipv4() const;
const ipv6_address& to_ipv6() const;
const std::uint8_t* bytes() const noexcept;
std::string to_string() const;
static std::optional<ip_address> from_string(std::string_view string) noexcept;
bool operator==(const ip_address& rhs) const noexcept;
bool operator!=(const ip_address& rhs) const noexcept;
// ipv4_address sorts less than ipv6_address
bool operator<(const ip_address& rhs) const noexcept;
bool operator>(const ip_address& rhs) const noexcept;
bool operator<=(const ip_address& rhs) const noexcept;
bool operator>=(const ip_address& rhs) const noexcept;
private:
enum class family
{
ipv4,
ipv6
};
family m_family;
union
{
ipv4_address m_ipv4;
ipv6_address m_ipv6;
};
};
inline ip_address::ip_address() noexcept
: m_family(family::ipv4)
, m_ipv4()
{}
inline ip_address::ip_address(ipv4_address address) noexcept
: m_family(family::ipv4)
, m_ipv4(address)
{}
inline ip_address::ip_address(ipv6_address address) noexcept
: m_family(family::ipv6)
, m_ipv6(address)
{
}
inline const ipv4_address& ip_address::to_ipv4() const
{
assert(is_ipv4());
return m_ipv4;
}
inline const ipv6_address& ip_address::to_ipv6() const
{
assert(is_ipv6());
return m_ipv6;
}
inline const std::uint8_t* ip_address::bytes() const noexcept
{
return is_ipv4() ? m_ipv4.bytes() : m_ipv6.bytes();
}
inline bool ip_address::operator==(const ip_address& rhs) const noexcept
{
if (is_ipv4())
{
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
}
}
inline bool ip_address::operator!=(const ip_address& rhs) const noexcept
{
return !(*this == rhs);
}
inline bool ip_address::operator<(const ip_address& rhs) const noexcept
{
if (is_ipv4())
{
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
}
}
inline bool ip_address::operator>(const ip_address& rhs) const noexcept
{
return rhs < *this;
}
inline bool ip_address::operator<=(const ip_address& rhs) const noexcept
{
return !(rhs < *this);
}
inline bool ip_address::operator>=(const ip_address& rhs) const noexcept
{
return !(*this < rhs);
}
}
}
#endif
+161
View File
@@ -0,0 +1,161 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ip_address.hpp>
#include <cppcoro/net/ipv4_endpoint.hpp>
#include <cppcoro/net/ipv6_endpoint.hpp>
#include <cassert>
#include <optional>
#include <string>
namespace cppcoro
{
namespace net
{
class ip_endpoint
{
public:
// Constructs to IPv4 end-point 0.0.0.0:0
ip_endpoint() noexcept;
ip_endpoint(ipv4_endpoint endpoint) noexcept;
ip_endpoint(ipv6_endpoint endpoint) noexcept;
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
const ipv4_endpoint& to_ipv4() const;
const ipv6_endpoint& to_ipv6() const;
ip_address address() const noexcept;
std::uint16_t port() const noexcept;
std::string to_string() const;
static std::optional<ip_endpoint> from_string(std::string_view string) noexcept;
bool operator==(const ip_endpoint& rhs) const noexcept;
bool operator!=(const ip_endpoint& rhs) const noexcept;
// ipv4_endpoint sorts less than ipv6_endpoint
bool operator<(const ip_endpoint& rhs) const noexcept;
bool operator>(const ip_endpoint& rhs) const noexcept;
bool operator<=(const ip_endpoint& rhs) const noexcept;
bool operator>=(const ip_endpoint& rhs) const noexcept;
private:
enum class family
{
ipv4,
ipv6
};
family m_family;
union
{
ipv4_endpoint m_ipv4;
ipv6_endpoint m_ipv6;
};
};
inline ip_endpoint::ip_endpoint() noexcept
: m_family(family::ipv4)
, m_ipv4()
{}
inline ip_endpoint::ip_endpoint(ipv4_endpoint endpoint) noexcept
: m_family(family::ipv4)
, m_ipv4(endpoint)
{}
inline ip_endpoint::ip_endpoint(ipv6_endpoint endpoint) noexcept
: m_family(family::ipv6)
, m_ipv6(endpoint)
{
}
inline const ipv4_endpoint& ip_endpoint::to_ipv4() const
{
assert(is_ipv4());
return m_ipv4;
}
inline const ipv6_endpoint& ip_endpoint::to_ipv6() const
{
assert(is_ipv6());
return m_ipv6;
}
inline ip_address ip_endpoint::address() const noexcept
{
if (is_ipv4())
{
return m_ipv4.address();
}
else
{
return m_ipv6.address();
}
}
inline std::uint16_t ip_endpoint::port() const noexcept
{
return is_ipv4() ? m_ipv4.port() : m_ipv6.port();
}
inline bool ip_endpoint::operator==(const ip_endpoint& rhs) const noexcept
{
if (is_ipv4())
{
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
}
}
inline bool ip_endpoint::operator!=(const ip_endpoint& rhs) const noexcept
{
return !(*this == rhs);
}
inline bool ip_endpoint::operator<(const ip_endpoint& rhs) const noexcept
{
if (is_ipv4())
{
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
}
else
{
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
}
}
inline bool ip_endpoint::operator>(const ip_endpoint& rhs) const noexcept
{
return rhs < *this;
}
inline bool ip_endpoint::operator<=(const ip_endpoint& rhs) const noexcept
{
return !(rhs < *this);
}
inline bool ip_endpoint::operator>=(const ip_endpoint& rhs) const noexcept
{
return !(*this < rhs);
}
}
}
#endif
+134
View File
@@ -0,0 +1,134 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro::net
{
class ipv4_address
{
using bytes_t = std::uint8_t[4];
public:
constexpr ipv4_address()
: m_bytes{ 0, 0, 0, 0 }
{}
explicit constexpr ipv4_address(std::uint32_t integer)
: m_bytes{
static_cast<std::uint8_t>(integer >> 24),
static_cast<std::uint8_t>(integer >> 16),
static_cast<std::uint8_t>(integer >> 8),
static_cast<std::uint8_t>(integer) }
{}
explicit constexpr ipv4_address(const std::uint8_t(&bytes)[4])
: m_bytes{ bytes[0], bytes[1], bytes[2], bytes[3] }
{}
explicit constexpr ipv4_address(
std::uint8_t b0,
std::uint8_t b1,
std::uint8_t b2,
std::uint8_t b3)
: m_bytes{ b0, b1, b2, b3 }
{}
constexpr const bytes_t& bytes() const { return m_bytes; }
constexpr std::uint32_t to_integer() const
{
return
std::uint32_t(m_bytes[0]) << 24 |
std::uint32_t(m_bytes[1]) << 16 |
std::uint32_t(m_bytes[2]) << 8 |
std::uint32_t(m_bytes[3]);
}
static constexpr ipv4_address loopback()
{
return ipv4_address(127, 0, 0, 1);
}
constexpr bool is_loopback() const
{
return m_bytes[0] == 127;
}
constexpr bool is_private_network() const
{
return m_bytes[0] == 10 ||
(m_bytes[0] == 172 && (m_bytes[1] & 0xF0) == 0x10) ||
(m_bytes[0] == 192 && m_bytes[2] == 168);
}
constexpr bool operator==(ipv4_address other) const
{
return
m_bytes[0] == other.m_bytes[0] &&
m_bytes[1] == other.m_bytes[1] &&
m_bytes[2] == other.m_bytes[2] &&
m_bytes[3] == other.m_bytes[3];
}
constexpr bool operator!=(ipv4_address other) const
{
return !(*this == other);
}
constexpr bool operator<(ipv4_address other) const
{
return to_integer() < other.to_integer();
}
constexpr bool operator>(ipv4_address other) const
{
return other < *this;
}
constexpr bool operator<=(ipv4_address other) const
{
return !(other < *this);
}
constexpr bool operator>=(ipv4_address other) const
{
return !(*this < other);
}
/// Parse a string representation of an IP address.
///
/// Parses strings of the form:
/// - "num.num.num.num" where num is an integer in range [0, 255].
/// - A single integer value in range [0, 2^32).
///
/// \param string
/// The string to parse.
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
///
/// \return
/// The IP address if successful, otherwise std::nullopt if the string
/// could not be parsed as an IPv4 address.
static std::optional<ipv4_address> from_string(std::string_view string) noexcept;
/// Convert the IP address to dotted decimal notation.
///
/// eg. "12.67.190.23"
std::string to_string() const;
private:
alignas(std::uint32_t) std::uint8_t m_bytes[4];
};
}
#endif
+82
View File
@@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ipv4_address.hpp>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro
{
namespace net
{
class ipv4_endpoint
{
public:
// Construct to 0.0.0.0:0
ipv4_endpoint() noexcept
: m_address()
, m_port(0)
{}
explicit ipv4_endpoint(ipv4_address address, std::uint16_t port = 0) noexcept
: m_address(address)
, m_port(port)
{}
const ipv4_address& address() const noexcept { return m_address; }
std::uint16_t port() const noexcept { return m_port; }
std::string to_string() const;
static std::optional<ipv4_endpoint> from_string(std::string_view string) noexcept;
private:
ipv4_address m_address;
std::uint16_t m_port;
};
inline bool operator==(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return a.address() == b.address() &&
a.port() == b.port();
}
inline bool operator!=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(a == b);
}
inline bool operator<(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return a.address() < b.address() ||
(a.address() == b.address() && a.port() < b.port());
}
inline bool operator>(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return b < a;
}
inline bool operator<=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(b < a);
}
inline bool operator>=(const ipv4_endpoint& a, const ipv4_endpoint& b)
{
return !(a < b);
}
}
}
#endif
+245
View File
@@ -0,0 +1,245 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro::net
{
class ipv4_address;
class ipv6_address
{
using bytes_t = std::uint8_t[16];
public:
constexpr ipv6_address();
explicit constexpr ipv6_address(
std::uint64_t subnetPrefix,
std::uint64_t interfaceIdentifier);
constexpr ipv6_address(
std::uint16_t part0,
std::uint16_t part1,
std::uint16_t part2,
std::uint16_t part3,
std::uint16_t part4,
std::uint16_t part5,
std::uint16_t part6,
std::uint16_t part7);
explicit constexpr ipv6_address(
const std::uint16_t(&parts)[8]);
explicit constexpr ipv6_address(
const std::uint8_t(&bytes)[16]);
constexpr const bytes_t& bytes() const { return m_bytes; }
constexpr std::uint64_t subnet_prefix() const;
constexpr std::uint64_t interface_identifier() const;
/// Get the IPv6 unspedified address :: (all zeroes).
static constexpr ipv6_address unspecified();
/// Get the IPv6 loopback address ::1.
static constexpr ipv6_address loopback();
/// Parse a string representation of an IPv6 address.
///
/// \param string
/// The string to parse.
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
///
/// \return
/// The IP address if successful, otherwise std::nullopt if the string
/// could not be parsed as an IPv4 address.
static std::optional<ipv6_address> from_string(std::string_view string) noexcept;
/// Convert the IP address to contracted string form.
///
/// Address is broken up into 16-bit parts, with each part represended in 1-4
/// lower-case hexadecimal with leading zeroes omitted. Parts are separated
/// by separated by a ':'. The longest contiguous run of zero parts is contracted
/// to "::".
///
/// For example:
/// ipv6_address::unspecified() -> "::"
/// ipv6_address::loopback() -> "::1"
/// ipv6_address(0x0011223344556677, 0x8899aabbccddeeff) ->
/// "11:2233:4455:6677:8899:aabb:ccdd:eeff"
/// ipv6_address(0x0102030400000000, 0x003fc447ab991011) ->
/// "102:304::3f:c447:ab99:1011"
std::string to_string() const;
constexpr bool operator==(const ipv6_address& other) const;
constexpr bool operator!=(const ipv6_address& other) const;
constexpr bool operator<(const ipv6_address& other) const;
constexpr bool operator>(const ipv6_address& other) const;
constexpr bool operator<=(const ipv6_address& other) const;
constexpr bool operator>=(const ipv6_address& other) const;
private:
alignas(std::uint64_t) std::uint8_t m_bytes[16];
};
constexpr ipv6_address::ipv6_address()
: m_bytes{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 }
{}
constexpr ipv6_address::ipv6_address(
std::uint64_t subnetPrefix,
std::uint64_t interfaceIdentifier)
: m_bytes{
static_cast<std::uint8_t>(subnetPrefix >> 56),
static_cast<std::uint8_t>(subnetPrefix >> 48),
static_cast<std::uint8_t>(subnetPrefix >> 40),
static_cast<std::uint8_t>(subnetPrefix >> 32),
static_cast<std::uint8_t>(subnetPrefix >> 24),
static_cast<std::uint8_t>(subnetPrefix >> 16),
static_cast<std::uint8_t>(subnetPrefix >> 8),
static_cast<std::uint8_t>(subnetPrefix),
static_cast<std::uint8_t>(interfaceIdentifier >> 56),
static_cast<std::uint8_t>(interfaceIdentifier >> 48),
static_cast<std::uint8_t>(interfaceIdentifier >> 40),
static_cast<std::uint8_t>(interfaceIdentifier >> 32),
static_cast<std::uint8_t>(interfaceIdentifier >> 24),
static_cast<std::uint8_t>(interfaceIdentifier >> 16),
static_cast<std::uint8_t>(interfaceIdentifier >> 8),
static_cast<std::uint8_t>(interfaceIdentifier) }
{}
constexpr ipv6_address::ipv6_address(
std::uint16_t part0,
std::uint16_t part1,
std::uint16_t part2,
std::uint16_t part3,
std::uint16_t part4,
std::uint16_t part5,
std::uint16_t part6,
std::uint16_t part7)
: m_bytes{
static_cast<std::uint8_t>(part0 >> 8),
static_cast<std::uint8_t>(part0),
static_cast<std::uint8_t>(part1 >> 8),
static_cast<std::uint8_t>(part1),
static_cast<std::uint8_t>(part2 >> 8),
static_cast<std::uint8_t>(part2),
static_cast<std::uint8_t>(part3 >> 8),
static_cast<std::uint8_t>(part3),
static_cast<std::uint8_t>(part4 >> 8),
static_cast<std::uint8_t>(part4),
static_cast<std::uint8_t>(part5 >> 8),
static_cast<std::uint8_t>(part5),
static_cast<std::uint8_t>(part6 >> 8),
static_cast<std::uint8_t>(part6),
static_cast<std::uint8_t>(part7 >> 8),
static_cast<std::uint8_t>(part7) }
{}
constexpr ipv6_address::ipv6_address(
const std::uint16_t(&parts)[8])
: ipv6_address(
parts[0], parts[1], parts[2], parts[3],
parts[4], parts[5], parts[6], parts[7])
{}
constexpr ipv6_address::ipv6_address(const std::uint8_t(&bytes)[16])
: m_bytes{
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11],
bytes[12], bytes[13], bytes[14], bytes[15] }
{}
constexpr std::uint64_t ipv6_address::subnet_prefix() const
{
return
static_cast<std::uint64_t>(m_bytes[0]) << 56 |
static_cast<std::uint64_t>(m_bytes[1]) << 48 |
static_cast<std::uint64_t>(m_bytes[2]) << 40 |
static_cast<std::uint64_t>(m_bytes[3]) << 32 |
static_cast<std::uint64_t>(m_bytes[4]) << 24 |
static_cast<std::uint64_t>(m_bytes[5]) << 16 |
static_cast<std::uint64_t>(m_bytes[6]) << 8 |
static_cast<std::uint64_t>(m_bytes[7]);
}
constexpr std::uint64_t ipv6_address::interface_identifier() const
{
return
static_cast<std::uint64_t>(m_bytes[8]) << 56 |
static_cast<std::uint64_t>(m_bytes[9]) << 48 |
static_cast<std::uint64_t>(m_bytes[10]) << 40 |
static_cast<std::uint64_t>(m_bytes[11]) << 32 |
static_cast<std::uint64_t>(m_bytes[12]) << 24 |
static_cast<std::uint64_t>(m_bytes[13]) << 16 |
static_cast<std::uint64_t>(m_bytes[14]) << 8 |
static_cast<std::uint64_t>(m_bytes[15]);
}
constexpr ipv6_address ipv6_address::unspecified()
{
return ipv6_address{};
}
constexpr ipv6_address ipv6_address::loopback()
{
return ipv6_address{ 0, 0, 0, 0, 0, 0, 0, 1 };
}
constexpr bool ipv6_address::operator==(const ipv6_address& other) const
{
for (int i = 0; i < 16; ++i)
{
if (m_bytes[i] != other.m_bytes[i]) return false;
}
return true;
}
constexpr bool ipv6_address::operator!=(const ipv6_address& other) const
{
return !(*this == other);
}
constexpr bool ipv6_address::operator<(const ipv6_address& other) const
{
for (int i = 0; i < 16; ++i)
{
if (m_bytes[i] != other.m_bytes[i])
return m_bytes[i] < other.m_bytes[i];
}
return false;
}
constexpr bool ipv6_address::operator>(const ipv6_address& other) const
{
return (other < *this);
}
constexpr bool ipv6_address::operator<=(const ipv6_address& other) const
{
return !(other < *this);
}
constexpr bool ipv6_address::operator>=(const ipv6_address& other) const
{
return !(*this < other);
}
}
#endif
+82
View File
@@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
#define CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
#include <cppcoro/net/ipv6_address.hpp>
#include <optional>
#include <string>
#include <string_view>
namespace cppcoro
{
namespace net
{
class ipv6_endpoint
{
public:
// Construct to [::]:0
ipv6_endpoint() noexcept
: m_address()
, m_port(0)
{}
explicit ipv6_endpoint(ipv6_address address, std::uint16_t port = 0) noexcept
: m_address(address)
, m_port(port)
{}
const ipv6_address& address() const noexcept { return m_address; }
std::uint16_t port() const noexcept { return m_port; }
std::string to_string() const;
static std::optional<ipv6_endpoint> from_string(std::string_view string) noexcept;
private:
ipv6_address m_address;
std::uint16_t m_port;
};
inline bool operator==(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return a.address() == b.address() &&
a.port() == b.port();
}
inline bool operator!=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(a == b);
}
inline bool operator<(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return a.address() < b.address() ||
(a.address() == b.address() && a.port() < b.port());
}
inline bool operator>(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return b < a;
}
inline bool operator<=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(b < a);
}
inline bool operator>=(const ipv6_endpoint& a, const ipv6_endpoint& b)
{
return !(a < b);
}
}
}
#endif
+268
View File
@@ -0,0 +1,268 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cppcoro/net/socket_accept_operation.hpp>
#include <cppcoro/net/socket_connect_operation.hpp>
#include <cppcoro/net/socket_disconnect_operation.hpp>
#include <cppcoro/net/socket_recv_operation.hpp>
#include <cppcoro/net/socket_recv_from_operation.hpp>
#include <cppcoro/net/socket_send_operation.hpp>
#include <cppcoro/net/socket_send_to_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#endif
namespace cppcoro
{
class io_service;
namespace net
{
class socket
{
public:
/// Create a socket that can be used to communicate using TCP/IPv4 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_tcpv4(io_service& ioSvc);
/// Create a socket that can be used to communicate using TCP/IPv6 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_tcpv6(io_service& ioSvc);
/// Create a socket that can be used to communicate using UDP/IPv4 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_udpv4(io_service& ioSvc);
/// Create a socket that can be used to communicate using UDP/IPv6 protocol.
///
/// \param ioSvc
/// The I/O service the socket will use for dispatching I/O completion events.
///
/// \return
/// The newly created socket.
///
/// \throws std::system_error
/// If the socket could not be created for some reason.
static socket create_udpv6(io_service& ioSvc);
socket(socket&& other) noexcept;
/// Closes the socket, releasing any associated resources.
///
/// If the socket still has an open connection then the connection will be
/// reset. The destructor will not block waiting for queueud data to be sent.
/// If you need to ensure that queued data is delivered then you must call
/// disconnect() and wait until the disconnect operation completes.
~socket();
socket& operator=(socket&& other) noexcept;
#if CPPCORO_OS_WINNT
/// Get the Win32 socket handle assocaited with this socket.
cppcoro::detail::win32::socket_t native_handle() noexcept { return m_handle; }
/// Query whether I/O operations that complete synchronously will skip posting
/// an I/O completion event to the I/O completion port.
///
/// The operation class implementations can use this to determine whether or not
/// it should immediately resume the coroutine on the current thread upon an
/// operation completing synchronously or whether it should suspend the coroutine
/// and wait until the I/O completion event is dispatched to an I/O thread.
bool skip_completion_on_success() noexcept { return m_skipCompletionOnSuccess; }
#endif
/// Get the address and port of the local end-point.
///
/// If the socket is not bound then this will be the unspecified end-point
/// of the socket's associated address-family.
const ip_endpoint& local_endpoint() const noexcept { return m_localEndPoint; }
/// Get the address and port of the remote end-point.
///
/// If the socket is not in the connected state then this will be the unspecified
/// end-point of the socket's associated address-family.
const ip_endpoint& remote_endpoint() const noexcept { return m_remoteEndPoint; }
/// Bind the local end of this socket to the specified local end-point.
///
/// \param localEndPoint
/// The end-point to bind to.
/// This can be either an unspecified address (in which case it binds to all available
/// interfaces) and/or an unspecified port (in which case a random port is allocated).
///
/// \throws std::system_error
/// If the socket could not be bound for some reason.
void bind(const ip_endpoint& localEndPoint);
/// Put the socket into a passive listening state that will start acknowledging
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
///
/// The backlog of connections ready to be accepted will be set to some default
/// suitable large value, depending on the network provider. If you need more
/// control over the size of the queue then use the overload of listen()
/// that accepts a 'backlog' parameter.
///
/// \throws std::system_error
/// If the socket could not be placed into a listening mode.
void listen();
/// Put the socket into a passive listening state that will start acknowledging
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
///
/// \param backlog
/// The maximum number of pending connections to allow in the queue of ready-to-accept
/// connections.
///
/// \throws std::system_error
/// If the socket could not be placed into a listening mode.
void listen(std::uint32_t backlog);
/// Connect the socket to the specified remote end-point.
///
/// The socket must be in a bound but unconnected state prior to this call.
///
/// \param remoteEndPoint
/// The IP address and port-number to connect to.
///
/// \return
/// An awaitable object that must be co_await'ed to perform the async connect
/// operation. The result of the co_await expression is type void.
[[nodiscard]]
socket_connect_operation connect(const ip_endpoint& remoteEndPoint) noexcept;
/// Connect to the specified remote end-point.
///
/// \param remoteEndPoint
/// The IP address and port of the remote end-point to connect to.
///
/// \param ct
/// A cancellation token that can be used to communicate a request to
/// later cancel the operation. If the operation is successfully
/// cancelled then it will complete by throwing a cppcoro::operation_cancelled
/// exception.
///
/// \return
/// An awaitable object that will start the connect operation when co_await'ed
/// and will suspend the coroutine, resuming it when the operation completes.
/// The result of the co_await expression has type 'void'.
[[nodiscard]]
socket_connect_operation_cancellable connect(
const ip_endpoint& remoteEndPoint,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_accept_operation accept(socket& acceptingSocket) noexcept;
[[nodiscard]]
socket_accept_operation_cancellable accept(
socket& acceptingSocket,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_disconnect_operation disconnect() noexcept;
[[nodiscard]]
socket_disconnect_operation_cancellable disconnect(cancellation_token ct) noexcept;
[[nodiscard]]
socket_send_operation send(
const void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_send_operation_cancellable send(
const void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_recv_operation recv(
void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_recv_operation_cancellable recv(
void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_recv_from_operation recv_from(
void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_recv_from_operation_cancellable recv_from(
void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
[[nodiscard]]
socket_send_to_operation send_to(
const ip_endpoint& destination,
const void* buffer,
std::size_t size) noexcept;
[[nodiscard]]
socket_send_to_operation_cancellable send_to(
const ip_endpoint& destination,
const void* buffer,
std::size_t size,
cancellation_token ct) noexcept;
void close_send();
void close_recv();
private:
friend class socket_accept_operation_impl;
friend class socket_connect_operation_impl;
#if CPPCORO_OS_WINNT
explicit socket(
cppcoro::detail::win32::socket_t handle,
bool skipCompletionOnSuccess) noexcept;
#endif
#if CPPCORO_OS_WINNT
cppcoro::detail::win32::socket_t m_handle;
bool m_skipCompletionOnSuccess;
#endif
ip_endpoint m_localEndPoint;
ip_endpoint m_remoteEndPoint;
};
}
}
#endif
@@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/cancellation_registration.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
# include <atomic>
# include <optional>
namespace cppcoro
{
namespace net
{
class socket;
class socket_accept_operation_impl
{
public:
socket_accept_operation_impl(
socket& listeningSocket,
socket& acceptingSocket) noexcept
: m_listeningSocket(listeningSocket)
, m_acceptingSocket(acceptingSocket)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // Structure padded due to alignment
#endif
socket& m_listeningSocket;
socket& m_acceptingSocket;
alignas(8) std::uint8_t m_addressBuffer[88];
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
class socket_accept_operation
: public cppcoro::detail::win32_overlapped_operation<socket_accept_operation>
{
public:
socket_accept_operation(
socket& listeningSocket,
socket& acceptingSocket) noexcept
: m_impl(listeningSocket, acceptingSocket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_accept_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void get_result() { m_impl.get_result(*this); }
socket_accept_operation_impl m_impl;
};
class socket_accept_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>
{
public:
socket_accept_operation_cancellable(
socket& listeningSocket,
socket& acceptingSocket,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>(std::move(ct))
, m_impl(listeningSocket, acceptingSocket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_accept_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,95 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
namespace net
{
class socket;
class socket_connect_operation_impl
{
public:
socket_connect_operation_impl(
socket& socket,
const ip_endpoint& remoteEndPoint) noexcept
: m_socket(socket)
, m_remoteEndPoint(remoteEndPoint)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
ip_endpoint m_remoteEndPoint;
};
class socket_connect_operation
: public cppcoro::detail::win32_overlapped_operation<socket_connect_operation>
{
public:
socket_connect_operation(
socket& socket,
const ip_endpoint& remoteEndPoint) noexcept
: m_impl(socket, remoteEndPoint)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_connect_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_connect_operation_impl m_impl;
};
class socket_connect_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>
{
public:
socket_connect_operation_cancellable(
socket& socket,
const ip_endpoint& remoteEndPoint,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>(std::move(ct))
, m_impl(socket, remoteEndPoint)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_connect_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,85 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro
{
namespace net
{
class socket;
class socket_disconnect_operation_impl
{
public:
socket_disconnect_operation_impl(socket& socket) noexcept
: m_socket(socket)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
};
class socket_disconnect_operation
: public cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>
{
public:
socket_disconnect_operation(socket& socket) noexcept
: m_impl(socket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void get_result() { m_impl.get_result(*this); }
socket_disconnect_operation_impl m_impl;
};
class socket_disconnect_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>
{
public:
socket_disconnect_operation_cancellable(socket& socket, cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>(std::move(ct))
, m_impl(socket)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
void get_result() { m_impl.get_result(*this); }
socket_disconnect_operation_impl m_impl;
};
}
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,106 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cstdint>
#include <tuple>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_recv_from_operation_impl
{
public:
socket_recv_from_operation_impl(
socket& socket,
void* buffer,
std::size_t byteCount) noexcept
: m_socket(socket)
, m_buffer(buffer, byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
std::tuple<std::size_t, ip_endpoint> get_result(
cppcoro::detail::win32_overlapped_operation_base& operation);
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
static constexpr std::size_t sockaddrStorageAlignment = 4;
// Storage suitable for either SOCKADDR_IN or SOCKADDR_IN6
alignas(sockaddrStorageAlignment) std::uint8_t m_sourceSockaddrStorage[28];
int m_sourceSockaddrLength;
};
class socket_recv_from_operation
: public cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>
{
public:
socket_recv_from_operation(
socket& socket,
void* buffer,
std::size_t byteCount) noexcept
: m_impl(socket, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_recv_from_operation_impl m_impl;
};
class socket_recv_from_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>
{
public:
socket_recv_from_operation_cancellable(
socket& socket,
void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>(std::move(ct))
, m_impl(socket, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
decltype(auto) get_result() { return m_impl.get_result(*this); }
socket_recv_from_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_recv_operation_impl
{
public:
socket_recv_operation_impl(
socket& s,
void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_buffer(buffer, byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_recv_operation
: public cppcoro::detail::win32_overlapped_operation<socket_recv_operation>
{
public:
socket_recv_operation(
socket& s,
void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_recv_operation_impl m_impl;
};
class socket_recv_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>
{
public:
socket_recv_operation_cancellable(
socket& s,
void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>(std::move(ct))
, m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { m_impl.cancel(*this); }
socket_recv_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_send_operation_impl
{
public:
socket_send_operation_impl(
socket& s,
const void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_buffer(const_cast<void*>(buffer), byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_send_operation
: public cppcoro::detail::win32_overlapped_operation<socket_send_operation>
{
public:
socket_send_operation(
socket& s,
const void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_send_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_send_operation_impl m_impl;
};
class socket_send_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>
{
public:
socket_send_operation_cancellable(
socket& s,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>(std::move(ct))
, m_impl(s, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { return m_impl.cancel(*this); }
socket_send_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
@@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
#define CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/net/ip_endpoint.hpp>
#include <cstdint>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
# include <cppcoro/detail/win32_overlapped_operation.hpp>
namespace cppcoro::net
{
class socket;
class socket_send_to_operation_impl
{
public:
socket_send_to_operation_impl(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount) noexcept
: m_socket(s)
, m_destination(destination)
, m_buffer(const_cast<void*>(buffer), byteCount)
{}
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
private:
socket& m_socket;
ip_endpoint m_destination;
cppcoro::detail::win32::wsabuf m_buffer;
};
class socket_send_to_operation
: public cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>
{
public:
socket_send_to_operation(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount) noexcept
: m_impl(s, destination, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>;
bool try_start() noexcept { return m_impl.try_start(*this); }
socket_send_to_operation_impl m_impl;
};
class socket_send_to_operation_cancellable
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>
{
public:
socket_send_to_operation_cancellable(
socket& s,
const ip_endpoint& destination,
const void* buffer,
std::size_t byteCount,
cancellation_token&& ct) noexcept
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>(std::move(ct))
, m_impl(s, destination, buffer, byteCount)
{}
private:
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>;
bool try_start() noexcept { return m_impl.try_start(*this); }
void cancel() noexcept { return m_impl.cancel(*this); }
socket_send_to_operation_impl m_impl;
};
}
#endif // CPPCORO_OS_WINNT
#endif
+147
View File
@@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
#define CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
#include <type_traits>
#include <exception>
namespace cppcoro
{
template<typename FUNC>
class scoped_lambda
{
public:
scoped_lambda(FUNC&& func)
: m_func(std::forward<FUNC>(func))
, m_cancelled(false)
{}
scoped_lambda(const scoped_lambda& other) = delete;
scoped_lambda(scoped_lambda&& other)
: m_func(std::forward<FUNC>(other.m_func))
, m_cancelled(other.m_cancelled)
{
other.cancel();
}
~scoped_lambda()
{
if (!m_cancelled)
{
m_func();
}
}
void cancel()
{
m_cancelled = true;
}
void call_now()
{
m_cancelled = true;
m_func();
}
private:
FUNC m_func;
bool m_cancelled;
};
/// A scoped lambda that executes the lambda when the object destructs
/// but only if exiting due to an exception (CALL_ON_FAILURE = true) or
/// only if not exiting due to an exception (CALL_ON_FAILURE = false).
template<typename FUNC, bool CALL_ON_FAILURE>
class conditional_scoped_lambda
{
public:
conditional_scoped_lambda(FUNC&& func)
: m_func(std::forward<FUNC>(func))
, m_uncaughtExceptionCount(std::uncaught_exceptions())
, m_cancelled(false)
{}
conditional_scoped_lambda(const conditional_scoped_lambda& other) = delete;
conditional_scoped_lambda(conditional_scoped_lambda&& other)
noexcept(std::is_nothrow_move_constructible<FUNC>::value)
: m_func(std::forward<FUNC>(other.m_func))
, m_uncaughtExceptionCount(other.m_uncaughtExceptionCount)
, m_cancelled(other.m_cancelled)
{
other.cancel();
}
~conditional_scoped_lambda() noexcept(CALL_ON_FAILURE || noexcept(std::declval<FUNC>()()))
{
if (!m_cancelled && (is_unwinding_due_to_exception() == CALL_ON_FAILURE))
{
m_func();
}
}
void cancel() noexcept
{
m_cancelled = true;
}
private:
bool is_unwinding_due_to_exception() const noexcept
{
return std::uncaught_exceptions() > m_uncaughtExceptionCount;
}
FUNC m_func;
int m_uncaughtExceptionCount;
bool m_cancelled;
};
/// Returns an object that calls the provided function when it goes out
/// of scope either normally or due to an uncaught exception unwinding
/// the stack.
///
/// \param func
/// The function to call when the scope exits.
/// The function must be noexcept.
template<typename FUNC>
auto on_scope_exit(FUNC&& func)
{
return scoped_lambda<FUNC>{ std::forward<FUNC>(func) };
}
/// Returns an object that calls the provided function when it goes out
/// of scope due to an uncaught exception unwinding the stack.
///
/// \param func
/// The function to be called if unwinding due to an exception.
/// The function must be noexcept.
template<typename FUNC>
auto on_scope_failure(FUNC&& func)
{
return conditional_scoped_lambda<FUNC, true>{ std::forward<FUNC>(func) };
}
/// Returns an object that calls the provided function when it goes out
/// of scope via normal execution (ie. not unwinding due to an exception).
///
/// \param func
/// The function to call if the scope exits normally.
/// The function does not necessarily need to be noexcept.
template<typename FUNC>
auto on_scope_success(FUNC&& func)
{
return conditional_scoped_lambda<FUNC, false>{ std::forward<FUNC>(func) };
}
}
#endif
+24
View File
@@ -0,0 +1,24 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
#define CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
#include <exception>
namespace cppcoro
{
class operation_cancelled : public std::exception
{
public:
operation_cancelled() noexcept
: std::exception()
{}
const char* what() const noexcept override { return "operation cancelled"; }
};
}
#endif
+59
View File
@@ -0,0 +1,59 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
#define CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
#include <cppcoro/readable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class read_only_file : public readable_file
{
public:
/// Open a file for read-only access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous read operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param path
/// Path of the file to open.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to read from the file.
///
/// \throw std::system_error
/// If the file could not be opened for read.
[[nodiscard]]
static read_only_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_share_mode shareMode = file_share_mode::read,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
read_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif
+66
View File
@@ -0,0 +1,66 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
#define CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
#include <cppcoro/readable_file.hpp>
#include <cppcoro/writable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class read_write_file : public readable_file, public writable_file
{
public:
/// Open a file for read-write access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous write operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param pathMode
/// Path of the file to open.
///
/// \param openMode
/// Specifies how the file should be opened and how to handle cases
/// when the file exists or doesn't exist.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to write to the file.
///
/// \throw std::system_error
/// If the file could not be opened for write.
[[nodiscard]]
static read_write_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
read_write_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif
+65
View File
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_READABLE_FILE_HPP_INCLUDED
#define CPPCORO_READABLE_FILE_HPP_INCLUDED
#include <cppcoro/file.hpp>
#include <cppcoro/file_read_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
namespace cppcoro
{
class readable_file : virtual public file
{
public:
/// Read some data from the file.
///
/// Reads \a byteCount bytes from the file starting at \a offset
/// into the specified \a buffer.
///
/// \param offset
/// The offset within the file to start reading from.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the offset must be a multiple of the file-system's sector size.
///
/// \param buffer
/// The buffer to read the file contents into.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the address of the start of the buffer must be a multiple of
/// the file-system's sector size.
///
/// \param byteCount
/// The number of bytes to read from the file.
/// If the file has been opeend using file_buffering_mode::unbuffered
/// then the byteCount must be a multiple of the file-system's sector size.
///
/// \param ct
/// An optional cancellation_token that can be used to cancel the
/// read operation before it completes.
///
/// \return
/// An object that represents the read-operation.
/// This object must be co_await'ed to start the read operation.
[[nodiscard]]
file_read_operation read(
std::uint64_t offset,
void* buffer,
std::size_t byteCount) const noexcept;
[[nodiscard]]
file_read_operation_cancellable read(
std::uint64_t offset,
void* buffer,
std::size_t byteCount,
cancellation_token ct) const noexcept;
protected:
using file::file;
};
}
#endif
+345
View File
@@ -0,0 +1,345 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
#define CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
#include <cppcoro/generator.hpp>
#include <cppcoro/coroutine.hpp>
#include <type_traits>
#include <utility>
#include <cassert>
#include <functional>
namespace cppcoro
{
template<typename T>
class [[nodiscard]] recursive_generator
{
public:
class promise_type final
{
public:
promise_type() noexcept
: m_value(nullptr)
, m_exception(nullptr)
, m_root(this)
, m_parentOrLeaf(this)
{}
promise_type(const promise_type&) = delete;
promise_type(promise_type&&) = delete;
auto get_return_object() noexcept
{
return recursive_generator<T>{ *this };
}
cppcoro::suspend_always initial_suspend() noexcept
{
return {};
}
cppcoro::suspend_always final_suspend() noexcept
{
return {};
}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_void() noexcept {}
cppcoro::suspend_always yield_value(T& value) noexcept
{
m_value = std::addressof(value);
return {};
}
cppcoro::suspend_always yield_value(T&& value) noexcept
{
m_value = std::addressof(value);
return {};
}
auto yield_value(recursive_generator&& generator) noexcept
{
return yield_value(generator);
}
auto yield_value(recursive_generator& generator) noexcept
{
struct awaitable
{
awaitable(promise_type* childPromise)
: m_childPromise(childPromise)
{}
bool await_ready() noexcept
{
return this->m_childPromise == nullptr;
}
void await_suspend(cppcoro::coroutine_handle<promise_type>) noexcept
{}
void await_resume()
{
if (this->m_childPromise != nullptr)
{
this->m_childPromise->throw_if_exception();
}
}
private:
promise_type* m_childPromise;
};
if (generator.m_promise != nullptr)
{
m_root->m_parentOrLeaf = generator.m_promise;
generator.m_promise->m_root = m_root;
generator.m_promise->m_parentOrLeaf = this;
generator.m_promise->resume();
if (!generator.m_promise->is_complete())
{
return awaitable{ generator.m_promise };
}
m_root->m_parentOrLeaf = this;
}
return awaitable{ nullptr };
}
// Don't allow any use of 'co_await' inside the recursive_generator coroutine.
template<typename U>
cppcoro::suspend_never await_transform(U&& value) = delete;
void destroy() noexcept
{
cppcoro::coroutine_handle<promise_type>::from_promise(*this).destroy();
}
void throw_if_exception()
{
if (m_exception != nullptr)
{
std::rethrow_exception(std::move(m_exception));
}
}
bool is_complete() noexcept
{
return cppcoro::coroutine_handle<promise_type>::from_promise(*this).done();
}
T& value() noexcept
{
assert(this == m_root);
assert(!is_complete());
return *(m_parentOrLeaf->m_value);
}
void pull() noexcept
{
assert(this == m_root);
assert(!m_parentOrLeaf->is_complete());
m_parentOrLeaf->resume();
while (m_parentOrLeaf != this && m_parentOrLeaf->is_complete())
{
m_parentOrLeaf = m_parentOrLeaf->m_parentOrLeaf;
m_parentOrLeaf->resume();
}
}
private:
void resume() noexcept
{
cppcoro::coroutine_handle<promise_type>::from_promise(*this).resume();
}
std::add_pointer_t<T> m_value;
std::exception_ptr m_exception;
promise_type* m_root;
// If this is the promise of the root generator then this field
// is a pointer to the leaf promise.
// For non-root generators this is a pointer to the parent promise.
promise_type* m_parentOrLeaf;
};
recursive_generator() noexcept
: m_promise(nullptr)
{}
recursive_generator(promise_type& promise) noexcept
: m_promise(&promise)
{}
recursive_generator(recursive_generator&& other) noexcept
: m_promise(other.m_promise)
{
other.m_promise = nullptr;
}
recursive_generator(const recursive_generator& other) = delete;
recursive_generator& operator=(const recursive_generator& other) = delete;
~recursive_generator()
{
if (m_promise != nullptr)
{
m_promise->destroy();
}
}
recursive_generator& operator=(recursive_generator&& other) noexcept
{
if (this != &other)
{
if (m_promise != nullptr)
{
m_promise->destroy();
}
m_promise = other.m_promise;
other.m_promise = nullptr;
}
return *this;
}
class iterator
{
public:
using iterator_category = std::input_iterator_tag;
// What type should we use for counting elements of a potentially infinite sequence?
using difference_type = std::ptrdiff_t;
using value_type = std::remove_reference_t<T>;
using reference = std::conditional_t<std::is_reference_v<T>, T, T&>;
using pointer = std::add_pointer_t<T>;
iterator() noexcept
: m_promise(nullptr)
{}
explicit iterator(promise_type* promise) noexcept
: m_promise(promise)
{}
bool operator==(const iterator& other) const noexcept
{
return m_promise == other.m_promise;
}
bool operator!=(const iterator& other) const noexcept
{
return m_promise != other.m_promise;
}
iterator& operator++()
{
assert(m_promise != nullptr);
assert(!m_promise->is_complete());
m_promise->pull();
if (m_promise->is_complete())
{
auto* temp = m_promise;
m_promise = nullptr;
temp->throw_if_exception();
}
return *this;
}
void operator++(int)
{
(void)operator++();
}
reference operator*() const noexcept
{
assert(m_promise != nullptr);
return static_cast<reference>(m_promise->value());
}
pointer operator->() const noexcept
{
return std::addressof(operator*());
}
private:
promise_type* m_promise;
};
iterator begin()
{
if (m_promise != nullptr)
{
m_promise->pull();
if (!m_promise->is_complete())
{
return iterator(m_promise);
}
m_promise->throw_if_exception();
}
return iterator(nullptr);
}
iterator end() noexcept
{
return iterator(nullptr);
}
void swap(recursive_generator& other) noexcept
{
std::swap(m_promise, other.m_promise);
}
private:
friend class promise_type;
promise_type* m_promise;
};
template<typename T>
void swap(recursive_generator<T>& a, recursive_generator<T>& b) noexcept
{
a.swap(b);
}
// Note: When applying fmap operator to a recursive_generator we just yield a non-recursive
// generator since we generally won't be using the result in a recursive context.
template<typename FUNC, typename T>
generator<std::invoke_result_t<FUNC&, typename recursive_generator<T>::iterator::reference>> fmap(FUNC func, recursive_generator<T> source)
{
for (auto&& value : source)
{
co_yield std::invoke(func, static_cast<decltype(value)>(value));
}
}
}
#endif
+129
View File
@@ -0,0 +1,129 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_RESUME_ON_HPP_INCLUDED
#define CPPCORO_RESUME_ON_HPP_INCLUDED
#include <cppcoro/task.hpp>
#include <cppcoro/async_generator.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/get_awaiter.hpp>
#include <exception>
#include <type_traits>
namespace cppcoro
{
template<typename SCHEDULER>
struct resume_on_transform
{
explicit resume_on_transform(SCHEDULER& s) noexcept
: scheduler(s)
{}
SCHEDULER& scheduler;
};
template<typename SCHEDULER>
resume_on_transform<SCHEDULER> resume_on(SCHEDULER& scheduler) noexcept
{
return resume_on_transform<SCHEDULER>(scheduler);
}
template<typename T, typename SCHEDULER>
decltype(auto) operator|(T&& value, resume_on_transform<SCHEDULER> transform)
{
return resume_on(transform.scheduler, std::forward<T>(value));
}
template<
typename SCHEDULER,
typename AWAITABLE,
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<AWAIT_RESULT>
{
bool rescheduled = false;
std::exception_ptr ex;
try
{
// We manually get the awaiter here so that we can keep
// it alive across the call to `scheduler.schedule()`
// just in case the result is a reference to a value
// in the awaiter that would otherwise be a temporary
// and destructed before the value could be returned.
auto&& awaiter = detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable));
auto&& result = co_await static_cast<decltype(awaiter)>(awaiter);
// Flag as rescheduled before scheduling in case it is the
// schedule() operation that throws an exception as we don't
// want to attempt to schedule twice if scheduling fails.
rescheduled = true;
co_await scheduler.schedule();
co_return static_cast<decltype(result)>(result);
}
catch (...)
{
ex = std::current_exception();
}
// We still want to resume on the scheduler even in the presence
// of an exception.
if (!rescheduled)
{
co_await scheduler.schedule();
}
std::rethrow_exception(ex);
}
template<
typename SCHEDULER,
typename AWAITABLE,
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<>
{
std::exception_ptr ex;
try
{
co_await static_cast<AWAITABLE&&>(awaitable);
}
catch (...)
{
ex = std::current_exception();
}
// NOTE: We're assuming that `schedule()` operation is noexcept
// here. If it were to throw what would we do if 'ex' was non-null?
// Presumably we'd treat it the same as throwing an exception while
// unwinding and call std::terminate()?
co_await scheduler.schedule();
if (ex)
{
std::rethrow_exception(ex);
}
}
template<typename SCHEDULER, typename T>
async_generator<T> resume_on(SCHEDULER& scheduler, async_generator<T> source)
{
for (auto iter = co_await source.begin(); iter != source.end(); co_await ++iter)
{
auto& value = *iter;
co_await scheduler.schedule();
co_yield value;
}
}
}
#endif
+124
View File
@@ -0,0 +1,124 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
#define CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/coroutine.hpp>
#include <array>
#include <cassert>
#include <algorithm>
#include <utility>
namespace cppcoro
{
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
/// This is a scheduler class that schedules coroutines in a round-robin
/// fashion once N coroutines have been scheduled to it.
///
/// Only supports access from a single thread at a time so
///
/// This implementation was inspired by Gor Nishanov's CppCon 2018 talk
/// about nano-coroutines.
///
/// The implementation relies on symmetric transfer and noop_coroutine()
/// and so only works with a relatively recent version of Clang and does
/// not yet work with MSVC.
template<size_t N>
class round_robin_scheduler
{
static_assert(
N >= 2,
"Round robin scheduler must be configured to support at least two coroutines");
class schedule_operation
{
public:
explicit schedule_operation(round_robin_scheduler& s) noexcept : m_scheduler(s) {}
bool await_ready() noexcept
{
return false;
}
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_scheduler.exchange_next(awaitingCoroutine);
}
void await_resume() noexcept {}
private:
round_robin_scheduler& m_scheduler;
};
friend class schedule_operation;
public:
round_robin_scheduler() noexcept
: m_index(0)
, m_noop(cppcoro::noop_coroutine())
{
for (size_t i = 0; i < N - 1; ++i)
{
m_coroutines[i] = m_noop();
}
}
~round_robin_scheduler()
{
// All tasks should have been joined before calling destructor.
assert(std::all_of(
m_coroutines.begin(),
m_coroutines.end(),
[&](auto h) { return h == m_noop; }));
}
schedule_operation schedule() noexcept
{
return schedule_operation{ *this };
}
/// Resume any queued coroutines until there are no more coroutines.
void drain() noexcept
{
size_t countRemaining = N - 1;
do
{
auto nextToResume = exchange_next(m_noop);
if (nextToResume != m_noop)
{
nextToResume.resume();
countRemaining = N - 1;
}
else
{
--countRemaining;
}
} while (countRemaining > 0);
}
private:
cppcoro::coroutine_handle exchange_next(
cppcoro::coroutine_handle<> coroutine) noexcept
{
auto coroutineToResume = std::exchange(
m_scheduler.m_coroutines[m_scheduler.m_index],
awaitingCoroutine);
m_scheduler.m_index = m_scheduler.m_index < (N - 2) ? m_scheduler.m_index + 1 : 0;
return coroutineToResume;
}
size_t m_index;
const cppcoro::coroutine_handle<> m_noop;
std::array<cppcoro::coroutine_handle<>, N - 1> m_coroutines;
};
#endif
}
#endif
+69
View File
@@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SCHEDULE_ON_HPP_INCLUDED
#define CPPCORO_SCHEDULE_ON_HPP_INCLUDED
#include <cppcoro/task.hpp>
#include <cppcoro/shared_task.hpp>
#include <cppcoro/async_generator.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
namespace cppcoro
{
template<typename SCHEDULER>
struct schedule_on_transform
{
explicit schedule_on_transform(SCHEDULER& scheduler) noexcept
: scheduler(scheduler)
{}
SCHEDULER& scheduler;
};
template<typename SCHEDULER>
schedule_on_transform<SCHEDULER> schedule_on(SCHEDULER& scheduler)
{
return schedule_on_transform<SCHEDULER>{ scheduler };
}
template<typename T, typename SCHEDULER>
decltype(auto) operator|(T&& value, schedule_on_transform<SCHEDULER> transform)
{
return schedule_on(transform.scheduler, std::forward<T>(value));
}
template<typename SCHEDULER, typename AWAITABLE>
auto schedule_on(SCHEDULER& scheduler, AWAITABLE awaitable)
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_await scheduler.schedule();
co_return co_await std::move(awaitable);
}
template<typename T, typename SCHEDULER>
async_generator<T> schedule_on(SCHEDULER& scheduler, async_generator<T> source)
{
// Transfer exection to the scheduler before the implicit calls to
// 'co_await begin()' or subsequent calls to `co_await iterator::operator++()`
// below. This ensures that all calls to the generator's coroutine_handle<>::resume()
// are executed on the execution context of the scheduler.
co_await scheduler.schedule();
const auto itEnd = source.end();
auto it = co_await source.begin();
while (it != itEnd)
{
co_yield *it;
co_await scheduler.schedule();
(void)co_await ++it;
}
}
}
#endif
+470
View File
@@ -0,0 +1,470 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
#define CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/sequence_traits.hpp>
#include <cppcoro/detail/manual_lifetime.hpp>
#include <atomic>
#include <cassert>
#include <cstdint>
#include <limits>
#include <optional>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS>
class sequence_barrier_wait_operation_base;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class sequence_barrier_wait_operation;
/// A sequence barrier is a synchronisation primitive that allows a single-producer
/// and multiple-consumers to coordinate with respect to a monotonically increasing
/// sequence number.
///
/// A single producer advances the sequence number by publishing new sequence numbers in a
/// monotonically increasing order. One or more consumers can query the last-published
/// sequence number and can wait until a particular sequence number has been published.
///
/// A sequence barrier can be used to represent a cursor into a thread-safe producer/consumer
/// ring-buffer.
///
/// See the LMAX Disruptor pattern for more background:
/// https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class sequence_barrier
{
static_assert(
std::is_integral_v<SEQUENCE>,
"sequence_barrier requires an integral sequence type");
using awaiter_t = sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
public:
/// Construct a sequence barrier with the specified initial sequence number
/// as the initial value 'last_published()'.
sequence_barrier(SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
: m_lastPublished(initialSequence)
, m_awaiters(nullptr)
{}
~sequence_barrier()
{
// Shouldn't be destructing a sequence barrier if there are still waiters.
assert(m_awaiters.load(std::memory_order_relaxed) == nullptr);
}
/// Query the sequence number that was most recently published by the producer.
///
/// You can assume that all sequence numbers prior to the returned sequence number
/// have also been published. This means you can safely access all elements with
/// sequence numbers up to and including the returned sequence number without any
/// further synchronisation.
SEQUENCE last_published() const noexcept
{
return m_lastPublished.load(std::memory_order_acquire);
}
/// Wait until a particular sequence number has been published.
///
/// If the specified sequence number is not yet published then the awaiting coroutine
/// will be suspended and later resumed inside the call to publish() that publishes
/// the specified sequence number.
///
/// \param targetSequence
/// The sequence number to wait for.
///
/// \return
/// An awaitable that when co_await'ed will suspend the awaiting coroutine until
/// the specified target sequence number has been published.
/// The result of the co_await expression will be the last-known published sequence
/// number. This is guaranteed not to precede \p targetSequence but may be a sequence
/// number after \p targetSequence, which indicates that more elements have been
/// published than you were waiting for.
template<typename SCHEDULER>
[[nodiscard]]
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept;
/// Publish the specified sequence number to consumers.
///
/// This publishes all sequence numbers up to and including the specified sequence
/// number. This will resume any coroutine that was suspended waiting for a sequence
/// number that was published by this operation.
///
/// \param sequence
/// The sequence number to publish. This number must not precede the current
/// last_published() value. ie. the published sequence numbers must be monotonically
/// increasing.
void publish(SEQUENCE sequence) noexcept;
private:
friend class sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
void add_awaiter(awaiter_t* awaiter) const noexcept;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
// First cache-line is written to by the producer only
alignas(CPPCORO_CPU_CACHE_LINE)
std::atomic<SEQUENCE> m_lastPublished;
// Second cache-line is written to by both the producer and consumers
alignas(CPPCORO_CPU_CACHE_LINE)
mutable std::atomic<awaiter_t*> m_awaiters;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS>
class sequence_barrier_wait_operation_base
{
public:
explicit sequence_barrier_wait_operation_base(
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
SEQUENCE targetSequence) noexcept
: m_barrier(barrier)
, m_targetSequence(targetSequence)
, m_lastKnownPublished(barrier.last_published())
, m_readyToResume(false)
{}
sequence_barrier_wait_operation_base(
const sequence_barrier_wait_operation_base& other) noexcept
: m_barrier(other.m_barrier)
, m_targetSequence(other.m_targetSequence)
, m_lastKnownPublished(other.m_lastKnownPublished)
, m_readyToResume(false)
{}
bool await_ready() const noexcept
{
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
}
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
m_barrier.add_awaiter(this);
return !m_readyToResume.exchange(true, std::memory_order_acquire);
}
SEQUENCE await_resume() noexcept
{
return m_lastKnownPublished;
}
protected:
friend class sequence_barrier<SEQUENCE, TRAITS>;
void resume() noexcept
{
// This synchronises with the exchange(true, std::memory_order_acquire) in await_suspend().
if (m_readyToResume.exchange(true, std::memory_order_release))
{
resume_impl();
}
}
virtual void resume_impl() noexcept = 0;
const sequence_barrier<SEQUENCE, TRAITS>& m_barrier;
const SEQUENCE m_targetSequence;
SEQUENCE m_lastKnownPublished;
sequence_barrier_wait_operation_base* m_next;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
std::atomic<bool> m_readyToResume;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class sequence_barrier_wait_operation : public sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>
{
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
public:
sequence_barrier_wait_operation(
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
SEQUENCE targetSequence,
SCHEDULER& scheduler) noexcept
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(barrier, targetSequence)
, m_scheduler(scheduler)
{}
sequence_barrier_wait_operation(
const sequence_barrier_wait_operation& other) noexcept
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(other)
, m_scheduler(other.m_scheduler)
{}
~sequence_barrier_wait_operation()
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter.destruct();
}
if (m_isScheduleOperationCreated)
{
m_scheduleOperation.destruct();
}
}
decltype(auto) await_resume() noexcept(noexcept(m_scheduleAwaiter->await_resume()))
{
if (m_isScheduleAwaiterCreated)
{
m_scheduleAwaiter->await_resume();
}
return sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
}
private:
void resume_impl() noexcept override
{
try
{
m_scheduleOperation.construct(m_scheduler.schedule());
m_isScheduleOperationCreated = true;
m_scheduleAwaiter.construct(detail::get_awaiter(
static_cast<schedule_operation&&>(*m_scheduleOperation)));
m_isScheduleAwaiterCreated = true;
if (!m_scheduleAwaiter->await_ready())
{
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
if constexpr (std::is_void_v<await_suspend_result_t>)
{
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
return;
}
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
{
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
{
return;
}
}
else
{
// Assume it returns a coroutine_handle.
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
return;
}
}
}
catch (...)
{
// Ignore failure to reschedule and resume inline?
// Should we catch the exception and rethrow from await_resume()?
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
}
// Resume outside the catch-block.
this->m_awaitingCoroutine.resume();
}
SCHEDULER& m_scheduler;
// Can't use std::optional<T> here since T could be a reference.
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
bool m_isScheduleOperationCreated = false;
bool m_isScheduleAwaiterCreated = false;
};
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> sequence_barrier<SEQUENCE, TRAITS>::wait_until_published(
SEQUENCE targetSequence,
SCHEDULER& scheduler) const noexcept
{
return sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, targetSequence, scheduler);
}
template<typename SEQUENCE, typename TRAITS>
void sequence_barrier<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
{
m_lastPublished.store(sequence, std::memory_order_seq_cst);
// Cheaper check to see if there are any awaiting coroutines.
auto* awaiters = m_awaiters.load(std::memory_order_seq_cst);
if (awaiters == nullptr)
{
return;
}
// Acquire the list of awaiters.
// Note we may be racing with add_awaiter() which could also acquire the list of waiters
// so we need to check again whether we won the race and acquired the list.
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
if (awaiters == nullptr)
{
return;
}
// Check the list of awaiters for ones that are now satisfied by the sequence number
// we just published. Awaiters are added to either the 'awaitersToResume' list or to
// the 'awaitersToRequeue' list.
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
awaiter_t* awaitersToRequeue;
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
do
{
if (TRAITS::precedes(sequence, awaiters->m_targetSequence))
{
// Target sequence not reached. Append to 'requeue' list.
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
}
else
{
// Target sequence reached. Append to 'resume' list.
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
} while (awaiters != nullptr);
// Null-terminate the two lists.
*awaitersToRequeueTail = nullptr;
*awaitersToResumeTail = nullptr;
if (awaitersToRequeue != nullptr)
{
awaiter_t* oldHead = nullptr;
while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToRequeue,
std::memory_order_release,
std::memory_order_relaxed))
{
*awaitersToRequeueTail = oldHead;
}
}
while (awaitersToResume != nullptr)
{
auto* next = awaitersToResume->m_next;
awaitersToResume->m_lastKnownPublished = sequence;
awaitersToResume->resume();
awaitersToResume = next;
}
}
template<typename SEQUENCE, typename TRAITS>
void sequence_barrier<SEQUENCE, TRAITS>::add_awaiter(awaiter_t* awaiter) const noexcept
{
SEQUENCE targetSequence = awaiter->m_targetSequence;
awaiter_t* awaitersToRequeue = awaiter;
awaiter_t** awaitersToRequeueTail = &awaiter->m_next;
SEQUENCE lastKnownPublished;
awaiter_t* awaitersToResume;
awaiter_t** awaitersToResumeTail = &awaitersToResume;
do
{
// Enqueue the awaiter(s)
{
auto* oldHead = m_awaiters.load(std::memory_order_relaxed);
do
{
*awaitersToRequeueTail = oldHead;
} while (!m_awaiters.compare_exchange_weak(
oldHead,
awaitersToRequeue,
std::memory_order_seq_cst,
std::memory_order_relaxed));
}
// Check that the sequence we were waiting for wasn't published while
// we were enqueueing the waiter.
// This needs to be seq_cst memory order to ensure that in the case that the producer
// publishes a new sequence number concurrently with this call that we either see
// their write to m_lastPublished after enqueueing our awaiter, or they see our
// write to m_awaiters after their write to m_lastPublished.
lastKnownPublished = m_lastPublished.load(std::memory_order_seq_cst);
if (TRAITS::precedes(lastKnownPublished, targetSequence))
{
// None of the the awaiters we enqueued have been satisfied yet.
break;
}
// Reset the requeue list to empty
awaitersToRequeueTail = &awaitersToRequeue;
// At least one of the awaiters we just enqueued is now satisfied by a concurrently
// published sequence number. The producer thread may not have seen our write to m_awaiters
// so we need to try to re-acquire the list of awaiters to ensure that the waiters that
// are now satisfied are woken up.
auto* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
auto minDiff = std::numeric_limits<typename TRAITS::difference_type>::max();
while (awaiters != nullptr)
{
const auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
if (diff > 0)
{
*awaitersToRequeueTail = awaiters;
awaitersToRequeueTail = &awaiters->m_next;
minDiff = diff < minDiff ? diff : minDiff;
}
else
{
*awaitersToResumeTail = awaiters;
awaitersToResumeTail = &awaiters->m_next;
}
awaiters = awaiters->m_next;
}
// Null-terminate the list of awaiters to requeue.
*awaitersToRequeueTail = nullptr;
// Calculate the earliest target sequence required by any of the awaiters to requeue.
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
} while (awaitersToRequeue != nullptr);
// Null-terminate the list of awaiters to resume
*awaitersToResumeTail = nullptr;
// Resume the awaiters that are ready
while (awaitersToResume != nullptr)
{
auto* next = awaitersToResume->m_next;
awaitersToResume->m_lastKnownPublished = lastKnownPublished;
awaitersToResume->resume();
awaitersToResume = next;
}
}
}
#endif
+107
View File
@@ -0,0 +1,107 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
#define CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
#include <cppcoro/sequence_traits.hpp>
#include <algorithm>
#include <iterator>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS = sequence_traits<SEQUENCE>>
class sequence_range
{
public:
using value_type = SEQUENCE;
using difference_type = typename TRAITS::difference_type;
using size_type = typename TRAITS::size_type;
class const_iterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = SEQUENCE;
using difference_type = typename TRAITS::difference_type;
using reference = const SEQUENCE&;
using pointer = const SEQUENCE*;
explicit constexpr const_iterator(SEQUENCE value) noexcept : m_value(value) {}
const SEQUENCE& operator*() const noexcept { return m_value; }
const SEQUENCE* operator->() const noexcept { return std::addressof(m_value); }
const_iterator& operator++() noexcept { ++m_value; return *this; }
const_iterator& operator--() noexcept { --m_value; return *this; }
const_iterator operator++(int) noexcept { return const_iterator(m_value++); }
const_iterator operator--(int) noexcept { return const_iterator(m_value--); }
constexpr difference_type operator-(const_iterator other) const noexcept { return TRAITS::difference(m_value, other.m_value); }
constexpr const_iterator operator-(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value - delta) }; }
constexpr const_iterator operator+(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value + delta) }; }
constexpr bool operator==(const_iterator other) const noexcept { return m_value == other.m_value; }
constexpr bool operator!=(const_iterator other) const noexcept { return m_value != other.m_value; }
private:
SEQUENCE m_value;
};
constexpr sequence_range() noexcept
: m_begin()
, m_end()
{}
constexpr sequence_range(SEQUENCE begin, SEQUENCE end) noexcept
: m_begin(begin)
, m_end(end)
{}
constexpr const_iterator begin() const noexcept { return const_iterator(m_begin); }
constexpr const_iterator end() const noexcept { return const_iterator(m_end); }
constexpr SEQUENCE front() const noexcept { return m_begin; }
constexpr SEQUENCE back() const noexcept { return m_end - 1; }
constexpr size_type size() const noexcept
{
return static_cast<size_type>(TRAITS::difference(m_end, m_begin));
}
constexpr bool empty() const noexcept
{
return m_begin == m_end;
}
constexpr SEQUENCE operator[](size_type index) const noexcept
{
return m_begin + index;
}
constexpr sequence_range first(size_type count) const noexcept
{
return sequence_range{ m_begin, static_cast<SEQUENCE>(m_begin + std::min(size(), count)) };
}
constexpr sequence_range skip(size_type count) const noexcept
{
return sequence_range{ m_begin + std::min(size(), count), m_end };
}
private:
SEQUENCE m_begin;
SEQUENCE m_end;
};
}
#endif
+33
View File
@@ -0,0 +1,33 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
#define CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
#include <type_traits>
namespace cppcoro
{
template<typename SEQUENCE>
struct sequence_traits
{
using value_type = SEQUENCE;
using difference_type = std::make_signed_t<SEQUENCE>;
using size_type = std::make_unsigned_t<SEQUENCE>;
static constexpr value_type initial_sequence = static_cast<value_type>(-1);
static constexpr difference_type difference(value_type a, value_type b)
{
return static_cast<difference_type>(a - b);
}
static constexpr bool precedes(value_type a, value_type b)
{
return difference(a, b) < 0;
}
};
}
#endif
+511
View File
@@ -0,0 +1,511 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
#define CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/broken_promise.hpp>
#include <cppcoro/task.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
#include <atomic>
#include <exception>
#include <utility>
#include <type_traits>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename T>
class shared_task;
namespace detail
{
struct shared_task_waiter
{
cppcoro::coroutine_handle<> m_continuation;
shared_task_waiter* m_next;
};
class shared_task_promise_base
{
friend struct final_awaiter;
struct final_awaiter
{
bool await_ready() const noexcept { return false; }
template<typename PROMISE>
void await_suspend(cppcoro::coroutine_handle<PROMISE> h) noexcept
{
shared_task_promise_base& promise = h.promise();
// Exchange operation needs to be 'release' so that subsequent awaiters have
// visibility of the result. Also needs to be 'acquire' so we have visibility
// of writes to the waiters list.
void* const valueReadyValue = &promise;
void* waiters = promise.m_waiters.exchange(valueReadyValue, std::memory_order_acq_rel);
if (waiters != nullptr)
{
shared_task_waiter* waiter = static_cast<shared_task_waiter*>(waiters);
while (waiter->m_next != nullptr)
{
// Read the m_next pointer before resuming the coroutine
// since resuming the coroutine may destroy the shared_task_waiter value.
auto* next = waiter->m_next;
waiter->m_continuation.resume();
waiter = next;
}
// Resume last waiter in tail position to allow it to potentially
// be compiled as a tail-call.
waiter->m_continuation.resume();
}
}
void await_resume() noexcept {}
};
public:
shared_task_promise_base() noexcept
: m_refCount(1)
, m_waiters(&this->m_waiters)
, m_exception(nullptr)
{}
cppcoro::suspend_always initial_suspend() noexcept { return {}; }
final_awaiter final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
bool is_ready() const noexcept
{
const void* const valueReadyValue = this;
return m_waiters.load(std::memory_order_acquire) == valueReadyValue;
}
void add_ref() noexcept
{
m_refCount.fetch_add(1, std::memory_order_relaxed);
}
/// Decrement the reference count.
///
/// \return
/// true if successfully detached, false if this was the last
/// reference to the coroutine, in which case the caller must
/// call destroy() on the coroutine handle.
bool try_detach() noexcept
{
return m_refCount.fetch_sub(1, std::memory_order_acq_rel) != 1;
}
/// Try to enqueue a waiter to the list of waiters.
///
/// \param waiter
/// Pointer to the state from the waiter object.
/// Must have waiter->m_coroutine member populated with the coroutine
/// handle of the awaiting coroutine.
///
/// \param coroutine
/// Coroutine handle for this promise object.
///
/// \return
/// true if the waiter was successfully queued, in which case
/// waiter->m_coroutine will be resumed when the task completes.
/// false if the coroutine was already completed and the awaiting
/// coroutine can continue without suspending.
bool try_await(shared_task_waiter* waiter, cppcoro::coroutine_handle<> coroutine)
{
void* const valueReadyValue = this;
void* const notStartedValue = &this->m_waiters;
constexpr void* startedNoWaitersValue = static_cast<shared_task_waiter*>(nullptr);
// NOTE: If the coroutine is not yet started then the first waiter
// will start the coroutine before enqueuing itself up to the list
// of suspended waiters waiting for completion. We split this into
// two steps to allow the first awaiter to return without suspending.
// This avoids recursively resuming the first waiter inside the call to
// coroutine.resume() in the case that the coroutine completes
// synchronously, which could otherwise lead to stack-overflow if
// the awaiting coroutine awaited many synchronously-completing
// tasks in a row.
// Start the coroutine if not already started.
void* oldWaiters = m_waiters.load(std::memory_order_acquire);
if (oldWaiters == notStartedValue &&
m_waiters.compare_exchange_strong(
oldWaiters,
startedNoWaitersValue,
std::memory_order_relaxed))
{
// Start the task executing.
coroutine.resume();
oldWaiters = m_waiters.load(std::memory_order_acquire);
}
// Enqueue the waiter into the list of waiting coroutines.
do
{
if (oldWaiters == valueReadyValue)
{
// Coroutine already completed, don't suspend.
return false;
}
waiter->m_next = static_cast<shared_task_waiter*>(oldWaiters);
} while (!m_waiters.compare_exchange_weak(
oldWaiters,
static_cast<void*>(waiter),
std::memory_order_release,
std::memory_order_acquire));
return true;
}
protected:
bool completed_with_unhandled_exception()
{
return m_exception != nullptr;
}
void rethrow_if_unhandled_exception()
{
if (m_exception != nullptr)
{
std::rethrow_exception(m_exception);
}
}
private:
std::atomic<std::uint32_t> m_refCount;
// Value is either
// - nullptr - indicates started, no waiters
// - this - indicates value is ready
// - &this->m_waiters - indicates coroutine not started
// - other - pointer to head item in linked-list of waiters.
// values are of type 'cppcoro::shared_task_waiter'.
// indicates that the coroutine has been started.
std::atomic<void*> m_waiters;
std::exception_ptr m_exception;
};
template<typename T>
class shared_task_promise : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
~shared_task_promise()
{
if (this->is_ready() && !this->completed_with_unhandled_exception())
{
reinterpret_cast<T*>(&m_valueStorage)->~T();
}
}
shared_task<T> get_return_object() noexcept;
template<
typename VALUE,
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
void return_value(VALUE&& value)
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
{
new (&m_valueStorage) T(std::forward<VALUE>(value));
}
T& result()
{
this->rethrow_if_unhandled_exception();
return *reinterpret_cast<T*>(&m_valueStorage);
}
private:
// Not using std::aligned_storage here due to bug in MSVC 2015 Update 2
// that means it doesn't work for types with alignof(T) > 8.
// See MS-Connect bug #2658635.
alignas(T) char m_valueStorage[sizeof(T)];
};
template<>
class shared_task_promise<void> : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
shared_task<void> get_return_object() noexcept;
void return_void() noexcept
{}
void result()
{
this->rethrow_if_unhandled_exception();
}
};
template<typename T>
class shared_task_promise<T&> : public shared_task_promise_base
{
public:
shared_task_promise() noexcept = default;
shared_task<T&> get_return_object() noexcept;
void return_value(T& value) noexcept
{
m_value = std::addressof(value);
}
T& result()
{
this->rethrow_if_unhandled_exception();
return *m_value;
}
private:
T* m_value;
};
}
template<typename T = void>
class [[nodiscard]] shared_task
{
public:
using promise_type = detail::shared_task_promise<T>;
using value_type = T;
private:
struct awaitable_base
{
cppcoro::coroutine_handle<promise_type> m_coroutine;
detail::shared_task_waiter m_waiter;
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
bool await_ready() const noexcept
{
return !m_coroutine || m_coroutine.promise().is_ready();
}
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept
{
m_waiter.m_continuation = awaiter;
return m_coroutine.promise().try_await(&m_waiter, m_coroutine);
}
};
public:
shared_task() noexcept
: m_coroutine(nullptr)
{}
explicit shared_task(cppcoro::coroutine_handle<promise_type> coroutine)
: m_coroutine(coroutine)
{
// Don't increment the ref-count here since it has already been
// initialised to 2 (one for shared_task and one for coroutine)
// in the shared_task_promise constructor.
}
shared_task(shared_task&& other) noexcept
: m_coroutine(other.m_coroutine)
{
other.m_coroutine = nullptr;
}
shared_task(const shared_task& other) noexcept
: m_coroutine(other.m_coroutine)
{
if (m_coroutine)
{
m_coroutine.promise().add_ref();
}
}
~shared_task()
{
destroy();
}
shared_task& operator=(shared_task&& other) noexcept
{
if (&other != this)
{
destroy();
m_coroutine = other.m_coroutine;
other.m_coroutine = nullptr;
}
return *this;
}
shared_task& operator=(const shared_task& other) noexcept
{
if (m_coroutine != other.m_coroutine)
{
destroy();
m_coroutine = other.m_coroutine;
if (m_coroutine)
{
m_coroutine.promise().add_ref();
}
}
return *this;
}
void swap(shared_task& other) noexcept
{
std::swap(m_coroutine, other.m_coroutine);
}
/// \brief
/// Query if the task result is complete.
///
/// Awaiting a task that is ready will not block.
bool is_ready() const noexcept
{
return !m_coroutine || m_coroutine.promise().is_ready();
}
auto operator co_await() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return this->m_coroutine.promise().result();
}
};
return awaitable{ m_coroutine };
}
/// \brief
/// Returns an awaitable that will await completion of the task without
/// attempting to retrieve the result.
auto when_ready() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
void await_resume() const noexcept {}
};
return awaitable{ m_coroutine };
}
private:
template<typename U>
friend bool operator==(const shared_task<U>&, const shared_task<U>&) noexcept;
void destroy() noexcept
{
if (m_coroutine)
{
if (!m_coroutine.promise().try_detach())
{
m_coroutine.destroy();
}
}
}
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
template<typename T>
bool operator==(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
{
return lhs.m_coroutine == rhs.m_coroutine;
}
template<typename T>
bool operator!=(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
{
return !(lhs == rhs);
}
template<typename T>
void swap(shared_task<T>& a, shared_task<T>& b) noexcept
{
a.swap(b);
}
namespace detail
{
template<typename T>
shared_task<T> shared_task_promise<T>::get_return_object() noexcept
{
return shared_task<T>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
template<typename T>
shared_task<T&> shared_task_promise<T&>::get_return_object() noexcept
{
return shared_task<T&>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
inline shared_task<void> shared_task_promise<void>::get_return_object() noexcept
{
return shared_task<void>{
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
};
}
}
template<typename AWAITABLE>
auto make_shared_task(AWAITABLE awaitable)
-> shared_task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_return co_await static_cast<AWAITABLE&&>(awaitable);
}
}
#endif
@@ -0,0 +1,101 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/coroutine.hpp>
#include <atomic>
#include <cstdint>
#include <cassert>
namespace cppcoro
{
class single_consumer_async_auto_reset_event
{
public:
single_consumer_async_auto_reset_event(bool initiallySet = false) noexcept
: m_state(initiallySet ? this : nullptr)
{}
void set() noexcept
{
void* oldValue = m_state.exchange(this, std::memory_order_release);
if (oldValue != nullptr && oldValue != this)
{
// There was a waiting coroutine that we now need to resume.
auto handle = *static_cast<cppcoro::coroutine_handle<>*>(oldValue);
// We also need to transition the state back to 'not set' before
// resuming the coroutine. This operation needs to be 'acquire'
// so that it synchronises with other calls to .set() that execute
// concurrently with this call and execute the above m_state.exchange(this)
// operation with 'release' semantics.
// This needs to be an exchange() instead of a store() so that it can have
// 'acquire' semantics.
(void)m_state.exchange(nullptr, std::memory_order_acquire);
// Finally, resume the waiting coroutine.
handle.resume();
}
}
auto operator co_await() const noexcept
{
class awaiter
{
public:
awaiter(const single_consumer_async_auto_reset_event& event) noexcept
: m_event(event)
{}
bool await_ready() const noexcept { return false; }
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_awaitingCoroutine = awaitingCoroutine;
void* oldValue = nullptr;
if (!m_event.m_state.compare_exchange_strong(
oldValue,
&m_awaitingCoroutine,
std::memory_order_release,
std::memory_order_relaxed))
{
// This will only fail if the event was already 'set'
// In which case we can just reset back to 'not set'
// Need to use exchange() rather than store() here so we can make this
// operation an 'acquire' operation so that we get visibility of all
// writes prior to all preceding calls to .set().
assert(oldValue == &m_event);
(void)m_event.m_state.exchange(nullptr, std::memory_order_acquire);
return false;
}
return true;
}
void await_resume() noexcept {}
private:
const single_consumer_async_auto_reset_event& m_event;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
};
return awaiter{ *this };
}
private:
// nullptr - not set, no waiter
// this - set
// other - not set, pointer is address of a coroutine_handle<> to resume.
mutable std::atomic<void*> m_state;
};
}
#endif
+128
View File
@@ -0,0 +1,128 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
#define CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
#include <atomic>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
/// \brief
/// A manual-reset event that supports only a single awaiting
/// coroutine at a time.
///
/// You can co_await the event to suspend the current coroutine until
/// some thread calls set(). If the event is already set then the
/// coroutine will not be suspended and will continue execution.
/// If the event was not yet set then the coroutine will be resumed
/// on the thread that calls set() within the call to set().
///
/// Callers must ensure that only one coroutine is executing a
/// co_await statement at any point in time.
class single_consumer_event
{
public:
/// \brief
/// Construct a new event, initialising to either 'set' or 'not set' state.
///
/// \param initiallySet
/// If true then initialises the event to the 'set' state.
/// Otherwise, initialised the event to the 'not set' state.
single_consumer_event(bool initiallySet = false) noexcept
: m_state(initiallySet ? state::set : state::not_set)
{}
/// Query if this event has been set.
bool is_set() const noexcept
{
return m_state.load(std::memory_order_acquire) == state::set;
}
/// \brief
/// Transition this event to the 'set' state if it is not already set.
///
/// If there was a coroutine awaiting the event then it will be resumed
/// inside this call.
void set()
{
const state oldState = m_state.exchange(state::set, std::memory_order_acq_rel);
if (oldState == state::not_set_consumer_waiting)
{
m_awaiter.resume();
}
}
/// \brief
/// Transition this event to the 'non set' state if it was in the set state.
void reset() noexcept
{
state oldState = state::set;
m_state.compare_exchange_strong(oldState, state::not_set, std::memory_order_relaxed);
}
/// \brief
/// Wait until the event becomes set.
///
/// If the event is already set then the awaiting coroutine will not be suspended
/// and will continue execution. If the event was not yet set then the coroutine
/// will be suspended and will be later resumed inside a subsequent call to set()
/// on the thread that calls set().
auto operator co_await() noexcept
{
class awaiter
{
public:
awaiter(single_consumer_event& event) : m_event(event) {}
bool await_ready() const noexcept
{
return m_event.is_set();
}
bool await_suspend(cppcoro::coroutine_handle<> awaiter)
{
m_event.m_awaiter = awaiter;
state oldState = state::not_set;
return m_event.m_state.compare_exchange_strong(
oldState,
state::not_set_consumer_waiting,
std::memory_order_release,
std::memory_order_acquire);
}
void await_resume() noexcept {}
private:
single_consumer_event& m_event;
};
return awaiter{ *this };
}
private:
enum class state
{
not_set,
not_set_consumer_waiting,
set
};
// TODO: Merge these two fields into a single std::atomic<std::uintptr_t>
// by encoding 'not_set' as 0 (nullptr), 'set' as 1 and
// 'not_set_consumer_waiting' as a coroutine handle pointer.
std::atomic<state> m_state;
cppcoro::coroutine_handle<> m_awaiter;
};
}
#endif
@@ -0,0 +1,246 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
#define CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/sequence_barrier.hpp>
#include <cppcoro/sequence_range.hpp>
namespace cppcoro
{
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_one_operation;
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_operation;
template<
typename SEQUENCE = std::size_t,
typename TRAITS = sequence_traits<SEQUENCE>>
class single_producer_sequencer
{
public:
using size_type = typename sequence_range<SEQUENCE, TRAITS>::size_type;
single_producer_sequencer(
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
std::size_t bufferSize,
SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
: m_consumerBarrier(consumerBarrier)
, m_bufferSize(bufferSize)
, m_nextToClaim(initialSequence + 1)
, m_producerBarrier(initialSequence)
{}
/// Claim a slot in the ring buffer asynchronously.
///
/// \return
/// Returns an operation that when awaited will suspend the coroutine until
/// a slot is available for writing in the ring buffer. The result of the
/// co_await expression will be the sequence number of the slot.
/// The caller must publish() the claimed sequence number once they have written to
/// the ring-buffer.
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
claim_one(SCHEDULER& scheduler) noexcept;
/// Claim one or more contiguous slots in the ring-buffer.
///
/// Use this method over many calls to claim_one() when you have multiple elements to
/// enqueue. This will claim as many slots as are available up to the specified count
/// but may claim as few as one slot if only one slot is available.
///
/// \param count
/// The maximum number of slots to claim.
///
/// \return
/// Returns an awaitable object that when awaited returns a sequence_range that contains
/// the range of sequence numbers that were claimed. Once you have written element values
/// to all of the claimed slots you must publish() the sequence range in order to make
/// the elements available to consumers.
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER> claim_up_to(
std::size_t count, SCHEDULER& scheduler) noexcept;
/// Publish the specified sequence number.
///
/// This also implies that all prior sequence numbers have already been published.
void publish(SEQUENCE sequence) noexcept
{
m_producerBarrier.publish(sequence);
}
/// Publish a contiguous range of sequence numbers.
///
/// You must have already published all prior sequence numbers.
///
/// This is equivalent to just publishing the last sequence number in the range.
void publish(const sequence_range<SEQUENCE, TRAITS>& sequences) noexcept
{
m_producerBarrier.publish(sequences.back());
}
/// Query what the last-published sequence number is.
///
/// You can assume that all prior sequence numbers are also published.
SEQUENCE last_published() const noexcept
{
return m_producerBarrier.last_published();
}
/// Asynchronously wait until the specified sequence number is published.
///
/// \param targetSequence
/// The sequence number to wait for.
///
/// \return
/// Returns an Awaitable type that, when awaited, will suspend the awaiting coroutine until the
/// specified sequence number has been published.
///
/// The result of the 'co_await barrier.wait_until_published(seq)' expression will be the
/// last-published sequence number, which is guaranteed to be at least 'seq' but may be some
/// subsequent sequence number if additional items were published while waiting for the
/// the requested sequence number to be published.
template<typename SCHEDULER>
[[nodiscard]]
auto wait_until_published(SEQUENCE targetSequence, SCHEDULER& scheduler) const noexcept
{
return m_producerBarrier.wait_until_published(targetSequence, scheduler);
}
private:
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class single_producer_sequencer_claim_operation;
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
friend class single_producer_sequencer_claim_one_operation;
#if CPPCORO_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
#endif
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
const std::size_t m_bufferSize;
alignas(CPPCORO_CPU_CACHE_LINE)
SEQUENCE m_nextToClaim;
sequence_barrier<SEQUENCE, TRAITS> m_producerBarrier;
#if CPPCORO_COMPILER_MSVC
# pragma warning(pop)
#endif
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_one_operation
{
public:
single_producer_sequencer_claim_one_operation(
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
SCHEDULER& scheduler) noexcept
: m_consumerWaitOperation(
sequencer.m_consumerBarrier,
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
scheduler)
, m_sequencer(sequencer)
{}
bool await_ready() const noexcept
{
return m_consumerWaitOperation.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
}
SEQUENCE await_resume() const noexcept
{
return m_sequencer.m_nextToClaim++;
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
};
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
class single_producer_sequencer_claim_operation
{
public:
explicit single_producer_sequencer_claim_operation(
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
std::size_t count,
SCHEDULER& scheduler) noexcept
: m_consumerWaitOperation(
sequencer.m_consumerBarrier,
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
scheduler)
, m_sequencer(sequencer)
, m_count(count)
{}
bool await_ready() const noexcept
{
return m_consumerWaitOperation.await_ready();
}
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
}
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
{
const SEQUENCE lastAvailableSequence =
static_cast<SEQUENCE>(m_consumerWaitOperation.await_resume() + m_sequencer.m_bufferSize);
const SEQUENCE begin = m_sequencer.m_nextToClaim;
const std::size_t availableCount = static_cast<std::size_t>(lastAvailableSequence - begin) + 1;
const std::size_t countToClaim = std::min(m_count, availableCount);
const SEQUENCE end = static_cast<SEQUENCE>(begin + countToClaim);
m_sequencer.m_nextToClaim = end;
return sequence_range<SEQUENCE, TRAITS>(begin, end);
}
private:
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
std::size_t m_count;
};
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
single_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
{
return single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
}
template<typename SEQUENCE, typename TRAITS>
template<typename SCHEDULER>
[[nodiscard]]
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
single_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
{
return single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, count, scheduler);
}
}
#endif
+116
View File
@@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
#define CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
#include <atomic>
#include <cstdint>
#include <memory>
#include <thread>
#include <vector>
#include <mutex>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
class static_thread_pool
{
public:
/// Initialise to a number of threads equal to the number of cores
/// on the current machine.
static_thread_pool();
/// Construct a thread pool with the specified number of threads.
///
/// \param threadCount
/// The number of threads in the pool that will be used to execute work.
explicit static_thread_pool(std::uint32_t threadCount);
~static_thread_pool();
class schedule_operation
{
public:
schedule_operation(static_thread_pool* tp) noexcept : m_threadPool(tp) {}
bool await_ready() noexcept { return false; }
void await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept;
void await_resume() noexcept {}
private:
friend class static_thread_pool;
static_thread_pool* m_threadPool;
cppcoro::coroutine_handle<> m_awaitingCoroutine;
schedule_operation* m_next;
};
std::uint32_t thread_count() const noexcept { return m_threadCount; }
[[nodiscard]]
schedule_operation schedule() noexcept { return schedule_operation{ this }; }
private:
friend class schedule_operation;
void run_worker_thread(std::uint32_t threadIndex) noexcept;
void shutdown();
void schedule_impl(schedule_operation* operation) noexcept;
void remote_enqueue(schedule_operation* operation) noexcept;
bool has_any_queued_work_for(std::uint32_t threadIndex) noexcept;
bool approx_has_any_queued_work_for(std::uint32_t threadIndex) const noexcept;
bool is_shutdown_requested() const noexcept;
void notify_intent_to_sleep(std::uint32_t threadIndex) noexcept;
void try_clear_intent_to_sleep(std::uint32_t threadIndex) noexcept;
schedule_operation* try_global_dequeue() noexcept;
/// Try to steal a task from another thread.
///
/// \return
/// A pointer to the operation that was stolen if one could be stolen
/// from another thread. Otherwise returns nullptr if none of the other
/// threads had any tasks that could be stolen.
schedule_operation* try_steal_from_other_thread(std::uint32_t thisThreadIndex) noexcept;
void wake_one_thread() noexcept;
class thread_state;
static thread_local thread_state* s_currentState;
static thread_local static_thread_pool* s_currentThreadPool;
const std::uint32_t m_threadCount;
const std::unique_ptr<thread_state[]> m_threadStates;
std::vector<std::thread> m_threads;
std::atomic<bool> m_stopRequested;
std::mutex m_globalQueueMutex;
std::atomic<schedule_operation*> m_globalQueueHead;
//alignas(std::hardware_destructive_interference_size)
std::atomic<schedule_operation*> m_globalQueueTail;
//alignas(std::hardware_destructive_interference_size)
std::atomic<std::uint32_t> m_sleepingThreadCount;
};
}
#endif
+50
View File
@@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_SYNC_WAIT_HPP_INCLUDED
#define CPPCORO_SYNC_WAIT_HPP_INCLUDED
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
#include <cppcoro/detail/sync_wait_task.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cstdint>
#include <atomic>
#include <span>
namespace cppcoro
{
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait();
return task.result();
}
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable, io_service& srv, std::chrono::system_clock::duration step)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait({ &srv, 1 }, step);
return task.result();
}
template<typename AWAITABLE>
auto sync_wait(AWAITABLE&& awaitable, std::span<io_service> srvs, std::chrono::system_clock::duration step)
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
{
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
detail::lightweight_manual_reset_event event;
task.start(event);
event.wait(srvs, step);
return task.result();
}
}
#endif
+481
View File
@@ -0,0 +1,481 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_TASK_HPP_INCLUDED
#define CPPCORO_TASK_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/broken_promise.hpp>
#include <cppcoro/detail/remove_rvalue_reference.hpp>
#include <atomic>
#include <exception>
#include <utility>
#include <type_traits>
#include <cstdint>
#include <cassert>
#include <cppcoro/coroutine.hpp>
namespace cppcoro
{
template<typename T> class task;
namespace detail
{
class task_promise_base
{
friend struct final_awaitable;
struct final_awaitable
{
bool await_ready() const noexcept { return false; }
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
template<typename PROMISE>
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<PROMISE> coro) noexcept
{
return coro.promise().m_continuation;
}
#else
// HACK: Need to add CPPCORO_NOINLINE to await_suspend() method
// to avoid MSVC 2017.8 from spilling some local variables in
// await_suspend() onto the coroutine frame in some cases.
// Without this, some tests in async_auto_reset_event_tests.cpp
// were crashing under x86 optimised builds.
template<typename PROMISE>
CPPCORO_NOINLINE
void await_suspend(cppcoro::coroutine_handle<PROMISE> coroutine) noexcept
{
task_promise_base& promise = coroutine.promise();
// Use 'release' memory semantics in case we finish before the
// awaiter can suspend so that the awaiting thread sees our
// writes to the resulting value.
// Use 'acquire' memory semantics in case the caller registered
// the continuation before we finished. Ensure we see their write
// to m_continuation.
if (promise.m_state.exchange(true, std::memory_order_acq_rel))
{
promise.m_continuation.resume();
}
}
#endif
void await_resume() noexcept {}
};
public:
task_promise_base() noexcept
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
: m_state(false)
#endif
{}
auto initial_suspend() noexcept
{
return cppcoro::suspend_always{};
}
auto final_suspend() noexcept
{
return final_awaitable{};
}
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
void set_continuation(cppcoro::coroutine_handle<> continuation) noexcept
{
m_continuation = continuation;
}
#else
bool try_set_continuation(cppcoro::coroutine_handle<> continuation)
{
m_continuation = continuation;
return !m_state.exchange(true, std::memory_order_acq_rel);
}
#endif
private:
cppcoro::coroutine_handle<> m_continuation;
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
// Initially false. Set to true when either a continuation is registered
// or when the coroutine has run to completion. Whichever operation
// successfully transitions from false->true got there first.
std::atomic<bool> m_state;
#endif
};
template<typename T>
class task_promise final : public task_promise_base
{
public:
task_promise() noexcept {}
~task_promise()
{
switch (m_resultType)
{
case result_type::value:
m_value.~T();
break;
case result_type::exception:
m_exception.~exception_ptr();
break;
default:
break;
}
}
task<T> get_return_object() noexcept;
void unhandled_exception() noexcept
{
::new (static_cast<void*>(std::addressof(m_exception))) std::exception_ptr(
std::current_exception());
m_resultType = result_type::exception;
}
template<
typename VALUE,
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
void return_value(VALUE&& value)
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
{
::new (static_cast<void*>(std::addressof(m_value))) T(std::forward<VALUE>(value));
m_resultType = result_type::value;
}
T& result() &
{
if (m_resultType == result_type::exception)
{
std::rethrow_exception(m_exception);
}
assert(m_resultType == result_type::value);
return m_value;
}
// HACK: Need to have co_await of task<int> return prvalue rather than
// rvalue-reference to work around an issue with MSVC where returning
// rvalue reference of a fundamental type from await_resume() will
// cause the value to be copied to a temporary. This breaks the
// sync_wait() implementation.
// See https://github.com/lewissbaker/cppcoro/issues/40#issuecomment-326864107
using rvalue_type = std::conditional_t<
std::is_arithmetic_v<T> || std::is_pointer_v<T>,
T,
T&&>;
rvalue_type result() &&
{
if (m_resultType == result_type::exception)
{
std::rethrow_exception(m_exception);
}
assert(m_resultType == result_type::value);
return std::move(m_value);
}
private:
enum class result_type { empty, value, exception };
result_type m_resultType = result_type::empty;
union
{
T m_value;
std::exception_ptr m_exception;
};
};
template<>
class task_promise<void> : public task_promise_base
{
public:
task_promise() noexcept = default;
task<void> get_return_object() noexcept;
void return_void() noexcept
{}
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
}
private:
std::exception_ptr m_exception;
};
template<typename T>
class task_promise<T&> : public task_promise_base
{
public:
task_promise() noexcept = default;
task<T&> get_return_object() noexcept;
void unhandled_exception() noexcept
{
m_exception = std::current_exception();
}
void return_value(T& value) noexcept
{
m_value = std::addressof(value);
}
T& result()
{
if (m_exception)
{
std::rethrow_exception(m_exception);
}
return *m_value;
}
private:
T* m_value = nullptr;
std::exception_ptr m_exception;
};
}
/// \brief
/// A task represents an operation that produces a result both lazily
/// and asynchronously.
///
/// When you call a coroutine that returns a task, the coroutine
/// simply captures any passed parameters and returns exeuction to the
/// caller. Execution of the coroutine body does not start until the
/// coroutine is first co_await'ed.
template<typename T = void>
class [[nodiscard]] task
{
public:
using promise_type = detail::task_promise<T>;
using value_type = T;
private:
struct awaitable_base
{
cppcoro::coroutine_handle<promise_type> m_coroutine;
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
: m_coroutine(coroutine)
{}
bool await_ready() const noexcept
{
return !m_coroutine || m_coroutine.done();
}
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
cppcoro::coroutine_handle<> await_suspend(
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
m_coroutine.promise().set_continuation(awaitingCoroutine);
return m_coroutine;
}
#else
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
{
// NOTE: We are using the bool-returning version of await_suspend() here
// to work around a potential stack-overflow issue if a coroutine
// awaits many synchronously-completing tasks in a loop.
//
// We first start the task by calling resume() and then conditionally
// attach the continuation if it has not already completed. This allows us
// to immediately resume the awaiting coroutine without increasing
// the stack depth, avoiding the stack-overflow problem. However, it has
// the down-side of requiring a std::atomic to arbitrate the race between
// the coroutine potentially completing on another thread concurrently
// with registering the continuation on this thread.
//
// We can eliminate the use of the std::atomic once we have access to
// coroutine_handle-returning await_suspend() on both MSVC and Clang
// as this will provide ability to suspend the awaiting coroutine and
// resume another coroutine with a guaranteed tail-call to resume().
m_coroutine.resume();
return m_coroutine.promise().try_set_continuation(awaitingCoroutine);
}
#endif
};
public:
task() noexcept
: m_coroutine(nullptr)
{}
explicit task(cppcoro::coroutine_handle<promise_type> coroutine)
: m_coroutine(coroutine)
{}
task(task&& t) noexcept
: m_coroutine(t.m_coroutine)
{
t.m_coroutine = nullptr;
}
/// Disable copy construction/assignment.
task(const task&) = delete;
task& operator=(const task&) = delete;
/// Frees resources used by this task.
~task()
{
if (m_coroutine)
{
m_coroutine.destroy();
}
}
task& operator=(task&& other) noexcept
{
if (std::addressof(other) != this)
{
if (m_coroutine)
{
m_coroutine.destroy();
}
m_coroutine = other.m_coroutine;
other.m_coroutine = nullptr;
}
return *this;
}
/// \brief
/// Query if the task result is complete.
///
/// Awaiting a task that is ready is guaranteed not to block/suspend.
bool is_ready() const noexcept
{
return !m_coroutine || m_coroutine.done();
}
auto operator co_await() const & noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return this->m_coroutine.promise().result();
}
};
return awaitable{ m_coroutine };
}
auto operator co_await() const && noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
decltype(auto) await_resume()
{
if (!this->m_coroutine)
{
throw broken_promise{};
}
return std::move(this->m_coroutine.promise()).result();
}
};
return awaitable{ m_coroutine };
}
/// \brief
/// Returns an awaitable that will await completion of the task without
/// attempting to retrieve the result.
auto when_ready() const noexcept
{
struct awaitable : awaitable_base
{
using awaitable_base::awaitable_base;
void await_resume() const noexcept {}
};
return awaitable{ m_coroutine };
}
private:
cppcoro::coroutine_handle<promise_type> m_coroutine;
};
namespace detail
{
template<typename T>
task<T> task_promise<T>::get_return_object() noexcept
{
return task<T>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
inline task<void> task_promise<void>::get_return_object() noexcept
{
return task<void>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
template<typename T>
task<T&> task_promise<T&>::get_return_object() noexcept
{
return task<T&>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
}
}
template<typename AWAITABLE>
auto make_task(AWAITABLE awaitable)
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
{
co_return co_await static_cast<AWAITABLE&&>(awaitable);
}
}
#endif
+91
View File
@@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WHEN_ALL_HPP_INCLUDED
#define CPPCORO_WHEN_ALL_HPP_INCLUDED
#include <cppcoro/when_all_ready.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <cppcoro/fmap.hpp>
#include <cppcoro/detail/unwrap_reference.hpp>
#include <tuple>
#include <functional>
#include <utility>
#include <vector>
#include <type_traits>
#include <cassert>
namespace cppcoro
{
//////////
// Variadic when_all()
template<
typename... AWAITABLES,
std::enable_if_t<
std::conjunction_v<is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>,
int> = 0>
[[nodiscard]] auto when_all(AWAITABLES&&... awaitables)
{
return fmap([](auto&& taskTuple)
{
return std::apply([](auto&&... tasks) {
return std::make_tuple(static_cast<decltype(tasks)>(tasks).non_void_result()...);
}, static_cast<decltype(taskTuple)>(taskTuple));
}, when_all_ready(std::forward<AWAITABLES>(awaitables)...));
}
//////////
// when_all() with vector of awaitable
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
[[nodiscard]]
auto when_all(std::vector<AWAITABLE> awaitables)
{
return fmap([](auto&& taskVector) {
for (auto& task : taskVector)
{
task.result();
}
}, when_all_ready(std::move(awaitables)));
}
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
[[nodiscard]]
auto when_all(std::vector<AWAITABLE> awaitables)
{
using result_t = std::conditional_t<
std::is_lvalue_reference_v<RESULT>,
std::reference_wrapper<std::remove_reference_t<RESULT>>,
std::remove_reference_t<RESULT>>;
return fmap([](auto&& taskVector) {
std::vector<result_t> results;
results.reserve(taskVector.size());
for (auto& task : taskVector)
{
if constexpr (std::is_rvalue_reference_v<decltype(taskVector)>)
{
results.emplace_back(std::move(task).result());
}
else
{
results.emplace_back(task.result());
}
}
return results;
}, when_all_ready(std::move(awaitables)));
}
}
#endif
+56
View File
@@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
#define CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
#include <cppcoro/config.hpp>
#include <cppcoro/awaitable_traits.hpp>
#include <cppcoro/is_awaitable.hpp>
#include <cppcoro/detail/when_all_ready_awaitable.hpp>
#include <cppcoro/detail/when_all_task.hpp>
#include <cppcoro/detail/unwrap_reference.hpp>
#include <tuple>
#include <utility>
#include <vector>
#include <type_traits>
namespace cppcoro
{
template<
typename... AWAITABLES,
std::enable_if_t<std::conjunction_v<
is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>, int> = 0>
[[nodiscard]]
CPPCORO_FORCE_INLINE auto when_all_ready(AWAITABLES&&... awaitables)
{
return detail::when_all_ready_awaitable<std::tuple<detail::when_all_task<
typename awaitable_traits<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>::await_result_t>...>>(
std::make_tuple(detail::make_when_all_task(std::forward<AWAITABLES>(awaitables))...));
}
// TODO: Generalise this from vector<AWAITABLE> to arbitrary sequence of awaitable.
template<
typename AWAITABLE,
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t>
[[nodiscard]] auto when_all_ready(std::vector<AWAITABLE> awaitables)
{
std::vector<detail::when_all_task<RESULT>> tasks;
tasks.reserve(awaitables.size());
for (auto& awaitable : awaitables)
{
tasks.emplace_back(detail::make_when_all_task(std::move(awaitable)));
}
return detail::when_all_ready_awaitable<std::vector<detail::when_all_task<RESULT>>>(
std::move(tasks));
}
}
#endif
+71
View File
@@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WRITABLE_FILE_HPP_INCLUDED
#define CPPCORO_WRITABLE_FILE_HPP_INCLUDED
#include <cppcoro/file.hpp>
#include <cppcoro/file_write_operation.hpp>
#include <cppcoro/cancellation_token.hpp>
namespace cppcoro
{
class writable_file : virtual public file
{
public:
/// Set the size of the file.
///
/// \param fileSize
/// The new size of the file in bytes.
void set_size(std::uint64_t fileSize);
/// Write some data to the file.
///
/// Writes \a byteCount bytes from the file starting at \a offset
/// into the specified \a buffer.
///
/// \param offset
/// The offset within the file to start writing from.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the offset must be a multiple of the file-system's sector size.
///
/// \param buffer
/// The buffer containing the data to be written to the file.
/// If the file has been opened using file_buffering_mode::unbuffered
/// then the address of the start of the buffer must be a multiple of
/// the file-system's sector size.
///
/// \param byteCount
/// The number of bytes to write to the file.
/// If the file has been opeend using file_buffering_mode::unbuffered
/// then the byteCount must be a multiple of the file-system's sector size.
///
/// \param ct
/// An optional cancellation_token that can be used to cancel the
/// write operation before it completes.
///
/// \return
/// An object that represents the write operation.
/// This object must be co_await'ed to start the write operation.
[[nodiscard]]
file_write_operation write(
std::uint64_t offset,
const void* buffer,
std::size_t byteCount) noexcept;
[[nodiscard]]
file_write_operation_cancellable write(
std::uint64_t offset,
const void* buffer,
std::size_t byteCount,
cancellation_token ct) noexcept;
protected:
using file::file;
};
}
#endif
+65
View File
@@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
#define CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
#include <cppcoro/writable_file.hpp>
#include <cppcoro/file_share_mode.hpp>
#include <cppcoro/file_buffering_mode.hpp>
#include <cppcoro/file_open_mode.hpp>
#include <cppcoro/filesystem.hpp>
namespace cppcoro
{
class write_only_file : public writable_file
{
public:
/// Open a file for write-only access.
///
/// \param ioContext
/// The I/O context to use when dispatching I/O completion events.
/// When asynchronous write operations on this file complete the
/// completion events will be dispatched to an I/O thread associated
/// with the I/O context.
///
/// \param pathMode
/// Path of the file to open.
///
/// \param openMode
/// Specifies how the file should be opened and how to handle cases
/// when the file exists or doesn't exist.
///
/// \param shareMode
/// Specifies the access to be allowed on the file concurrently with this file access.
///
/// \param bufferingMode
/// Specifies the modes/hints to provide to the OS that affects the behaviour
/// of its file buffering.
///
/// \return
/// An object that can be used to write to the file.
///
/// \throw std::system_error
/// If the file could not be opened for write.
[[nodiscard]]
static write_only_file open(
io_service& ioService,
const cppcoro::filesystem::path& path,
file_open_mode openMode = file_open_mode::create_or_open,
file_share_mode shareMode = file_share_mode::none,
file_buffering_mode bufferingMode = file_buffering_mode::default_);
protected:
#if CPPCORO_OS_WINNT
write_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
#endif
};
}
#endif
+178
View File
@@ -0,0 +1,178 @@
set(includes
awaitable_traits.hpp
is_awaitable.hpp
async_auto_reset_event.hpp
async_manual_reset_event.hpp
async_generator.hpp
async_mutex.hpp
async_latch.hpp
async_scope.hpp
broken_promise.hpp
cancellation_registration.hpp
cancellation_source.hpp
cancellation_token.hpp
task.hpp
sequence_barrier.hpp
sequence_traits.hpp
single_producer_sequencer.hpp
multi_producer_sequencer.hpp
shared_task.hpp
shared_task.hpp
single_consumer_event.hpp
single_consumer_async_auto_reset_event.hpp
sync_wait.hpp
task.hpp
io_service.hpp
config.hpp
on_scope_exit.hpp
file_share_mode.hpp
file_open_mode.hpp
file_buffering_mode.hpp
file.hpp
fmap.hpp
when_all.hpp
when_all_ready.hpp
resume_on.hpp
schedule_on.hpp
generator.hpp
readable_file.hpp
recursive_generator.hpp
writable_file.hpp
read_only_file.hpp
write_only_file.hpp
read_write_file.hpp
file_read_operation.hpp
file_write_operation.hpp
static_thread_pool.hpp
)
list(TRANSFORM includes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/")
set(netIncludes
ip_address.hpp
ip_endpoint.hpp
ipv4_address.hpp
ipv4_endpoint.hpp
ipv6_address.hpp
ipv6_endpoint.hpp
socket.hpp
)
list(TRANSFORM netIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/net/")
set(detailIncludes
void_value.hpp
when_all_ready_awaitable.hpp
when_all_counter.hpp
when_all_task.hpp
get_awaiter.hpp
is_awaiter.hpp
any.hpp
sync_wait_task.hpp
unwrap_reference.hpp
lightweight_manual_reset_event.hpp
)
list(TRANSFORM detailIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/detail/")
set(privateHeaders
cancellation_state.hpp
socket_helpers.hpp
auto_reset_event.hpp
spin_wait.hpp
spin_mutex.hpp
)
set(sources
async_auto_reset_event.cpp
async_manual_reset_event.cpp
async_mutex.cpp
cancellation_state.cpp
cancellation_token.cpp
cancellation_source.cpp
cancellation_registration.cpp
lightweight_manual_reset_event.cpp
ip_address.cpp
ip_endpoint.cpp
ipv4_address.cpp
ipv4_endpoint.cpp
ipv6_address.cpp
ipv6_endpoint.cpp
static_thread_pool.cpp
auto_reset_event.cpp
spin_wait.cpp
spin_mutex.cpp
)
if(WIN32)
set(win32DetailIncludes
win32.hpp
win32_overlapped_operation.hpp
)
list(TRANSFORM win32DetailIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/detail/")
list(APPEND detailIncludes ${win32DetailIncludes})
set(win32NetIncludes
socket.hpp
socket_accept_operation.hpp
socket_connect_operation.hpp
socket_disconnect_operation.hpp
socket_recv_operation.hpp
socket_recv_from_operation.hpp
socket_send_operation.hpp
socket_send_to_operation.hpp
)
list(TRANSFORM win32NetIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/net/")
list(APPEND netIncludes ${win32NetIncludes})
set(win32Sources
win32.cpp
io_service.cpp
file.cpp
readable_file.cpp
writable_file.cpp
read_only_file.cpp
write_only_file.cpp
read_write_file.cpp
file_read_operation.cpp
file_write_operation.cpp
socket_helpers.cpp
socket.cpp
socket_accept_operation.cpp
socket_connect_operation.cpp
socket_disconnect_operation.cpp
socket_send_operation.cpp
socket_send_to_operation.cpp
socket_recv_operation.cpp
socket_recv_from_operation.cpp
)
list(APPEND sources ${win32Sources})
list(APPEND libraries Ws2_32 Mswsock Synchronization)
list(APPEND compile_options /EHsc)
if("${MSVC_VERSION}" VERSION_GREATER_EQUAL 1900)
# TODO remove this when experimental/non-experimental include are fixed
list(APPEND compile_definition _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING=1)
endif()
endif()
add_library(cppcoro
${includes}
${netIncludes}
${detailIncludes}
${privateHeaders}
${sources}
)
target_include_directories(cppcoro PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_definitions(cppcoro PUBLIC ${compile_definition})
target_compile_options(cppcoro PUBLIC ${compile_options})
#find_package(Coroutines COMPONENTS Experimental Final REQUIRED)
#target_link_libraries(cppcoro PUBLIC std::coroutines ${libraries})
install(TARGETS cppcoro EXPORT cppcoroTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin)
+285
View File
@@ -0,0 +1,285 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/async_auto_reset_event.hpp>
#include <cppcoro/config.hpp>
#include <cassert>
#include <algorithm>
namespace
{
namespace local
{
// Some helpers for manipulating the 'm_state' value.
constexpr std::uint64_t set_increment = 1;
constexpr std::uint64_t waiter_increment = std::uint64_t(1) << 32;
constexpr std::uint32_t get_set_count(std::uint64_t state)
{
return static_cast<std::uint32_t>(state);
}
constexpr std::uint32_t get_waiter_count(std::uint64_t state)
{
return static_cast<std::uint32_t>(state >> 32);
}
constexpr std::uint32_t get_resumable_waiter_count(std::uint64_t state)
{
return std::min(get_set_count(state), get_waiter_count(state));
}
}
}
cppcoro::async_auto_reset_event::async_auto_reset_event(bool initiallySet) noexcept
: m_state(initiallySet ? local::set_increment : 0)
, m_newWaiters(nullptr)
, m_waiters(nullptr)
{
}
cppcoro::async_auto_reset_event::~async_auto_reset_event()
{
assert(m_newWaiters.load(std::memory_order_relaxed) == nullptr);
assert(m_waiters == nullptr);
}
cppcoro::async_auto_reset_event_operation
cppcoro::async_auto_reset_event::operator co_await() const noexcept
{
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
if (local::get_set_count(oldState) > local::get_waiter_count(oldState))
{
// Try to synchronously acquire the event.
if (m_state.compare_exchange_strong(
oldState,
oldState - local::set_increment,
std::memory_order_acquire,
std::memory_order_relaxed))
{
// Acquired the event, return an operation object that
// won't suspend.
return async_auto_reset_event_operation{};
}
}
return async_auto_reset_event_operation{ *this };
}
void cppcoro::async_auto_reset_event::set() noexcept
{
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
do
{
if (local::get_set_count(oldState) > local::get_waiter_count(oldState))
{
// Already set.
return;
}
// Increment the set-count
} while (!m_state.compare_exchange_weak(
oldState,
oldState + local::set_increment,
std::memory_order_acq_rel,
std::memory_order_acquire));
// Did we transition from non-zero waiters and zero set-count
// to non-zero set-count?
// If so then we acquired the lock and are responsible for resuming waiters.
if (oldState != 0 && local::get_set_count(oldState) == 0)
{
// We acquired the lock.
resume_waiters(oldState + local::set_increment);
}
}
void cppcoro::async_auto_reset_event::reset() noexcept
{
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
while (local::get_set_count(oldState) > local::get_waiter_count(oldState))
{
if (m_state.compare_exchange_weak(
oldState,
oldState - local::set_increment,
std::memory_order_relaxed))
{
// Successfully reset.
return;
}
}
// Not set. Nothing to do.
}
void cppcoro::async_auto_reset_event::resume_waiters(
std::uint64_t initialState) const noexcept
{
async_auto_reset_event_operation* waitersToResumeList = nullptr;
async_auto_reset_event_operation** waitersToResumeListEnd = &waitersToResumeList;
std::uint32_t waiterCountToResume = local::get_resumable_waiter_count(initialState);
assert(waiterCountToResume > 0);
do
{
// Dequeue 'waiterCountToResume' from m_waiters/m_newWaiters and
// push them onto 'waitersToResumeList'.
for (std::uint32_t i = 0; i < waiterCountToResume; ++i)
{
if (m_waiters == nullptr)
{
// We've run out of of waiters that we can consume without synchronisation
// Dequeue the list of new waiters atomically.
auto* newWaiters = m_newWaiters.exchange(nullptr, std::memory_order_acquire);
// There should always be enough waiters in the list as
// the waiters are queued before the waiter-count is incremented.
assert(newWaiters != nullptr);
CPPCORO_ASSUME(newWaiters != nullptr);
// Reverse order of new waiters so they are resumed in FIFO.
// This ensures fairness.
//
// The alternative would be to not reverse the list and instead
// resume waiters in the reverse order they were queued in.
// This might result in better cache locality (most recently
// suspended coroutine might still be in cache).
// It should still provide a bounded wait time as well since we
// are guaranteed to process all waiters in this list before
// looking at any waiters newly queued after this point.
// Something to consider.
do
{
auto* next = newWaiters->m_next;
newWaiters->m_next = m_waiters;
m_waiters = newWaiters;
newWaiters = next;
} while (newWaiters != nullptr);
}
assert(m_waiters != nullptr);
// Pop the next waiter off the list
auto* waiterToResume = m_waiters;
m_waiters = m_waiters->m_next;
// Push it onto the end of the list of waiters to resume
waiterToResume->m_next = nullptr;
*waitersToResumeListEnd = waiterToResume;
waitersToResumeListEnd = &waiterToResume->m_next;
}
// We've now removed 'waiterCountToResume' waiters from the list
// so we can now decrement both the waiter and set count.
//
// However, there might have been more waiters or more calls to
// set() since we last checked so we need to go around again if
// there are still waiters that are ready to resume after decrementing
// both the 'waiter count' and 'set count' by 'waiterCountToResume'.
const std::uint64_t delta =
std::uint64_t(waiterCountToResume) |
std::uint64_t(waiterCountToResume) << 32;
// Needs to be 'release' as we're releasing the lock and anyone that
// subsequently acquires the lock needs to see our prior writes to
// m_waiters.
// Needs to be 'acquire' in the case that new waiters were added so
// that we see their prior writes to 'm_newWaiters'.
const std::uint64_t newState =
m_state.fetch_sub(delta, std::memory_order_acq_rel) - delta;
waiterCountToResume = local::get_resumable_waiter_count(newState);
} while (waiterCountToResume > 0);
// Now resume all of the waiters we've dequeued.
// There should be at least one.
assert(waitersToResumeList != nullptr);
CPPCORO_ASSUME(waitersToResumeList != nullptr);
do
{
auto* const waiter = waitersToResumeList;
// Read 'next' before resuming since resuming the waiter is
// likely to destroy the waiter object.
auto* const next = waitersToResumeList->m_next;
// Decrement reference count and see if we decremented the last
// reference and if so then we are responsible for resuming.
// If not, then await_suspend() is responsible for resuming by
// returning 'false' and not suspending.
if (waiter->m_refCount.fetch_sub(1, std::memory_order_release) == 1)
{
waiter->m_awaiter.resume();
}
waitersToResumeList = next;
} while (waitersToResumeList != nullptr);
}
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation() noexcept
: m_event(nullptr)
{}
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation(
const async_auto_reset_event& event) noexcept
: m_event(&event)
, m_refCount(2)
{}
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation(
const async_auto_reset_event_operation& other) noexcept
: m_event(other.m_event)
, m_refCount(2)
{}
bool cppcoro::async_auto_reset_event_operation::await_suspend(
cppcoro::coroutine_handle<> awaiter) noexcept
{
m_awaiter = awaiter;
// Queue the waiter to the m_newWaiters list.
async_auto_reset_event_operation* head = m_event->m_newWaiters.load(std::memory_order_relaxed);
do
{
m_next = head;
} while (!m_event->m_newWaiters.compare_exchange_weak(
head,
this,
std::memory_order_release,
std::memory_order_relaxed));
// Increment the waiter count.
// Needs to be 'release' so that our prior write to m_newWaiters is
// visible to anyone that acquires the lock.
// Needs to be 'acquire' in case we acquired the lock so we can see
// others' writes to m_newWaiters and writes prior to set() calls.
const std::uint64_t oldState =
m_event->m_state.fetch_add(local::waiter_increment, std::memory_order_acq_rel);
if (oldState != 0 && local::get_waiter_count(oldState) == 0)
{
// We transitioned from non-zero set and zero waiters to
// non-zero set and non-zero waiters, so we acquired the lock
// and thus responsibility for resuming waiters.
m_event->resume_waiters(oldState + local::waiter_increment);
}
// Decrement the ref-count to indicate that this waiter is now safe
// to resume. We don't want it to resume while we're still accessing the
// m_event object as resuming it might cause the event object to be
// destructed.
//
// Need 'acquire' semantics here in the case that another thread has
// concurrently dequeued us and scheduled us for resumption by decrementing
// the ref-count with 'release' semantics so that we see the writes prior
// to the 'set()' call that released this waiter.
return m_refCount.fetch_sub(1, std::memory_order_acquire) != 1;
}
+99
View File
@@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/async_manual_reset_event.hpp>
#include <cppcoro/config.hpp>
#include <cassert>
cppcoro::async_manual_reset_event::async_manual_reset_event(bool initiallySet) noexcept
: m_state(initiallySet ? static_cast<void*>(this) : nullptr)
{}
cppcoro::async_manual_reset_event::~async_manual_reset_event()
{
// There should be no coroutines still awaiting the event.
assert(
m_state.load(std::memory_order_relaxed) == nullptr ||
m_state.load(std::memory_order_relaxed) == static_cast<void*>(this));
}
bool cppcoro::async_manual_reset_event::is_set() const noexcept
{
return m_state.load(std::memory_order_acquire) == static_cast<const void*>(this);
}
cppcoro::async_manual_reset_event_operation
cppcoro::async_manual_reset_event::operator co_await() const noexcept
{
return async_manual_reset_event_operation{ *this };
}
void cppcoro::async_manual_reset_event::set() noexcept
{
void* const setState = static_cast<void*>(this);
// Needs 'release' semantics so that prior writes are visible to event awaiters
// that synchronise either via 'is_set()' or 'operator co_await()'.
// Needs 'acquire' semantics in case there are any waiters so that we see
// prior writes to the waiting coroutine's state and to the contents of
// the queued async_manual_reset_event_operation objects.
void* oldState = m_state.exchange(setState, std::memory_order_acq_rel);
if (oldState != setState)
{
auto* current = static_cast<async_manual_reset_event_operation*>(oldState);
while (current != nullptr)
{
auto* next = current->m_next;
current->m_awaiter.resume();
current = next;
}
}
}
void cppcoro::async_manual_reset_event::reset() noexcept
{
void* oldState = static_cast<void*>(this);
m_state.compare_exchange_strong(oldState, nullptr, std::memory_order_relaxed);
}
cppcoro::async_manual_reset_event_operation::async_manual_reset_event_operation(
const async_manual_reset_event& event) noexcept
: m_event(event)
{
}
bool cppcoro::async_manual_reset_event_operation::await_ready() const noexcept
{
return m_event.is_set();
}
bool cppcoro::async_manual_reset_event_operation::await_suspend(
cppcoro::coroutine_handle<> awaiter) noexcept
{
m_awaiter = awaiter;
const void* const setState = static_cast<const void*>(&m_event);
void* oldState = m_event.m_state.load(std::memory_order_acquire);
do
{
if (oldState == setState)
{
// State is now 'set' no need to suspend.
return false;
}
m_next = static_cast<async_manual_reset_event_operation*>(oldState);
} while (!m_event.m_state.compare_exchange_weak(
oldState,
static_cast<void*>(this),
std::memory_order_release,
std::memory_order_acquire));
// Successfully queued this waiter to the list.
return true;
}
+122
View File
@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/async_mutex.hpp>
#include <cassert>
cppcoro::async_mutex::async_mutex() noexcept
: m_state(not_locked)
, m_waiters(nullptr)
{}
cppcoro::async_mutex::~async_mutex()
{
[[maybe_unused]] auto state = m_state.load(std::memory_order_relaxed);
assert(state == not_locked || state == locked_no_waiters);
assert(m_waiters == nullptr);
}
bool cppcoro::async_mutex::try_lock() noexcept
{
// Try to atomically transition from nullptr (not-locked) -> this (locked-no-waiters).
auto oldState = not_locked;
return m_state.compare_exchange_strong(
oldState,
locked_no_waiters,
std::memory_order_acquire,
std::memory_order_relaxed);
}
cppcoro::async_mutex_lock_operation cppcoro::async_mutex::lock_async() noexcept
{
return async_mutex_lock_operation{ *this };
}
cppcoro::async_mutex_scoped_lock_operation cppcoro::async_mutex::scoped_lock_async() noexcept
{
return async_mutex_scoped_lock_operation{ *this };
}
void cppcoro::async_mutex::unlock()
{
assert(m_state.load(std::memory_order_relaxed) != not_locked);
async_mutex_lock_operation* waitersHead = m_waiters;
if (waitersHead == nullptr)
{
auto oldState = locked_no_waiters;
const bool releasedLock = m_state.compare_exchange_strong(
oldState,
not_locked,
std::memory_order_release,
std::memory_order_relaxed);
if (releasedLock)
{
return;
}
// At least one new waiter.
// Acquire the list of new waiter operations atomically.
oldState = m_state.exchange(locked_no_waiters, std::memory_order_acquire);
assert(oldState != locked_no_waiters && oldState != not_locked);
// Transfer the list to m_waiters, reversing the list in the process so
// that the head of the list is the first to be resumed.
auto* next = reinterpret_cast<async_mutex_lock_operation*>(oldState);
do
{
auto* temp = next->m_next;
next->m_next = waitersHead;
waitersHead = next;
next = temp;
} while (next != nullptr);
}
assert(waitersHead != nullptr);
m_waiters = waitersHead->m_next;
// Resume the waiter.
// This will pass the ownership of the lock on to that operation/coroutine.
waitersHead->m_awaiter.resume();
}
bool cppcoro::async_mutex_lock_operation::await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept
{
m_awaiter = awaiter;
std::uintptr_t oldState = m_mutex.m_state.load(std::memory_order_acquire);
while (true)
{
if (oldState == async_mutex::not_locked)
{
if (m_mutex.m_state.compare_exchange_weak(
oldState,
async_mutex::locked_no_waiters,
std::memory_order_acquire,
std::memory_order_relaxed))
{
// Acquired lock, don't suspend.
return false;
}
}
else
{
// Try to push this operation onto the head of the waiter stack.
m_next = reinterpret_cast<async_mutex_lock_operation*>(oldState);
if (m_mutex.m_state.compare_exchange_weak(
oldState,
reinterpret_cast<std::uintptr_t>(this),
std::memory_order_release,
std::memory_order_relaxed))
{
// Queued operation to waiters list, suspend now.
return true;
}
}
}
}
+97
View File
@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include "auto_reset_event.hpp"
#if CPPCORO_OS_WINNT
# define WIN32_LEAN_AND_MEAN
# include <Windows.h>
# include <system_error>
#endif
namespace cppcoro
{
#if CPPCORO_OS_WINNT
auto_reset_event::auto_reset_event(bool initiallySet)
: m_event(::CreateEventW(NULL, FALSE, initiallySet ? TRUE : FALSE, NULL))
{
if (m_event.handle() == NULL)
{
DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"auto_reset_event: CreateEvent failed"
};
}
}
auto_reset_event::~auto_reset_event()
{
}
void auto_reset_event::set()
{
BOOL ok =::SetEvent(m_event.handle());
if (!ok)
{
DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"auto_reset_event: SetEvent failed"
};
}
}
void auto_reset_event::wait()
{
DWORD result = ::WaitForSingleObjectEx(m_event.handle(), INFINITE, FALSE);
if (result != WAIT_OBJECT_0)
{
DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"auto_reset_event: WaitForSingleObjectEx failed"
};
}
}
#else
auto_reset_event::auto_reset_event(bool initiallySet)
: m_isSet(initiallySet)
{}
auto_reset_event::~auto_reset_event()
{}
void auto_reset_event::set()
{
std::unique_lock lock{ m_mutex };
if (!m_isSet)
{
m_isSet = true;
m_cv.notify_one();
}
}
void auto_reset_event::wait()
{
std::unique_lock lock{ m_mutex };
while (!m_isSet)
{
m_cv.wait(lock);
}
m_isSet = false;
}
#endif
}
+44
View File
@@ -0,0 +1,44 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED
#define CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED
#include <cppcoro/config.hpp>
#if CPPCORO_OS_WINNT
# include <cppcoro/detail/win32.hpp>
#else
# include <mutex>
# include <condition_variable>
#endif
namespace cppcoro
{
class auto_reset_event
{
public:
auto_reset_event(bool initiallySet = false);
~auto_reset_event();
void set();
void wait();
private:
#if CPPCORO_OS_WINNT
cppcoro::detail::win32::safe_handle m_event;
#else
std::mutex m_mutex;
std::condition_variable m_cv;
bool m_isSet;
#endif
};
}
#endif
+182
View File
@@ -0,0 +1,182 @@
###############################################################################
# Copyright Lewis Baker
# Licenced under MIT license. See LICENSE.txt for details.
###############################################################################
import cake.path
from cake.tools import compiler, script, env, project, variant
includes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', [
'awaitable_traits.hpp',
'is_awaitable.hpp',
'async_auto_reset_event.hpp',
'async_manual_reset_event.hpp',
'async_generator.hpp',
'async_mutex.hpp',
'async_latch.hpp',
'async_scope.hpp',
'broken_promise.hpp',
'cancellation_registration.hpp',
'cancellation_source.hpp',
'cancellation_token.hpp',
'task.hpp',
'sequence_barrier.hpp',
'sequence_traits.hpp',
'single_producer_sequencer.hpp',
'multi_producer_sequencer.hpp',
'shared_task.hpp',
'single_consumer_event.hpp',
'single_consumer_async_auto_reset_event.hpp',
'sync_wait.hpp',
'task.hpp',
'io_service.hpp',
'config.hpp',
'on_scope_exit.hpp',
'file_share_mode.hpp',
'file_open_mode.hpp',
'file_buffering_mode.hpp',
'file.hpp',
'fmap.hpp',
'when_all.hpp',
'when_all_ready.hpp',
'resume_on.hpp',
'schedule_on.hpp',
'generator.hpp',
'readable_file.hpp',
'recursive_generator.hpp',
'writable_file.hpp',
'read_only_file.hpp',
'write_only_file.hpp',
'read_write_file.hpp',
'file_read_operation.hpp',
'file_write_operation.hpp',
'static_thread_pool.hpp',
])
netIncludes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'net', [
'ip_address.hpp',
'ip_endpoint.hpp',
'ipv4_address.hpp',
'ipv4_endpoint.hpp',
'ipv6_address.hpp',
'ipv6_endpoint.hpp',
'socket.hpp',
])
detailIncludes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'detail', [
'void_value.hpp',
'when_all_ready_awaitable.hpp',
'when_all_counter.hpp',
'when_all_task.hpp',
'get_awaiter.hpp',
'is_awaiter.hpp',
'any.hpp',
'sync_wait_task.hpp',
'unwrap_reference.hpp',
'lightweight_manual_reset_event.hpp',
])
privateHeaders = script.cwd([
'cancellation_state.hpp',
'socket_helpers.hpp',
'auto_reset_event.hpp',
'spin_wait.hpp',
'spin_mutex.hpp',
])
sources = script.cwd([
'async_auto_reset_event.cpp',
'async_manual_reset_event.cpp',
'async_mutex.cpp',
'cancellation_state.cpp',
'cancellation_token.cpp',
'cancellation_source.cpp',
'cancellation_registration.cpp',
'lightweight_manual_reset_event.cpp',
'ip_address.cpp',
'ip_endpoint.cpp',
'ipv4_address.cpp',
'ipv4_endpoint.cpp',
'ipv6_address.cpp',
'ipv6_endpoint.cpp',
'static_thread_pool.cpp',
'auto_reset_event.cpp',
'spin_wait.cpp',
'spin_mutex.cpp',
])
extras = script.cwd([
'build.cake',
'use.cake',
])
if variant.platform == "windows":
detailIncludes.extend(cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'detail', [
'win32.hpp',
'win32_overlapped_operation.hpp',
]))
netIncludes.extend(cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'net', [
'socket.hpp',
'socket_accept_operation.hpp',
'socket_connect_operation.hpp',
'socket_disconnect_operation.hpp',
'socket_recv_operation.hpp',
'socket_recv_from_operation.hpp',
'socket_send_operation.hpp',
'socket_send_to_operation.hpp',
]))
sources.extend(script.cwd([
'win32.cpp',
'io_service.cpp',
'file.cpp',
'readable_file.cpp',
'writable_file.cpp',
'read_only_file.cpp',
'write_only_file.cpp',
'read_write_file.cpp',
'file_read_operation.cpp',
'file_write_operation.cpp',
'socket_helpers.cpp',
'socket.cpp',
'socket_accept_operation.cpp',
'socket_connect_operation.cpp',
'socket_disconnect_operation.cpp',
'socket_send_operation.cpp',
'socket_send_to_operation.cpp',
'socket_recv_operation.cpp',
'socket_recv_from_operation.cpp',
]))
buildDir = env.expand('${CPPCORO_BUILD}')
compiler.addIncludePath(env.expand('${CPPCORO}/include'))
objects = compiler.objects(
targetDir=env.expand('${CPPCORO_BUILD}/obj'),
sources=sources,
)
lib = compiler.library(
target=env.expand('${CPPCORO_LIB}/cppcoro'),
sources=objects,
)
vcproj = project.project(
target=env.expand('${CPPCORO_PROJECT}/cppcoro'),
items={
'Include': {
'Detail': detailIncludes,
'Net': netIncludes,
'': includes,
},
'Source': sources + privateHeaders,
'': extras
},
output=lib,
)
script.setResult(
project=vcproj,
library=lib,
)
+41
View File
@@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/cancellation_registration.hpp>
#include "cancellation_state.hpp"
#include <cassert>
cppcoro::cancellation_registration::~cancellation_registration()
{
if (m_state != nullptr)
{
m_state->deregister_callback(this);
m_state->release_token_ref();
}
}
void cppcoro::cancellation_registration::register_callback(cancellation_token&& token)
{
auto* state = token.m_state;
if (state != nullptr && state->can_be_cancelled())
{
m_state = state;
if (state->try_register_callback(this))
{
token.m_state = nullptr;
}
else
{
m_state = nullptr;
m_callback();
}
}
else
{
m_state = nullptr;
}
}
+97
View File
@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/cancellation_source.hpp>
#include "cancellation_state.hpp"
#include <cassert>
cppcoro::cancellation_source::cancellation_source()
: m_state(detail::cancellation_state::create())
{
}
cppcoro::cancellation_source::cancellation_source(const cancellation_source& other) noexcept
: m_state(other.m_state)
{
if (m_state != nullptr)
{
m_state->add_source_ref();
}
}
cppcoro::cancellation_source::cancellation_source(cancellation_source&& other) noexcept
: m_state(other.m_state)
{
other.m_state = nullptr;
}
cppcoro::cancellation_source::~cancellation_source()
{
if (m_state != nullptr)
{
m_state->release_source_ref();
}
}
cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(const cancellation_source& other) noexcept
{
if (m_state != other.m_state)
{
if (m_state != nullptr)
{
m_state->release_source_ref();
}
m_state = other.m_state;
if (m_state != nullptr)
{
m_state->add_source_ref();
}
}
return *this;
}
cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(cancellation_source&& other) noexcept
{
if (this != &other)
{
if (m_state != nullptr)
{
m_state->release_source_ref();
}
m_state = other.m_state;
other.m_state = nullptr;
}
return *this;
}
bool cppcoro::cancellation_source::can_be_cancelled() const noexcept
{
return m_state != nullptr;
}
cppcoro::cancellation_token cppcoro::cancellation_source::token() const noexcept
{
return cancellation_token(m_state);
}
void cppcoro::cancellation_source::request_cancellation()
{
if (m_state != nullptr)
{
m_state->request_cancellation();
}
}
bool cppcoro::cancellation_source::is_cancellation_requested() const noexcept
{
return m_state != nullptr && m_state->is_cancellation_requested();
}
+624
View File
@@ -0,0 +1,624 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include "cancellation_state.hpp"
#include "cppcoro/config.hpp"
#include <cppcoro/cancellation_registration.hpp>
#include <cassert>
#include <cstdlib>
namespace cppcoro
{
namespace detail
{
struct cancellation_registration_list_chunk
{
static cancellation_registration_list_chunk* allocate(std::uint32_t entryCount);
static void free(cancellation_registration_list_chunk* chunk) noexcept;
std::atomic<cancellation_registration_list_chunk*> m_nextChunk;
cancellation_registration_list_chunk* m_prevChunk;
std::atomic<std::int32_t> m_approximateFreeCount;
std::uint32_t m_entryCount;
std::atomic<cancellation_registration*> m_entries[1];
};
struct cancellation_registration_list
{
static cancellation_registration_list* allocate();
static void free(cancellation_registration_list* bucket) noexcept;
std::atomic<cancellation_registration_list_chunk*> m_approximateTail;
cancellation_registration_list_chunk m_headChunk;
};
struct cancellation_registration_result
{
cancellation_registration_result(
cancellation_registration_list_chunk* chunk,
std::uint32_t entryIndex)
: m_chunk(chunk)
, m_entryIndex(entryIndex)
{}
cancellation_registration_list_chunk* m_chunk;
std::uint32_t m_entryIndex;
};
struct cancellation_registration_state
{
static cancellation_registration_state* allocate();
static void free(cancellation_registration_state* list) noexcept;
cancellation_registration_result add_registration(
cppcoro::cancellation_registration* registration);
std::thread::id m_notificationThreadId;
// Store N separate lists and randomly apportion threads to a given
// list to reduce chance of contention.
std::uint32_t m_listCount;
std::atomic<cancellation_registration_list*> m_lists[1];
};
}
}
cppcoro::detail::cancellation_registration_list_chunk*
cppcoro::detail::cancellation_registration_list_chunk::allocate(std::uint32_t entryCount)
{
auto* chunk = static_cast<cancellation_registration_list_chunk*>(std::malloc(
sizeof(cancellation_registration_list_chunk) +
(entryCount - 1) * sizeof(cancellation_registration_list_chunk::m_entries[0])));
if (chunk == nullptr)
{
throw std::bad_alloc{};
}
::new (&chunk->m_nextChunk) std::atomic<cancellation_registration_list_chunk*>(nullptr);
chunk->m_prevChunk = nullptr;
::new (&chunk->m_approximateFreeCount) std::atomic<int32_t>(static_cast<std::int32_t>(entryCount - 1));
chunk->m_entryCount = entryCount;
for (std::uint32_t i = 0; i < entryCount; ++i)
{
::new (&chunk->m_entries[i]) std::atomic<cancellation_registration*>(nullptr);
}
return chunk;
}
void cppcoro::detail::cancellation_registration_list_chunk::free(
cancellation_registration_list_chunk* chunk) noexcept
{
std::free(chunk);
}
cppcoro::detail::cancellation_registration_list*
cppcoro::detail::cancellation_registration_list::allocate()
{
constexpr std::uint32_t initialChunkSize = 16;
const std::size_t bufferSize =
sizeof(cancellation_registration_list) +
(initialChunkSize - 1) * sizeof(cancellation_registration_list_chunk::m_entries[0]);
auto* bucket = static_cast<cancellation_registration_list*>(std::malloc(bufferSize));
if (bucket == nullptr)
{
throw std::bad_alloc{};
}
::new (&bucket->m_approximateTail) std::atomic<cancellation_registration_list_chunk*>(&bucket->m_headChunk);
::new (&bucket->m_headChunk.m_nextChunk) std::atomic<cancellation_registration_list_chunk*>(nullptr);
bucket->m_headChunk.m_prevChunk = nullptr;
::new (&bucket->m_headChunk.m_approximateFreeCount)
std::atomic<int32_t>(static_cast<std::int32_t>(initialChunkSize - 1));
bucket->m_headChunk.m_entryCount = initialChunkSize;
for (std::uint32_t i = 0; i < initialChunkSize; ++i)
{
::new (&bucket->m_headChunk.m_entries[i]) std::atomic<cancellation_registration*>(nullptr);
}
return bucket;
}
void cppcoro::detail::cancellation_registration_list::free(cancellation_registration_list* list) noexcept
{
std::free(list);
}
cppcoro::detail::cancellation_registration_state*
cppcoro::detail::cancellation_registration_state::allocate()
{
constexpr std::uint32_t maxListCount = 16;
auto listCount = std::thread::hardware_concurrency();
if (listCount > maxListCount)
{
listCount = maxListCount;
}
else if (listCount == 0)
{
listCount = 1;
}
const std::size_t bufferSize =
sizeof(cancellation_registration_state) +
(listCount - 1) * sizeof(cancellation_registration_state::m_lists[0]);
auto* state = static_cast<cancellation_registration_state*>(std::malloc(bufferSize));
if (state == nullptr)
{
throw std::bad_alloc{};
}
state->m_listCount = listCount;
for (std::uint32_t i = 0; i < listCount; ++i)
{
::new (&state->m_lists[i]) std::atomic<cancellation_registration_list*>(nullptr);
}
return state;
}
void cppcoro::detail::cancellation_registration_state::free(cancellation_registration_state* state) noexcept
{
std::free(state);
}
cppcoro::detail::cancellation_registration_result
cppcoro::detail::cancellation_registration_state::add_registration(
cppcoro::cancellation_registration* registration)
{
// Pick a list to add to based on the current thread to reduce the
// chance of contention with multiple threads concurrently registering
// callbacks.
const auto threadIdHashCode = std::hash<std::thread::id>{}(std::this_thread::get_id());
auto& listPtr = m_lists[threadIdHashCode % m_listCount];
auto* list = listPtr.load(std::memory_order_acquire);
if (list == nullptr)
{
auto* newList = cancellation_registration_list::allocate();
// Pre-claim the first slot.
registration->m_chunk = &newList->m_headChunk;
registration->m_entryIndex = 0;
::new (&newList->m_headChunk.m_entries[0]) std::atomic<cancellation_registration*>(registration);
if (listPtr.compare_exchange_strong(
list,
newList,
std::memory_order_seq_cst,
std::memory_order_acquire))
{
return cancellation_registration_result(&newList->m_headChunk, 0);
}
else
{
cancellation_registration_list::free(newList);
}
}
while (true)
{
// Navigate to the end of the chain of chunks and work backwards looking for a free slot.
auto* const originalLastChunk = list->m_approximateTail.load(std::memory_order_acquire);
auto* lastChunk = originalLastChunk;
for (auto* next = lastChunk->m_nextChunk.load(std::memory_order_acquire);
next != nullptr;
next = next->m_nextChunk.load(std::memory_order_acquire))
{
lastChunk = next;
}
// Work around false-warning raised by MSVC static analysis complaining that
// warning C28182: Dereferencing NULL pointer. 'lastChunk' contains the same NULL value as 'chunk' did.
// on statement initialising 'elementCount' below.
CPPCORO_ASSUME(lastChunk != nullptr);
if (lastChunk != originalLastChunk)
{
// Update the cache of last chunk pointer so that subsequent
// registration requests can start there instead.
// Doesn't matter if these writes race as it will eventually
// converge to the true last chunk.
list->m_approximateTail.store(lastChunk, std::memory_order_release);
}
for (auto* chunk = lastChunk;
chunk != nullptr;
chunk = chunk->m_prevChunk)
{
auto freeCount = chunk->m_approximateFreeCount.load(std::memory_order_relaxed);
// If it looks like there are no free slots then decrement the count again
// to force it to re-search every so-often, just in case the count has gotten
// out-of-sync with the true free count and is reporting none free even though
// there are some (or possibly all) free slots.
if (freeCount < 1)
{
--freeCount;
chunk->m_approximateFreeCount.store(freeCount, std::memory_order_relaxed);
}
constexpr std::int32_t forcedSearchThreshold = -10;
if (freeCount > 0 || freeCount < forcedSearchThreshold)
{
const std::uint32_t entryCount = chunk->m_entryCount;
const std::uint32_t indexMask = entryCount - 1;
const std::uint32_t startIndex = entryCount - freeCount;
registration->m_chunk = chunk;
for (std::uint32_t i = 0; i < entryCount; ++i)
{
const std::uint32_t entryIndex = (startIndex + i) & indexMask;
auto& entry = chunk->m_entries[entryIndex];
// Do a cheap initial read of the entry value to see if the
// entry is likely free. This can potentially read stale values
// and so may lead to falsely thinking it's free or falsely
// thinking it's occupied. But approximate is good enough here.
auto* entryValue = entry.load(std::memory_order_relaxed);
if (entryValue == nullptr)
{
registration->m_entryIndex = entryIndex;
if (entry.compare_exchange_strong(
entryValue,
registration,
std::memory_order_seq_cst,
std::memory_order_relaxed))
{
// Successfully claimed the slot.
const std::int32_t newFreeCount = freeCount < 0 ? 0 : freeCount - 1;
chunk->m_approximateFreeCount.store(newFreeCount, std::memory_order_relaxed);
return cancellation_registration_result(chunk, entryIndex);
}
}
}
// Read through all elements of chunk with no success.
// Clear free-count back to 0.
chunk->m_approximateFreeCount.store(0, std::memory_order_relaxed);
}
}
// We've traversed through all of the chunks and found no free slots.
// So try and allocate a new chunk and append it to the list.
constexpr std::uint32_t maxElementCount = 1024;
const std::uint32_t elementCount =
lastChunk->m_entryCount < maxElementCount ?
lastChunk->m_entryCount * 2 : maxElementCount;
// May throw std::bad_alloc if out of memory.
auto* newChunk = cancellation_registration_list_chunk::allocate(elementCount);
newChunk->m_prevChunk = lastChunk;
// Pre-allocate first slot.
registration->m_chunk = newChunk;
registration->m_entryIndex = 0;
::new (&newChunk->m_entries[0]) std::atomic<cancellation_registration*>(registration);
cancellation_registration_list_chunk* oldNext = nullptr;
if (lastChunk->m_nextChunk.compare_exchange_strong(
oldNext,
newChunk,
std::memory_order_seq_cst,
std::memory_order_relaxed))
{
list->m_approximateTail.store(newChunk, std::memory_order_release);
return cancellation_registration_result(newChunk, 0);
}
// Some other thread published a new chunk to the end of the list
// concurrently. Free our chunk and go around the loop again, hopefully
// allocating a slot from the chunk the other thread just allocated.
cancellation_registration_list_chunk::free(newChunk);
}
}
cppcoro::detail::cancellation_state* cppcoro::detail::cancellation_state::create()
{
return new cancellation_state();
}
cppcoro::detail::cancellation_state::~cancellation_state()
{
assert((m_state.load(std::memory_order_relaxed) & cancellation_ref_count_mask) == 0);
// Use relaxed memory order in reads here since we should already have visibility
// to all writes as the ref-count decrement that preceded the call to the destructor
// has acquire-release semantics.
auto* registrationState = m_registrationState.load(std::memory_order_relaxed);
if (registrationState != nullptr)
{
for (std::uint32_t i = 0; i < registrationState->m_listCount; ++i)
{
auto* list = registrationState->m_lists[i].load(std::memory_order_relaxed);
if (list != nullptr)
{
auto* chunk = list->m_headChunk.m_nextChunk.load(std::memory_order_relaxed);
cancellation_registration_list::free(list);
while (chunk != nullptr)
{
auto* next = chunk->m_nextChunk.load(std::memory_order_relaxed);
cancellation_registration_list_chunk::free(chunk);
chunk = next;
}
}
}
cancellation_registration_state::free(registrationState);
}
}
void cppcoro::detail::cancellation_state::add_token_ref() noexcept
{
m_state.fetch_add(cancellation_token_ref_increment, std::memory_order_relaxed);
}
void cppcoro::detail::cancellation_state::release_token_ref() noexcept
{
const std::uint64_t oldState = m_state.fetch_sub(cancellation_token_ref_increment, std::memory_order_acq_rel);
if ((oldState & cancellation_ref_count_mask) == cancellation_token_ref_increment)
{
delete this;
}
}
void cppcoro::detail::cancellation_state::add_source_ref() noexcept
{
m_state.fetch_add(cancellation_source_ref_increment, std::memory_order_relaxed);
}
void cppcoro::detail::cancellation_state::release_source_ref() noexcept
{
const std::uint64_t oldState = m_state.fetch_sub(cancellation_source_ref_increment, std::memory_order_acq_rel);
if ((oldState & cancellation_ref_count_mask) == cancellation_source_ref_increment)
{
delete this;
}
}
bool cppcoro::detail::cancellation_state::can_be_cancelled() const noexcept
{
return (m_state.load(std::memory_order_acquire) & can_be_cancelled_mask) != 0;
}
bool cppcoro::detail::cancellation_state::is_cancellation_requested() const noexcept
{
return (m_state.load(std::memory_order_acquire) & cancellation_requested_flag) != 0;
}
bool cppcoro::detail::cancellation_state::is_cancellation_notification_complete() const noexcept
{
return (m_state.load(std::memory_order_acquire) & cancellation_notification_complete_flag) != 0;
}
void cppcoro::detail::cancellation_state::request_cancellation()
{
const auto oldState = m_state.fetch_or(cancellation_requested_flag, std::memory_order_seq_cst);
if ((oldState & cancellation_requested_flag) != 0)
{
// Some thread has already called request_cancellation().
return;
}
// We are the first caller of request_cancellation.
// Need to execute any registered callbacks to notify them of cancellation.
// NOTE: We need to use sequentially-consistent operations here to ensure
// that if there is a concurrent call to try_register_callback() on another
// thread that either the other thread will read the prior write to m_state
// after they write to a registration slot or we will read their write to the
// registration slot after the prior write to m_state.
auto* const registrationState = m_registrationState.load(std::memory_order_seq_cst);
if (registrationState != nullptr)
{
// Note that there should be no data-race in writing to this value here
// as another thread will only read it if they are trying to deregister
// a callback and that fails because we have acquired the pointer to
// the registration inside the loop below. In this case the atomic
// exchange that acquires the pointer below acts as a release-operation
// that synchronises with the failed exchange operation in deregister_callback()
// which has acquire semantics and thus will have visibility of the write to
// the m_notificationThreadId value.
registrationState->m_notificationThreadId = std::this_thread::get_id();
for (std::uint32_t listIndex = 0, listCount = registrationState->m_listCount;
listIndex < listCount;
++listIndex)
{
auto* list = registrationState->m_lists[listIndex].load(std::memory_order_seq_cst);
if (list == nullptr)
{
continue;
}
auto* chunk = &list->m_headChunk;
do
{
for (std::uint32_t entryIndex = 0, entryCount = chunk->m_entryCount;
entryIndex < entryCount;
++entryIndex)
{
auto& entry = chunk->m_entries[entryIndex];
// Quick read-only operation to check if any registration
// is present.
auto* registration = entry.load(std::memory_order_seq_cst);
if (registration != nullptr)
{
// Try to acquire ownership of the registration by replacing its
// slot with nullptr atomically. This resolves the race between
// a concurrent call to deregister_callback() from the registration's
// destructor.
registration = entry.exchange(nullptr, std::memory_order_seq_cst);
if (registration != nullptr)
{
try
{
registration->m_callback();
}
catch (...)
{
// TODO: What should behaviour of unhandled exception in a callback be here?
std::terminate();
}
}
}
}
chunk = chunk->m_nextChunk.load(std::memory_order_seq_cst);
} while (chunk != nullptr);
}
m_state.fetch_add(cancellation_notification_complete_flag, std::memory_order_release);
}
}
bool cppcoro::detail::cancellation_state::try_register_callback(
cancellation_registration* registration)
{
if (is_cancellation_requested())
{
return false;
}
auto* registrationState = m_registrationState.load(std::memory_order_acquire);
if (registrationState == nullptr)
{
// Could throw std::bad_alloc
auto* newRegistrationState = cancellation_registration_state::allocate();
// Need to use 'sequentially consistent' on the write here to ensure that if
// we subsequently read a value from m_state at the end of this function that
// doesn't have the cancellation_requested_flag bit set that a subsequent call
// in another thread to request_cancellation() will see this write.
if (m_registrationState.compare_exchange_strong(
registrationState,
newRegistrationState,
std::memory_order_seq_cst,
std::memory_order_acquire))
{
registrationState = newRegistrationState;
}
else
{
cancellation_registration_state::free(newRegistrationState);
}
}
// Could throw std::bad_alloc
auto result = registrationState->add_registration(registration);
// Need to check status again to handle the case where
// another thread calls request_cancellation() concurrently
// but doesn't see our write to the registration list.
//
// Note, we don't call IsCancellationRequested() here since that
// only provides 'acquire' memory semantics and we need 'seq_cst'
// semantics.
if ((m_state.load(std::memory_order_seq_cst) & cancellation_requested_flag) != 0)
{
// Cancellation was requested concurrently with adding the
// registration to the list. Try to remove the registration.
// If successful we return false to indicate that the callback
// has not been registered and the caller should execute the
// callback. If it fails it means that the thread that requested
// cancellation will execute our callback and we need to wait
// until it finishes before returning.
auto& entry = result.m_chunk->m_entries[result.m_entryIndex];
// Need to use compare_exchange here rather than just exchange since
// it may be possible that the thread calling request_cancellation()
// acquired our registration and executed the callback, freeing up
// the slot and then a third thread registers a new registration
// that gets allocated to this slot.
//
// Can use relaxed memory order here since in the case that this succeeds
// no other thread will have written to the cancellation_registration record
// so we can safely read from the record without synchronisation.
auto* oldValue = registration;
const bool deregisteredSuccessfully =
entry.compare_exchange_strong(oldValue, nullptr, std::memory_order_relaxed);
if (deregisteredSuccessfully)
{
return false;
}
// Otherwise, the cancelling thread has taken ownership for executing
// the callback and we can just act as if the registration succeeded.
}
return true;
}
void cppcoro::detail::cancellation_state::deregister_callback(cancellation_registration* registration) noexcept
{
auto* chunk = registration->m_chunk;
auto& entry = chunk->m_entries[registration->m_entryIndex];
// Use 'acquire' memory order on failure case so that we synchronise with the write
// to the slot inside request_cancellation() that acquired the registration such that
// we have visibility of its prior write to m_notifyingThreadId.
//
// Could use 'relaxed' memory order on success case as if this succeeds it means that
// no thread will have written to the registration object.
auto* oldValue = registration;
bool deregisteredSuccessfully = entry.compare_exchange_strong(
oldValue,
nullptr,
std::memory_order_acquire);
if (deregisteredSuccessfully)
{
// Increment free-count if it won't make it larger than entry count.
const std::int32_t oldFreeCount = chunk->m_approximateFreeCount.load(std::memory_order_relaxed);
if (oldFreeCount < static_cast<std::int32_t>(chunk->m_entryCount))
{
const std::int32_t newFreeCount = oldFreeCount < 0 ? 1 : oldFreeCount + 1;
chunk->m_approximateFreeCount.store(newFreeCount, std::memory_order_relaxed);
}
}
else
{
// A thread executing request_cancellation() has acquired this callback and
// is executing it. Need to wait until it finishes executing before we return
// and the registration object is destructed.
//
// However, we also need to handle the case where the registration is being
// removed from within a callback which would otherwise deadlock waiting
// for the callbacks to finish executing.
// Use relaxed memory order here as we should already have visibility
// of the write to m_registrationState from when the registration was first
// registered.
auto* registrationState = m_registrationState.load(std::memory_order_relaxed);
if (std::this_thread::get_id() != registrationState->m_notificationThreadId)
{
// TODO: More efficient busy-wait backoff strategy
while (!is_cancellation_notification_complete())
{
std::this_thread::yield();
}
}
}
}
cppcoro::detail::cancellation_state::cancellation_state() noexcept
: m_state(cancellation_source_ref_increment)
, m_registrationState(nullptr)
{
}
+108
View File
@@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_CANCELLATION_STATE_HPP_INCLUDED
#define CPPCORO_CANCELLATION_STATE_HPP_INCLUDED
#include <cppcoro/cancellation_token.hpp>
#include <thread>
#include <atomic>
#include <cstdint>
namespace cppcoro
{
namespace detail
{
struct cancellation_registration_state;
class cancellation_state
{
public:
/// Allocates a new cancellation_state object.
///
/// \throw std::bad_alloc
/// If there was insufficient memory to allocate one.
static cancellation_state* create();
~cancellation_state();
/// Increment the reference count of cancellation_token and
/// cancellation_registration objects referencing this state.
void add_token_ref() noexcept;
/// Decrement the reference count of cancellation_token and
/// cancellation_registration objects referencing this state.
void release_token_ref() noexcept;
/// Increment the reference count of cancellation_source objects.
void add_source_ref() noexcept;
/// Decrement the reference count of cancellation_souce objects.
///
/// The cancellation_state will no longer be cancellable once the
/// cancellation_source ref count reaches zero.
void release_source_ref() noexcept;
/// Query if the cancellation_state can have cancellation requested.
///
/// \return
/// Returns true if there are no more references to a cancellation_source
/// object.
bool can_be_cancelled() const noexcept;
/// Query if some thread has called request_cancellation().
bool is_cancellation_requested() const noexcept;
/// Flag state has having cancellation_requested and execute any
/// registered callbacks.
void request_cancellation();
/// Try to register the cancellation_registration as a callback to be executed
/// when cancellation is requested.
///
/// \return
/// true if the callback was successfully registered, false if the callback was
/// not registered because cancellation had already been requested.
///
/// \throw std::bad_alloc
/// If callback was unable to be registered due to insufficient memory.
bool try_register_callback(cancellation_registration* registration);
/// Deregister a callback previously registered successfully in a call to try_register_callback().
///
/// If the callback is currently being executed on another
/// thread that is concurrently calling request_cancellation()
/// then this call will block until the callback has finished executing.
void deregister_callback(cancellation_registration* registration) noexcept;
private:
cancellation_state() noexcept;
bool is_cancellation_notification_complete() const noexcept;
static constexpr std::uint64_t cancellation_requested_flag = 1;
static constexpr std::uint64_t cancellation_notification_complete_flag = 2;
static constexpr std::uint64_t cancellation_source_ref_increment = 4;
static constexpr std::uint64_t cancellation_token_ref_increment = UINT64_C(1) << 33;
static constexpr std::uint64_t can_be_cancelled_mask = cancellation_token_ref_increment - 1;
static constexpr std::uint64_t cancellation_ref_count_mask =
~(cancellation_requested_flag | cancellation_notification_complete_flag);
// A value that has:
// - bit 0 - indicates whether cancellation has been requested.
// - bit 1 - indicates whether cancellation notification is complete.
// - bits 2-32 - ref-count for cancellation_source instances.
// - bits 33-63 - ref-count for cancellation_token/cancellation_registration instances.
std::atomic<std::uint64_t> m_state;
std::atomic<cancellation_registration_state*> m_registrationState;
};
}
}
#endif
+108
View File
@@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/operation_cancelled.hpp>
#include "cancellation_state.hpp"
#include <utility>
#include <cassert>
cppcoro::cancellation_token::cancellation_token() noexcept
: m_state(nullptr)
{
}
cppcoro::cancellation_token::cancellation_token(const cancellation_token& other) noexcept
: m_state(other.m_state)
{
if (m_state != nullptr)
{
m_state->add_token_ref();
}
}
cppcoro::cancellation_token::cancellation_token(cancellation_token&& other) noexcept
: m_state(other.m_state)
{
other.m_state = nullptr;
}
cppcoro::cancellation_token::~cancellation_token()
{
if (m_state != nullptr)
{
m_state->release_token_ref();
}
}
cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(const cancellation_token& other) noexcept
{
if (other.m_state != m_state)
{
if (m_state != nullptr)
{
m_state->release_token_ref();
}
m_state = other.m_state;
if (m_state != nullptr)
{
m_state->add_token_ref();
}
}
return *this;
}
cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(cancellation_token&& other) noexcept
{
if (this != &other)
{
if (m_state != nullptr)
{
m_state->release_token_ref();
}
m_state = other.m_state;
other.m_state = nullptr;
}
return *this;
}
void cppcoro::cancellation_token::swap(cancellation_token& other) noexcept
{
std::swap(m_state, other.m_state);
}
bool cppcoro::cancellation_token::can_be_cancelled() const noexcept
{
return m_state != nullptr && m_state->can_be_cancelled();
}
bool cppcoro::cancellation_token::is_cancellation_requested() const noexcept
{
return m_state != nullptr && m_state->is_cancellation_requested();
}
void cppcoro::cancellation_token::throw_if_cancellation_requested() const
{
if (is_cancellation_requested())
{
throw operation_cancelled{};
}
}
cppcoro::cancellation_token::cancellation_token(detail::cancellation_state* state) noexcept
: m_state(state)
{
if (m_state != nullptr)
{
m_state->add_token_ref();
}
}
+168
View File
@@ -0,0 +1,168 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include <cppcoro/file.hpp>
#include <cppcoro/io_service.hpp>
#include <system_error>
#include <cassert>
#if CPPCORO_OS_WINNT
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <Windows.h>
#endif
cppcoro::file::~file()
{}
std::uint64_t cppcoro::file::size() const
{
#if CPPCORO_OS_WINNT
LARGE_INTEGER size;
BOOL ok = ::GetFileSizeEx(m_fileHandle.handle(), &size);
if (!ok)
{
DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"error getting file size: GetFileSizeEx"
};
}
return size.QuadPart;
#endif
}
cppcoro::file::file(detail::win32::safe_handle&& fileHandle) noexcept
: m_fileHandle(std::move(fileHandle))
{
}
cppcoro::detail::win32::safe_handle cppcoro::file::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)
{
DWORD flags = FILE_FLAG_OVERLAPPED;
if ((bufferingMode & file_buffering_mode::random_access) == file_buffering_mode::random_access)
{
flags |= FILE_FLAG_RANDOM_ACCESS;
}
if ((bufferingMode & file_buffering_mode::sequential) == file_buffering_mode::sequential)
{
flags |= FILE_FLAG_SEQUENTIAL_SCAN;
}
if ((bufferingMode & file_buffering_mode::write_through) == file_buffering_mode::write_through)
{
flags |= FILE_FLAG_WRITE_THROUGH;
}
if ((bufferingMode & file_buffering_mode::temporary) == file_buffering_mode::temporary)
{
flags |= FILE_ATTRIBUTE_TEMPORARY;
}
if ((bufferingMode & file_buffering_mode::unbuffered) == file_buffering_mode::unbuffered)
{
flags |= FILE_FLAG_NO_BUFFERING;
}
DWORD shareFlags = 0;
if ((shareMode & file_share_mode::read) == file_share_mode::read)
{
shareFlags |= FILE_SHARE_READ;
}
if ((shareMode & file_share_mode::write) == file_share_mode::write)
{
shareFlags |= FILE_SHARE_WRITE;
}
if ((shareMode & file_share_mode::delete_) == file_share_mode::delete_)
{
shareFlags |= FILE_SHARE_DELETE;
}
DWORD creationDisposition = 0;
switch (openMode)
{
case file_open_mode::create_or_open:
creationDisposition = OPEN_ALWAYS;
break;
case file_open_mode::create_always:
creationDisposition = CREATE_ALWAYS;
break;
case file_open_mode::create_new:
creationDisposition = CREATE_NEW;
break;
case file_open_mode::open_existing:
creationDisposition = OPEN_EXISTING;
break;
case file_open_mode::truncate_existing:
creationDisposition = TRUNCATE_EXISTING;
break;
}
// Open the file
detail::win32::safe_handle fileHandle(
::CreateFileW(
path.wstring().c_str(),
fileAccess,
shareFlags,
nullptr,
creationDisposition,
flags,
nullptr));
if (fileHandle.handle() == INVALID_HANDLE_VALUE)
{
const DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"error opening file: CreateFileW"
};
}
// Associate with the I/O service's completion port.
const HANDLE result = ::CreateIoCompletionPort(
fileHandle.handle(),
ioService.native_iocp_handle(),
0,
0);
if (result == nullptr)
{
const DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"error opening file: CreateIoCompletionPort"
};
}
// Configure I/O operations to avoid dispatching a completion event
// to the I/O service if the operation completes synchronously.
// This avoids unnecessary suspension/resuption of the awaiting coroutine.
const BOOL ok = ::SetFileCompletionNotificationModes(
fileHandle.handle(),
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS |
FILE_SKIP_SET_EVENT_ON_HANDLE);
if (!ok)
{
const DWORD errorCode = ::GetLastError();
throw std::system_error
{
static_cast<int>(errorCode),
std::system_category(),
"error opening file: SetFileCompletionNotificationModes"
};
}
return std::move(fileHandle);
}

Some files were not shown because too many files have changed in this diff Show More