First
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
*.cakec
|
||||||
|
*.pyc
|
||||||
|
.idea
|
||||||
|
*~
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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()
|
||||||
@@ -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
@@ -0,0 +1,75 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ASYNC_LATCH_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ASYNC_LATCH_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/async_manual_reset_event.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class async_latch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Construct the latch with the specified initial count.
|
||||||
|
///
|
||||||
|
/// \param initialCount
|
||||||
|
/// The initial count of the latch. The latch will become signalled once
|
||||||
|
/// \c this->count_down() has been called \p initialCount times.
|
||||||
|
/// The latch will be immediately signalled on construction if this
|
||||||
|
/// parameter is zero or negative.
|
||||||
|
async_latch(std::ptrdiff_t initialCount) noexcept
|
||||||
|
: m_count(initialCount)
|
||||||
|
, m_event(initialCount <= 0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// Query if the latch has become signalled.
|
||||||
|
///
|
||||||
|
/// The latch is marked as signalled once the count reaches zero.
|
||||||
|
bool is_ready() const noexcept { return m_event.is_set(); }
|
||||||
|
|
||||||
|
/// Decrement the count by n.
|
||||||
|
///
|
||||||
|
/// Any coroutines awaiting this latch will be resumed once the count
|
||||||
|
/// reaches zero. ie. when this method has been called at least 'initialCount'
|
||||||
|
/// times.
|
||||||
|
///
|
||||||
|
/// Any awaiting coroutines that are currently suspended waiting for the
|
||||||
|
/// latch to become signalled will be resumed inside the last call to this
|
||||||
|
/// method (ie. the call that decrements the count to zero).
|
||||||
|
///
|
||||||
|
/// \param n
|
||||||
|
/// The amount to decrement the count by.
|
||||||
|
void count_down(std::ptrdiff_t n = 1) noexcept
|
||||||
|
{
|
||||||
|
if (m_count.fetch_sub(n, std::memory_order_acq_rel) <= n)
|
||||||
|
{
|
||||||
|
m_event.set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows the latch to be awaited within a coroutine.
|
||||||
|
///
|
||||||
|
/// If the latch is already signalled (ie. the count has been decremented
|
||||||
|
/// to zero) then the awaiting coroutine will continue without suspending.
|
||||||
|
/// Otherwise, the coroutine will suspend and will later be resumed inside
|
||||||
|
/// a call to `count_down()`.
|
||||||
|
auto operator co_await() const noexcept
|
||||||
|
{
|
||||||
|
return m_event.operator co_await();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::atomic<std::ptrdiff_t> m_count;
|
||||||
|
async_manual_reset_event m_event;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class async_manual_reset_event_operation;
|
||||||
|
|
||||||
|
/// An async manual-reset event is a coroutine synchronisation abstraction
|
||||||
|
/// that allows one or more coroutines to wait until some thread calls
|
||||||
|
/// set() on the event.
|
||||||
|
///
|
||||||
|
/// When a coroutine awaits a 'set' event the coroutine continues without
|
||||||
|
/// suspending. Otherwise, if it awaits a 'not set' event the coroutine is
|
||||||
|
/// suspended and is later resumed inside the call to 'set()'.
|
||||||
|
///
|
||||||
|
/// \seealso async_auto_reset_event
|
||||||
|
class async_manual_reset_event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Initialise the event to either 'set' or 'not set' state.
|
||||||
|
///
|
||||||
|
/// \param initiallySet
|
||||||
|
/// If 'true' then initialises the event to the 'set' state, otherwise
|
||||||
|
/// initialises the event to the 'not set' state.
|
||||||
|
async_manual_reset_event(bool initiallySet = false) noexcept;
|
||||||
|
|
||||||
|
~async_manual_reset_event();
|
||||||
|
|
||||||
|
/// Wait for the event to enter the 'set' state.
|
||||||
|
///
|
||||||
|
/// If the event is already 'set' then the coroutine continues without
|
||||||
|
/// suspending.
|
||||||
|
///
|
||||||
|
/// Otherwise, the coroutine is suspended and later resumed when some
|
||||||
|
/// thread calls 'set()'. The coroutine will be resumed inside the next
|
||||||
|
/// call to 'set()'.
|
||||||
|
async_manual_reset_event_operation operator co_await() const noexcept;
|
||||||
|
|
||||||
|
/// Query if the event is currently in the 'set' state.
|
||||||
|
bool is_set() const noexcept;
|
||||||
|
|
||||||
|
/// Set the state of the event to 'set'.
|
||||||
|
///
|
||||||
|
/// If there are pending coroutines awaiting the event then all
|
||||||
|
/// pending coroutines are resumed within this call.
|
||||||
|
/// Any coroutines that subsequently await the event will continue
|
||||||
|
/// without suspending.
|
||||||
|
///
|
||||||
|
/// This operation is a no-op if the event was already 'set'.
|
||||||
|
void set() noexcept;
|
||||||
|
|
||||||
|
/// Set the state of the event to 'not-set'.
|
||||||
|
///
|
||||||
|
/// Any coroutines that subsequently await the event will suspend
|
||||||
|
/// until some thread calls 'set()'.
|
||||||
|
///
|
||||||
|
/// This is a no-op if the state was already 'not set'.
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class async_manual_reset_event_operation;
|
||||||
|
|
||||||
|
// This variable has 3 states:
|
||||||
|
// - this - The state is 'set'.
|
||||||
|
// - nullptr - The state is 'not set' with no waiters.
|
||||||
|
// - other - The state is 'not set'.
|
||||||
|
// Points to an 'async_manual_reset_event_operation' that is
|
||||||
|
// the head of a linked-list of waiters.
|
||||||
|
mutable std::atomic<void*> m_state;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class async_manual_reset_event_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept;
|
||||||
|
|
||||||
|
bool await_ready() const noexcept;
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class async_manual_reset_event;
|
||||||
|
|
||||||
|
const async_manual_reset_event& m_event;
|
||||||
|
async_manual_reset_event_operation* m_next;
|
||||||
|
cppcoro::coroutine_handle<> m_awaiter;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex> // for std::adopt_lock_t
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class async_mutex_lock;
|
||||||
|
class async_mutex_lock_operation;
|
||||||
|
class async_mutex_scoped_lock_operation;
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// A mutex that can be locked asynchronously using 'co_await'.
|
||||||
|
///
|
||||||
|
/// Ownership of the mutex is not tied to any particular thread.
|
||||||
|
/// This allows the coroutine owning the lock to transition from
|
||||||
|
/// one thread to another while holding a lock.
|
||||||
|
///
|
||||||
|
/// Implementation is lock-free, using only std::atomic values for
|
||||||
|
/// synchronisation. Awaiting coroutines are suspended without blocking
|
||||||
|
/// the current thread if the lock could not be acquired synchronously.
|
||||||
|
class async_mutex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Construct to a mutex that is not currently locked.
|
||||||
|
async_mutex() noexcept;
|
||||||
|
|
||||||
|
/// Destroys the mutex.
|
||||||
|
///
|
||||||
|
/// Behaviour is undefined if there are any outstanding coroutines
|
||||||
|
/// still waiting to acquire the lock.
|
||||||
|
~async_mutex();
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Attempt to acquire a lock on the mutex without blocking.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// true if the lock was acquired, false if the mutex was already locked.
|
||||||
|
/// The caller is responsible for ensuring unlock() is called on the mutex
|
||||||
|
/// to release the lock if the lock was acquired by this call.
|
||||||
|
bool try_lock() noexcept;
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Acquire a lock on the mutex asynchronously.
|
||||||
|
///
|
||||||
|
/// If the lock could not be acquired synchronously then the awaiting
|
||||||
|
/// coroutine will be suspended and later resumed when the lock becomes
|
||||||
|
/// available. If suspended, the coroutine will be resumed inside the
|
||||||
|
/// call to unlock() from the previous lock owner.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An operation object that must be 'co_await'ed to wait until the
|
||||||
|
/// lock is acquired. The result of the 'co_await m.lock_async()'
|
||||||
|
/// expression has type 'void'.
|
||||||
|
async_mutex_lock_operation lock_async() noexcept;
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Acquire a lock on the mutex asynchronously, returning an object that
|
||||||
|
/// will call unlock() automatically when it goes out of scope.
|
||||||
|
///
|
||||||
|
/// If the lock could not be acquired synchronously then the awaiting
|
||||||
|
/// coroutine will be suspended and later resumed when the lock becomes
|
||||||
|
/// available. If suspended, the coroutine will be resumed inside the
|
||||||
|
/// call to unlock() from the previous lock owner.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An operation object that must be 'co_await'ed to wait until the
|
||||||
|
/// lock is acquired. The result of the 'co_await m.scoped_lock_async()'
|
||||||
|
/// expression returns an 'async_mutex_lock' object that will call
|
||||||
|
/// this->mutex() when it destructs.
|
||||||
|
async_mutex_scoped_lock_operation scoped_lock_async() noexcept;
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Unlock the mutex.
|
||||||
|
///
|
||||||
|
/// Must only be called by the current lock-holder.
|
||||||
|
///
|
||||||
|
/// If there are lock operations waiting to acquire the
|
||||||
|
/// mutex then the next lock operation in the queue will
|
||||||
|
/// be resumed inside this call.
|
||||||
|
void unlock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class async_mutex_lock_operation;
|
||||||
|
|
||||||
|
static constexpr std::uintptr_t not_locked = 1;
|
||||||
|
|
||||||
|
// assume == reinterpret_cast<std::uintptr_t>(static_cast<void*>(nullptr))
|
||||||
|
static constexpr std::uintptr_t locked_no_waiters = 0;
|
||||||
|
|
||||||
|
// This field provides synchronisation for the mutex.
|
||||||
|
//
|
||||||
|
// It can have three kinds of values:
|
||||||
|
// - not_locked
|
||||||
|
// - locked_no_waiters
|
||||||
|
// - a pointer to the head of a singly linked list of recently
|
||||||
|
// queued async_mutex_lock_operation objects. This list is
|
||||||
|
// in most-recently-queued order as new items are pushed onto
|
||||||
|
// the front of the list.
|
||||||
|
std::atomic<std::uintptr_t> m_state;
|
||||||
|
|
||||||
|
// Linked list of async lock operations that are waiting to acquire
|
||||||
|
// the mutex. These operations will acquire the lock in the order
|
||||||
|
// they appear in this list. Waiters in this list will acquire the
|
||||||
|
// mutex before waiters added to the m_newWaiters list.
|
||||||
|
async_mutex_lock_operation* m_waiters;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// An object that holds onto a mutex lock for its lifetime and
|
||||||
|
/// ensures that the mutex is unlocked when it is destructed.
|
||||||
|
///
|
||||||
|
/// It is equivalent to a std::lock_guard object but requires
|
||||||
|
/// that the result of co_await async_mutex::lock_async() is
|
||||||
|
/// passed to the constructor rather than passing the async_mutex
|
||||||
|
/// object itself.
|
||||||
|
class async_mutex_lock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
|
||||||
|
: m_mutex(&mutex)
|
||||||
|
{}
|
||||||
|
|
||||||
|
async_mutex_lock(async_mutex_lock&& other) noexcept
|
||||||
|
: m_mutex(other.m_mutex)
|
||||||
|
{
|
||||||
|
other.m_mutex = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
async_mutex_lock(const async_mutex_lock& other) = delete;
|
||||||
|
async_mutex_lock& operator=(const async_mutex_lock& other) = delete;
|
||||||
|
|
||||||
|
// Releases the lock.
|
||||||
|
~async_mutex_lock()
|
||||||
|
{
|
||||||
|
if (m_mutex != nullptr)
|
||||||
|
{
|
||||||
|
m_mutex->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
async_mutex* m_mutex;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class async_mutex_lock_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit async_mutex_lock_operation(async_mutex& mutex) noexcept
|
||||||
|
: m_mutex(mutex)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
friend class async_mutex;
|
||||||
|
|
||||||
|
async_mutex& m_mutex;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
async_mutex_lock_operation* m_next;
|
||||||
|
cppcoro::coroutine_handle<> m_awaiter;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class async_mutex_scoped_lock_operation : public async_mutex_lock_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using async_mutex_lock_operation::async_mutex_lock_operation;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
async_mutex_lock await_resume() const noexcept
|
||||||
|
{
|
||||||
|
return async_mutex_lock{ m_mutex, std::adopt_lock };
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ASYNC_SCOPE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/on_scope_exit.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class async_scope
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
async_scope() noexcept
|
||||||
|
: m_count(1u)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~async_scope()
|
||||||
|
{
|
||||||
|
// scope must be co_awaited before it destructs.
|
||||||
|
assert(m_continuation);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
void spawn(AWAITABLE&& awaitable)
|
||||||
|
{
|
||||||
|
[](async_scope* scope, std::decay_t<AWAITABLE> awaitable) -> oneway_task
|
||||||
|
{
|
||||||
|
scope->on_work_started();
|
||||||
|
auto decrementOnCompletion = on_scope_exit([scope] { scope->on_work_finished(); });
|
||||||
|
co_await std::move(awaitable);
|
||||||
|
}(this, std::forward<AWAITABLE>(awaitable));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto join() noexcept
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
async_scope* m_scope;
|
||||||
|
public:
|
||||||
|
awaiter(async_scope* scope) noexcept : m_scope(scope) {}
|
||||||
|
|
||||||
|
bool await_ready() noexcept
|
||||||
|
{
|
||||||
|
return m_scope->m_count.load(std::memory_order_acquire) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> continuation) noexcept
|
||||||
|
{
|
||||||
|
m_scope->m_continuation = continuation;
|
||||||
|
return m_scope->m_count.fetch_sub(1u, std::memory_order_acq_rel) > 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ this };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void on_work_finished() noexcept
|
||||||
|
{
|
||||||
|
if (m_count.fetch_sub(1u, std::memory_order_acq_rel) == 1)
|
||||||
|
{
|
||||||
|
m_continuation.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_work_started() noexcept
|
||||||
|
{
|
||||||
|
assert(m_count.load(std::memory_order_relaxed) != 0);
|
||||||
|
m_count.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct oneway_task
|
||||||
|
{
|
||||||
|
struct promise_type
|
||||||
|
{
|
||||||
|
cppcoro::suspend_never initial_suspend() noexcept { return {}; }
|
||||||
|
cppcoro::suspend_never final_suspend() noexcept { return {}; }
|
||||||
|
void unhandled_exception() { std::terminate(); }
|
||||||
|
oneway_task get_return_object() { return {}; }
|
||||||
|
void return_void() {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<size_t> m_count;
|
||||||
|
cppcoro::coroutine_handle<> m_continuation;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
|
||||||
|
#define CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/detail/get_awaiter.hpp>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct awaitable_traits
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct awaitable_traits<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
|
||||||
|
{
|
||||||
|
using awaiter_t = decltype(cppcoro::detail::get_awaiter(std::declval<T>()));
|
||||||
|
|
||||||
|
using await_result_t = decltype(std::declval<awaiter_t>().await_resume());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_BROKEN_PROMISE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
/// \brief
|
||||||
|
/// Exception thrown when you attempt to retrieve the result of
|
||||||
|
/// a task that has been detached from its promise/coroutine.
|
||||||
|
class broken_promise : public std::logic_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
broken_promise()
|
||||||
|
: std::logic_error("broken promise")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class cancellation_state;
|
||||||
|
struct cancellation_registration_list_chunk;
|
||||||
|
struct cancellation_registration_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class cancellation_registration
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Registers the callback to be executed when cancellation is requested
|
||||||
|
/// on the cancellation_token.
|
||||||
|
///
|
||||||
|
/// The callback will be executed if cancellation is requested for the
|
||||||
|
/// specified cancellation token. If cancellation has already been requested
|
||||||
|
/// then the callback will be executed immediately, before the constructor
|
||||||
|
/// returns. If cancellation has not yet been requested then the callback
|
||||||
|
/// will be executed on the first thread to request cancellation inside
|
||||||
|
/// the call to cancellation_source::request_cancellation().
|
||||||
|
///
|
||||||
|
/// \param token
|
||||||
|
/// The cancellation token to register the callback with.
|
||||||
|
///
|
||||||
|
/// \param callback
|
||||||
|
/// The callback to be executed when cancellation is requested on the
|
||||||
|
/// the cancellation_token. Note that callback must not throw an exception
|
||||||
|
/// if called when cancellation is requested otherwise std::terminate()
|
||||||
|
/// will be called.
|
||||||
|
///
|
||||||
|
/// \throw std::bad_alloc
|
||||||
|
/// If registration failed due to insufficient memory available.
|
||||||
|
template<
|
||||||
|
typename FUNC,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<std::function<void()>, FUNC&&>>>
|
||||||
|
cancellation_registration(cancellation_token token, FUNC&& callback)
|
||||||
|
: m_callback(std::forward<FUNC>(callback))
|
||||||
|
{
|
||||||
|
register_callback(std::move(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellation_registration(const cancellation_registration& other) = delete;
|
||||||
|
cancellation_registration& operator=(const cancellation_registration& other) = delete;
|
||||||
|
|
||||||
|
/// Deregisters the callback.
|
||||||
|
///
|
||||||
|
/// After the destructor returns it is guaranteed that the callback
|
||||||
|
/// will not be subsequently called during a call to request_cancellation()
|
||||||
|
/// on the cancellation_source.
|
||||||
|
///
|
||||||
|
/// This may block if cancellation has been requested on another thread
|
||||||
|
/// is it will need to wait until this callback has finished executing
|
||||||
|
/// before the callback can be destroyed.
|
||||||
|
~cancellation_registration();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class detail::cancellation_state;
|
||||||
|
friend struct detail::cancellation_registration_state;
|
||||||
|
|
||||||
|
void register_callback(cancellation_token&& token);
|
||||||
|
|
||||||
|
detail::cancellation_state* m_state;
|
||||||
|
std::function<void()> m_callback;
|
||||||
|
detail::cancellation_registration_list_chunk* m_chunk;
|
||||||
|
std::uint32_t m_entryIndex;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class cancellation_token;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class cancellation_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class cancellation_source
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Construct to a new cancellation source.
|
||||||
|
cancellation_source();
|
||||||
|
|
||||||
|
/// Create a new reference to the same underlying cancellation
|
||||||
|
/// source as \p other.
|
||||||
|
cancellation_source(const cancellation_source& other) noexcept;
|
||||||
|
|
||||||
|
cancellation_source(cancellation_source&& other) noexcept;
|
||||||
|
|
||||||
|
~cancellation_source();
|
||||||
|
|
||||||
|
cancellation_source& operator=(const cancellation_source& other) noexcept;
|
||||||
|
|
||||||
|
cancellation_source& operator=(cancellation_source&& other) noexcept;
|
||||||
|
|
||||||
|
/// Query if this cancellation source can be cancelled.
|
||||||
|
///
|
||||||
|
/// A cancellation source object will not be cancellable if it has
|
||||||
|
/// previously been moved into another cancellation_source instance
|
||||||
|
/// or was copied from a cancellation_source that was not cancellable.
|
||||||
|
bool can_be_cancelled() const noexcept;
|
||||||
|
|
||||||
|
/// Obtain a cancellation token that can be used to query if
|
||||||
|
/// cancellation has been requested on this source.
|
||||||
|
///
|
||||||
|
/// The cancellation token can be passed into functions that you
|
||||||
|
/// may want to later be able to request cancellation.
|
||||||
|
cancellation_token token() const noexcept;
|
||||||
|
|
||||||
|
/// Request cancellation of operations that were passed an associated
|
||||||
|
/// cancellation token.
|
||||||
|
///
|
||||||
|
/// Any cancellation callback registered via a cancellation_registration
|
||||||
|
/// object will be called inside this function by the first thread to
|
||||||
|
/// call this method.
|
||||||
|
///
|
||||||
|
/// This operation is a no-op if can_be_cancelled() returns false.
|
||||||
|
void request_cancellation();
|
||||||
|
|
||||||
|
/// Query if some thread has called 'request_cancellation()' on this
|
||||||
|
/// cancellation_source.
|
||||||
|
bool is_cancellation_requested() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
detail::cancellation_state* m_state;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
|
||||||
|
#define CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class cancellation_source;
|
||||||
|
class cancellation_registration;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class cancellation_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class cancellation_token
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Construct to a cancellation token that can't be cancelled.
|
||||||
|
cancellation_token() noexcept;
|
||||||
|
|
||||||
|
/// Copy another cancellation token.
|
||||||
|
///
|
||||||
|
/// New token will refer to the same underlying state.
|
||||||
|
cancellation_token(const cancellation_token& other) noexcept;
|
||||||
|
|
||||||
|
cancellation_token(cancellation_token&& other) noexcept;
|
||||||
|
|
||||||
|
~cancellation_token();
|
||||||
|
|
||||||
|
cancellation_token& operator=(const cancellation_token& other) noexcept;
|
||||||
|
|
||||||
|
cancellation_token& operator=(cancellation_token&& other) noexcept;
|
||||||
|
|
||||||
|
void swap(cancellation_token& other) noexcept;
|
||||||
|
|
||||||
|
/// Query if it is possible that this operation will be cancelled
|
||||||
|
/// or not.
|
||||||
|
///
|
||||||
|
/// Cancellable operations may be able to take more efficient code-paths
|
||||||
|
/// if they don't need to handle cancellation requests.
|
||||||
|
bool can_be_cancelled() const noexcept;
|
||||||
|
|
||||||
|
/// Query if some thread has requested cancellation on an associated
|
||||||
|
/// cancellation_source object.
|
||||||
|
bool is_cancellation_requested() const noexcept;
|
||||||
|
|
||||||
|
/// Throws cppcoro::operation_cancelled exception if cancellation
|
||||||
|
/// has been requested for the associated operation.
|
||||||
|
void throw_if_cancellation_requested() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cancellation_source;
|
||||||
|
friend class cancellation_registration;
|
||||||
|
|
||||||
|
cancellation_token(detail::cancellation_state* state) noexcept;
|
||||||
|
|
||||||
|
detail::cancellation_state* m_state;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void swap(cancellation_token& a, cancellation_token& b) noexcept
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_CONFIG_HPP_INCLUDED
|
||||||
|
#define CPPCORO_CONFIG_HPP_INCLUDED
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Compiler Detection
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# define CPPCORO_COMPILER_MSVC _MSC_FULL_VER
|
||||||
|
#else
|
||||||
|
# define CPPCORO_COMPILER_MSVC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
# define CPPCORO_COMPILER_CLANG (__clang_major__ * 10000 + \
|
||||||
|
__clang_minor__ * 100 + \
|
||||||
|
__clang_patchlevel__)
|
||||||
|
#else
|
||||||
|
# define CPPCORO_COMPILER_CLANG 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
# define CPPCORO_COMPILER_GCC (__GNUC__ * 10000 + \
|
||||||
|
__GNUC_MINOR__ * 100 + \
|
||||||
|
__GNUC_PATCHLEVEL__)
|
||||||
|
#else
|
||||||
|
# define CPPCORO_COMPILER_GCC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \def CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
/// Defined to 1 if the compiler supports returning a coroutine_handle from
|
||||||
|
/// the await_suspend() method as a way of transferring execution
|
||||||
|
/// to another coroutine with a guaranteed tail-call.
|
||||||
|
#if CPPCORO_COMPILER_CLANG
|
||||||
|
# if __clang_major__ >= 7
|
||||||
|
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#ifndef CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
# define CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# define CPPCORO_ASSUME(X) __assume(X)
|
||||||
|
#else
|
||||||
|
# define CPPCORO_ASSUME(X)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# define CPPCORO_NOINLINE __declspec(noinline)
|
||||||
|
#elif CPPCORO_COMPILER_CLANG || CPPCORO_COMPILER_GCC
|
||||||
|
# define CPPCORO_NOINLINE __attribute__((noinline))
|
||||||
|
#else
|
||||||
|
# define CPPCORO_NOINLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# define CPPCORO_FORCE_INLINE __forceinline
|
||||||
|
#elif CPPCORO_COMPILER_CLANG
|
||||||
|
# define CPPCORO_FORCE_INLINE __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
# define CPPCORO_FORCE_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// OS Detection
|
||||||
|
|
||||||
|
/// \def CPPCORO_OS_WINNT
|
||||||
|
/// Defined to non-zero if the target platform is a WindowsNT variant.
|
||||||
|
/// 0x0500 - Windows 2000
|
||||||
|
/// 0x0501 - Windows XP/Server 2003
|
||||||
|
/// 0x0502 - Windows XP SP2/Server 2003 SP1
|
||||||
|
/// 0x0600 - Windows Vista/Server 2008
|
||||||
|
/// 0x0601 - Windows 7
|
||||||
|
/// 0x0602 - Windows 8
|
||||||
|
/// 0x0603 - Windows 8.1
|
||||||
|
/// 0x0A00 - Windows 10
|
||||||
|
#if defined(_WIN32_WINNT) || defined(_WIN32)
|
||||||
|
# if !defined(_WIN32_WINNT)
|
||||||
|
// Default to targeting Windows 10 if not defined.
|
||||||
|
# define _WIN32_WINNT 0x0A00
|
||||||
|
# endif
|
||||||
|
# define CPPCORO_OS_WINNT _WIN32_WINNT
|
||||||
|
#else
|
||||||
|
# define CPPCORO_OS_WINNT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
# define CPPCORO_OS_LINUX 1
|
||||||
|
#else
|
||||||
|
# define CPPCORO_OS_LINUX 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CPU Detection
|
||||||
|
|
||||||
|
/// \def CPPCORO_CPU_X86
|
||||||
|
/// Defined to 1 if target CPU is of x86 family.
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# if defined(_M_IX86)
|
||||||
|
# define CPPCORO_CPU_X86 1
|
||||||
|
# endif
|
||||||
|
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
|
||||||
|
# if defined(__i386__)
|
||||||
|
# define CPPCORO_CPU_X86 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#if !defined(CPPCORO_CPU_X86)
|
||||||
|
# define CPPCORO_CPU_X86 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \def CPPCORO_CPU_X64
|
||||||
|
/// Defined to 1 if the target CPU is x64 family.
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# if defined(_M_X64)
|
||||||
|
# define CPPCORO_CPU_X64 1
|
||||||
|
# endif
|
||||||
|
#elif CPPCORO_COMPILER_GCC || CPPCORO_COMPILER_CLANG
|
||||||
|
# if defined(__x86_64__)
|
||||||
|
# define CPPCORO_CPU_X64 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#if !defined(CPPCORO_CPU_X64)
|
||||||
|
# define CPPCORO_CPU_X64 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \def CPPCORO_CPU_32BIT
|
||||||
|
/// Defined if compiling for a 32-bit CPU architecture.
|
||||||
|
#if CPPCORO_CPU_X86
|
||||||
|
# define CPPCORO_CPU_32BIT 1
|
||||||
|
#else
|
||||||
|
# define CPPCORO_CPU_32BIT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \def CPPCORO_CPU_64BIT
|
||||||
|
/// Defined if compiling for a 64-bit CPU architecture.
|
||||||
|
#if CPPCORO_CPU_X64
|
||||||
|
# define CPPCORO_CPU_64BIT 1
|
||||||
|
#else
|
||||||
|
# define CPPCORO_CPU_64BIT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# define CPPCORO_CPU_CACHE_LINE std::hardware_destructive_interference_size
|
||||||
|
#else
|
||||||
|
// On most architectures we can assume a 64-byte cache line.
|
||||||
|
# define CPPCORO_CPU_CACHE_LINE 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
#if __has_include(<coroutine>)
|
||||||
|
#include <yvals_core.h>
|
||||||
|
#ifdef __cpp_lib_coroutine
|
||||||
|
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#if __has_include(<coroutine>)
|
||||||
|
#define CPPCORO_COROHEADER_FOUND_AND_USABLE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef CPPCORO_COROUTINE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_COROUTINE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#ifdef CPPCORO_COROHEADER_FOUND_AND_USABLE
|
||||||
|
|
||||||
|
#include <coroutine>
|
||||||
|
|
||||||
|
namespace cppcoro {
|
||||||
|
using std::coroutine_handle;
|
||||||
|
using std::suspend_always;
|
||||||
|
using std::noop_coroutine;
|
||||||
|
using std::suspend_never;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif __has_include(<experimental/coroutine>)
|
||||||
|
|
||||||
|
#include <experimental/coroutine>
|
||||||
|
|
||||||
|
namespace cppcoro {
|
||||||
|
using std::experimental::coroutine_handle;
|
||||||
|
using std::experimental::suspend_always;
|
||||||
|
using std::experimental::suspend_never;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
using std::experimental::noop_coroutine;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Cppcoro requires a C++20 compiler with coroutine support
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_ANY_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_ANY_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
// Helper type that can be cast-to from any type.
|
||||||
|
struct any
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
any(T&&) noexcept
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/detail/is_awaiter.hpp>
|
||||||
|
#include <cppcoro/detail/any.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
auto get_awaiter_impl(T&& value, int)
|
||||||
|
noexcept(noexcept(static_cast<T&&>(value).operator co_await()))
|
||||||
|
-> decltype(static_cast<T&&>(value).operator co_await())
|
||||||
|
{
|
||||||
|
return static_cast<T&&>(value).operator co_await();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto get_awaiter_impl(T&& value, long)
|
||||||
|
noexcept(noexcept(operator co_await(static_cast<T&&>(value))))
|
||||||
|
-> decltype(operator co_await(static_cast<T&&>(value)))
|
||||||
|
{
|
||||||
|
return operator co_await(static_cast<T&&>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename T,
|
||||||
|
std::enable_if_t<cppcoro::detail::is_awaiter<T&&>::value, int> = 0>
|
||||||
|
T&& get_awaiter_impl(T&& value, cppcoro::detail::any) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<T&&>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto get_awaiter(T&& value)
|
||||||
|
noexcept(noexcept(detail::get_awaiter_impl(static_cast<T&&>(value), 123)))
|
||||||
|
-> decltype(detail::get_awaiter_impl(static_cast<T&&>(value), 123))
|
||||||
|
{
|
||||||
|
return detail::get_awaiter_impl(static_cast<T&&>(value), 123);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
struct is_coroutine_handle
|
||||||
|
: std::false_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<typename PROMISE>
|
||||||
|
struct is_coroutine_handle<cppcoro::coroutine_handle<PROMISE>>
|
||||||
|
: std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
// NOTE: We're accepting a return value of coroutine_handle<P> here
|
||||||
|
// which is an extension supported by Clang which is not yet part of
|
||||||
|
// the C++ coroutines TS.
|
||||||
|
template<typename T>
|
||||||
|
struct is_valid_await_suspend_return_value : std::disjunction<
|
||||||
|
std::is_void<T>,
|
||||||
|
std::is_same<T, bool>,
|
||||||
|
is_coroutine_handle<T>>
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<typename T, typename = std::void_t<>>
|
||||||
|
struct is_awaiter : std::false_type {};
|
||||||
|
|
||||||
|
// NOTE: We're testing whether await_suspend() will be callable using an
|
||||||
|
// arbitrary coroutine_handle here by checking if it supports being passed
|
||||||
|
// a coroutine_handle<void>. This may result in a false-result for some
|
||||||
|
// types which are only awaitable within a certain context.
|
||||||
|
template<typename T>
|
||||||
|
struct is_awaiter<T, std::void_t<
|
||||||
|
decltype(std::declval<T>().await_ready()),
|
||||||
|
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>())),
|
||||||
|
decltype(std::declval<T>().await_resume())>> :
|
||||||
|
std::conjunction<
|
||||||
|
std::is_constructible<bool, decltype(std::declval<T>().await_ready())>,
|
||||||
|
detail::is_valid_await_suspend_return_value<
|
||||||
|
decltype(std::declval<T>().await_suspend(std::declval<cppcoro::coroutine_handle<>>()))>>
|
||||||
|
{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <span>
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_LINUX || (CPPCORO_OS_WINNT >= 0x0602)
|
||||||
|
# include <atomic>
|
||||||
|
# include <cstdint>
|
||||||
|
#elif CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
#else
|
||||||
|
# include <mutex>
|
||||||
|
# include <condition_variable>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class io_service;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class lightweight_manual_reset_event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
lightweight_manual_reset_event(bool initiallySet = false);
|
||||||
|
|
||||||
|
~lightweight_manual_reset_event();
|
||||||
|
|
||||||
|
void set() noexcept;
|
||||||
|
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
void wait() noexcept;
|
||||||
|
void wait(std::span<io_service> srvs, std::chrono::system_clock::duration step) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if CPPCORO_OS_LINUX
|
||||||
|
std::atomic<int> m_value;
|
||||||
|
#elif CPPCORO_OS_WINNT >= 0x0602
|
||||||
|
// Windows 8 or newer we can use WaitOnAddress()
|
||||||
|
std::atomic<std::uint8_t> m_value;
|
||||||
|
#elif CPPCORO_OS_WINNT
|
||||||
|
// Before Windows 8 we need to use a WIN32 manual reset event.
|
||||||
|
cppcoro::detail::win32::handle_t m_eventHandle;
|
||||||
|
#else
|
||||||
|
// For other platforms that don't have a native futex
|
||||||
|
// or manual reset event we can just use a std::mutex
|
||||||
|
// and std::condition_variable to perform the wait.
|
||||||
|
// Not so lightweight, but should be portable to all platforms.
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::condition_variable m_cv;
|
||||||
|
bool m_isSet;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_MANUAL_LIFETIME_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace cppcoro::detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
struct manual_lifetime
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
manual_lifetime() noexcept {}
|
||||||
|
~manual_lifetime() noexcept {}
|
||||||
|
|
||||||
|
manual_lifetime(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime(manual_lifetime&&) = delete;
|
||||||
|
manual_lifetime& operator=(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime& operator=(manual_lifetime&&) = delete;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible_v<T, Args&&...>> construct(Args&&... args)
|
||||||
|
noexcept(std::is_nothrow_constructible_v<T, Args&&...>)
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(std::addressof(m_value))) T(static_cast<Args&&>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destruct() noexcept(std::is_nothrow_destructible_v<T>)
|
||||||
|
{
|
||||||
|
m_value.~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::add_pointer_t<T> operator->() noexcept { return std::addressof(**this); }
|
||||||
|
std::add_pointer_t<const T> operator->() const noexcept { return std::addressof(**this); }
|
||||||
|
|
||||||
|
T& operator*() & noexcept { return m_value; }
|
||||||
|
const T& operator*() const & noexcept { return m_value; }
|
||||||
|
T&& operator*() && noexcept { return static_cast<T&&>(m_value); }
|
||||||
|
const T&& operator*() const && noexcept { return static_cast<const T&&>(m_value); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
T m_value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct manual_lifetime<T&>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
manual_lifetime() noexcept {}
|
||||||
|
~manual_lifetime() noexcept {}
|
||||||
|
|
||||||
|
manual_lifetime(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime(manual_lifetime&&) = delete;
|
||||||
|
manual_lifetime& operator=(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime& operator=(manual_lifetime&&) = delete;
|
||||||
|
|
||||||
|
void construct(T& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destruct() noexcept {}
|
||||||
|
|
||||||
|
T* operator->() noexcept { return m_value; }
|
||||||
|
const T* operator->() const noexcept { return m_value; }
|
||||||
|
|
||||||
|
T& operator*() noexcept { return *m_value; }
|
||||||
|
const T& operator*() const noexcept { return *m_value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct manual_lifetime<T&&>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
manual_lifetime() noexcept {}
|
||||||
|
~manual_lifetime() noexcept {}
|
||||||
|
|
||||||
|
manual_lifetime(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime(manual_lifetime&&) = delete;
|
||||||
|
manual_lifetime& operator=(const manual_lifetime&) = delete;
|
||||||
|
manual_lifetime& operator=(manual_lifetime&&) = delete;
|
||||||
|
|
||||||
|
void construct(T&& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destruct() noexcept {}
|
||||||
|
|
||||||
|
T* operator->() noexcept { return m_value; }
|
||||||
|
const T* operator->() const noexcept { return m_value; }
|
||||||
|
|
||||||
|
T& operator*() & noexcept { return *m_value; }
|
||||||
|
const T& operator*() const & noexcept { return *m_value; }
|
||||||
|
T&& operator*() && noexcept { return static_cast<T&&>(*m_value); }
|
||||||
|
const T&& operator*() const && noexcept { return static_cast<const T&&>(*m_value); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct manual_lifetime<void>
|
||||||
|
{
|
||||||
|
void construct() noexcept {}
|
||||||
|
void destruct() noexcept {}
|
||||||
|
void operator*() const noexcept {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
struct remove_rvalue_reference
|
||||||
|
{
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct remove_rvalue_reference<T&&>
|
||||||
|
{
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_SYNC_WAIT_TASK_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename RESULT>
|
||||||
|
class sync_wait_task;
|
||||||
|
|
||||||
|
template<typename RESULT>
|
||||||
|
class sync_wait_task_promise final
|
||||||
|
{
|
||||||
|
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<RESULT>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using reference = RESULT&&;
|
||||||
|
|
||||||
|
sync_wait_task_promise() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
void start(detail::lightweight_manual_reset_event& event)
|
||||||
|
{
|
||||||
|
m_event = &event;
|
||||||
|
coroutine_handle_t::from_promise(*this).resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return coroutine_handle_t::from_promise(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept
|
||||||
|
{
|
||||||
|
class completion_notifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
void await_suspend(coroutine_handle_t coroutine) const noexcept
|
||||||
|
{
|
||||||
|
coroutine.promise().m_event->set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return completion_notifier{};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
|
||||||
|
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||||
|
// See comment in make_sync_wait_task below.
|
||||||
|
template<typename Awaitable>
|
||||||
|
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||||
|
{
|
||||||
|
return static_cast<Awaitable&&>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct get_promise_t {};
|
||||||
|
static constexpr get_promise_t get_promise = {};
|
||||||
|
|
||||||
|
auto await_transform(get_promise_t)
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
awaiter(sync_wait_task_promise* promise) noexcept : m_promise(promise) {}
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
|
||||||
|
sync_wait_task_promise& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return *m_promise;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
sync_wait_task_promise* m_promise;
|
||||||
|
};
|
||||||
|
return awaiter{ this };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto yield_value(reference result) noexcept
|
||||||
|
{
|
||||||
|
m_result = std::addressof(result);
|
||||||
|
return final_suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void() noexcept
|
||||||
|
{
|
||||||
|
// The coroutine should have either yielded a value or thrown
|
||||||
|
// an exception in which case it should have bypassed return_void().
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception()
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
reference result()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<reference>(*m_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
detail::lightweight_manual_reset_event* m_event;
|
||||||
|
std::remove_reference_t<RESULT>* m_result;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class sync_wait_task_promise<void>
|
||||||
|
{
|
||||||
|
using coroutine_handle_t = cppcoro::coroutine_handle<sync_wait_task_promise<void>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sync_wait_task_promise() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
void start(detail::lightweight_manual_reset_event& event)
|
||||||
|
{
|
||||||
|
m_event = &event;
|
||||||
|
coroutine_handle_t::from_promise(*this).resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return coroutine_handle_t::from_promise(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept
|
||||||
|
{
|
||||||
|
class completion_notifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
void await_suspend(coroutine_handle_t coroutine) const noexcept
|
||||||
|
{
|
||||||
|
coroutine.promise().m_event->set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return completion_notifier{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void() {}
|
||||||
|
|
||||||
|
void unhandled_exception()
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void result()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
detail::lightweight_manual_reset_event* m_event;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename RESULT>
|
||||||
|
class sync_wait_task final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using promise_type = sync_wait_task_promise<RESULT>;
|
||||||
|
|
||||||
|
using coroutine_handle_t = cppcoro::coroutine_handle<promise_type>;
|
||||||
|
|
||||||
|
sync_wait_task(coroutine_handle_t coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sync_wait_task(sync_wait_task&& other) noexcept
|
||||||
|
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
|
||||||
|
{}
|
||||||
|
|
||||||
|
~sync_wait_task()
|
||||||
|
{
|
||||||
|
if (m_coroutine) m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_wait_task(const sync_wait_task&) = delete;
|
||||||
|
sync_wait_task& operator=(const sync_wait_task&) = delete;
|
||||||
|
|
||||||
|
void start(lightweight_manual_reset_event& event) noexcept
|
||||||
|
{
|
||||||
|
m_coroutine.promise().start(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) result()
|
||||||
|
{
|
||||||
|
return m_coroutine.promise().result();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
coroutine_handle_t m_coroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
|
||||||
|
// HACK: Work around bug in MSVC where passing a parameter by universal reference
|
||||||
|
// results in an error when passed a move-only type, complaining that the copy-constructor
|
||||||
|
// has been deleted. The parameter should be passed by reference and the compiler should
|
||||||
|
// notcalling the copy-constructor for the argument
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
|
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
|
||||||
|
{
|
||||||
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await sync_wait_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||||
|
|
||||||
|
//co_yield co_await std::forward<AWAITABLE>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
|
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
|
||||||
|
{
|
||||||
|
co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
|
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE&& awaitable)
|
||||||
|
{
|
||||||
|
co_yield co_await std::forward<AWAITABLE>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
|
sync_wait_task<void> make_sync_wait_task(AWAITABLE&& awaitable)
|
||||||
|
{
|
||||||
|
co_await std::forward<AWAITABLE>(awaitable);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
struct unwrap_reference
|
||||||
|
{
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct unwrap_reference<std::reference_wrapper<T>>
|
||||||
|
{
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using unwrap_reference_t = typename unwrap_reference<T>::type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
struct void_value {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class when_all_counter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
when_all_counter(std::size_t count) noexcept
|
||||||
|
: m_count(count + 1)
|
||||||
|
, m_awaitingCoroutine(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
// We consider this complete if we're asking whether it's ready
|
||||||
|
// after a coroutine has already been registered.
|
||||||
|
return static_cast<bool>(m_awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notify_awaitable_completed() noexcept
|
||||||
|
{
|
||||||
|
if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1)
|
||||||
|
{
|
||||||
|
m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
std::atomic<std::size_t> m_count;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_WHEN_ALL_READY_AWAITABLE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/detail/when_all_counter.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename TASK_CONTAINER>
|
||||||
|
class when_all_ready_awaitable;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class when_all_ready_awaitable<std::tuple<>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
constexpr when_all_ready_awaitable() noexcept {}
|
||||||
|
explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {}
|
||||||
|
|
||||||
|
constexpr bool await_ready() const noexcept { return true; }
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
|
||||||
|
std::tuple<> await_resume() const noexcept { return {}; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... TASKS>
|
||||||
|
class when_all_ready_awaitable<std::tuple<TASKS...>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit when_all_ready_awaitable(TASKS&&... tasks)
|
||||||
|
noexcept(std::conjunction_v<std::is_nothrow_move_constructible<TASKS>...>)
|
||||||
|
: m_counter(sizeof...(TASKS))
|
||||||
|
, m_tasks(std::move(tasks)...)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit when_all_ready_awaitable(std::tuple<TASKS...>&& tasks)
|
||||||
|
noexcept(std::is_nothrow_move_constructible_v<std::tuple<TASKS...>>)
|
||||||
|
: m_counter(sizeof...(TASKS))
|
||||||
|
, m_tasks(std::move(tasks))
|
||||||
|
{}
|
||||||
|
|
||||||
|
when_all_ready_awaitable(when_all_ready_awaitable&& other) noexcept
|
||||||
|
: m_counter(sizeof...(TASKS))
|
||||||
|
, m_tasks(std::move(other.m_tasks))
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto operator co_await() & noexcept
|
||||||
|
{
|
||||||
|
struct awaiter
|
||||||
|
{
|
||||||
|
awaiter(when_all_ready_awaitable& awaitable) noexcept
|
||||||
|
: m_awaitable(awaitable)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<TASKS...>& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.m_tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
when_all_ready_awaitable& m_awaitable;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() && noexcept
|
||||||
|
{
|
||||||
|
struct awaiter
|
||||||
|
{
|
||||||
|
awaiter(when_all_ready_awaitable& awaitable) noexcept
|
||||||
|
: m_awaitable(awaitable)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<TASKS...>&& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return std::move(m_awaitable.m_tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
when_all_ready_awaitable& m_awaitable;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_counter.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
start_tasks(std::make_integer_sequence<std::size_t, sizeof...(TASKS)>{});
|
||||||
|
return m_counter.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t... INDICES>
|
||||||
|
void start_tasks(std::integer_sequence<std::size_t, INDICES...>) noexcept
|
||||||
|
{
|
||||||
|
(void)std::initializer_list<int>{
|
||||||
|
(std::get<INDICES>(m_tasks).start(m_counter), 0)...
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
when_all_counter m_counter;
|
||||||
|
std::tuple<TASKS...> m_tasks;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TASK_CONTAINER>
|
||||||
|
class when_all_ready_awaitable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit when_all_ready_awaitable(TASK_CONTAINER&& tasks) noexcept
|
||||||
|
: m_counter(tasks.size())
|
||||||
|
, m_tasks(std::forward<TASK_CONTAINER>(tasks))
|
||||||
|
{}
|
||||||
|
|
||||||
|
when_all_ready_awaitable(when_all_ready_awaitable&& other)
|
||||||
|
noexcept(std::is_nothrow_move_constructible_v<TASK_CONTAINER>)
|
||||||
|
: m_counter(other.m_tasks.size())
|
||||||
|
, m_tasks(std::move(other.m_tasks))
|
||||||
|
{}
|
||||||
|
|
||||||
|
when_all_ready_awaitable(const when_all_ready_awaitable&) = delete;
|
||||||
|
when_all_ready_awaitable& operator=(const when_all_ready_awaitable&) = delete;
|
||||||
|
|
||||||
|
auto operator co_await() & noexcept
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
awaiter(when_all_ready_awaitable& awaitable)
|
||||||
|
: m_awaitable(awaitable)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
TASK_CONTAINER& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.m_tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
when_all_ready_awaitable& m_awaitable;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto operator co_await() && noexcept
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
awaiter(when_all_ready_awaitable& awaitable)
|
||||||
|
: m_awaitable(awaitable)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_awaitable.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
TASK_CONTAINER&& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return std::move(m_awaitable.m_tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
when_all_ready_awaitable& m_awaitable;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_counter.is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
for (auto&& task : m_tasks)
|
||||||
|
{
|
||||||
|
task.start(m_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_counter.try_await(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
when_all_counter m_counter;
|
||||||
|
TASK_CONTAINER m_tasks;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_WHEN_ALL_TASK_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/when_all_counter.hpp>
|
||||||
|
#include <cppcoro/detail/void_value.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename TASK_CONTAINER>
|
||||||
|
class when_all_ready_awaitable;
|
||||||
|
|
||||||
|
template<typename RESULT>
|
||||||
|
class when_all_task;
|
||||||
|
|
||||||
|
template<typename RESULT>
|
||||||
|
class when_all_task_promise final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<RESULT>>;
|
||||||
|
|
||||||
|
when_all_task_promise() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return coroutine_handle_t::from_promise(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept
|
||||||
|
{
|
||||||
|
class completion_notifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
void await_suspend(coroutine_handle_t coro) const noexcept
|
||||||
|
{
|
||||||
|
coro.promise().m_counter->notify_awaitable_completed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return completion_notifier{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void() noexcept
|
||||||
|
{
|
||||||
|
// We should have either suspended at co_yield point or
|
||||||
|
// an exception was thrown before running off the end of
|
||||||
|
// the coroutine.
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
|
||||||
|
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||||
|
// See comment in make_when_all_task below.
|
||||||
|
template<typename Awaitable>
|
||||||
|
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||||
|
{
|
||||||
|
return static_cast<Awaitable&&>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct get_promise_t {};
|
||||||
|
static constexpr get_promise_t get_promise = {};
|
||||||
|
|
||||||
|
auto await_transform(get_promise_t)
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
awaiter(when_all_task_promise* promise) noexcept : m_promise(promise) {}
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<>) noexcept {}
|
||||||
|
when_all_task_promise& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return *m_promise;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
when_all_task_promise* m_promise;
|
||||||
|
};
|
||||||
|
return awaiter{ this };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
auto yield_value(RESULT&& result) noexcept
|
||||||
|
{
|
||||||
|
m_result = std::addressof(result);
|
||||||
|
return final_suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(when_all_counter& counter) noexcept
|
||||||
|
{
|
||||||
|
m_counter = &counter;
|
||||||
|
coroutine_handle_t::from_promise(*this).resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
RESULT& result() &
|
||||||
|
{
|
||||||
|
rethrow_if_exception();
|
||||||
|
return *m_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RESULT&& result() &&
|
||||||
|
{
|
||||||
|
rethrow_if_exception();
|
||||||
|
return std::forward<RESULT>(*m_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void rethrow_if_exception()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when_all_counter* m_counter;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
std::add_pointer_t<RESULT> m_result;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class when_all_task_promise<void> final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using coroutine_handle_t = cppcoro::coroutine_handle<when_all_task_promise<void>>;
|
||||||
|
|
||||||
|
when_all_task_promise() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return coroutine_handle_t::from_promise(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept
|
||||||
|
{
|
||||||
|
class completion_notifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
void await_suspend(coroutine_handle_t coro) const noexcept
|
||||||
|
{
|
||||||
|
coro.promise().m_counter->notify_awaitable_completed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return completion_notifier{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(when_all_counter& counter) noexcept
|
||||||
|
{
|
||||||
|
m_counter = &counter;
|
||||||
|
coroutine_handle_t::from_promise(*this).resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
void result()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
when_all_counter* m_counter;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename RESULT>
|
||||||
|
class when_all_task final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using promise_type = when_all_task_promise<RESULT>;
|
||||||
|
|
||||||
|
using coroutine_handle_t = typename promise_type::coroutine_handle_t;
|
||||||
|
|
||||||
|
when_all_task(coroutine_handle_t coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
when_all_task(when_all_task&& other) noexcept
|
||||||
|
: m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{}))
|
||||||
|
{}
|
||||||
|
|
||||||
|
~when_all_task()
|
||||||
|
{
|
||||||
|
if (m_coroutine) m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
when_all_task(const when_all_task&) = delete;
|
||||||
|
when_all_task& operator=(const when_all_task&) = delete;
|
||||||
|
|
||||||
|
decltype(auto) result() &
|
||||||
|
{
|
||||||
|
return m_coroutine.promise().result();
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) result() &&
|
||||||
|
{
|
||||||
|
return std::move(m_coroutine.promise()).result();
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) non_void_result() &
|
||||||
|
{
|
||||||
|
if constexpr (std::is_void_v<decltype(this->result())>)
|
||||||
|
{
|
||||||
|
this->result();
|
||||||
|
return void_value{};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this->result();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) non_void_result() &&
|
||||||
|
{
|
||||||
|
if constexpr (std::is_void_v<decltype(this->result())>)
|
||||||
|
{
|
||||||
|
std::move(*this).result();
|
||||||
|
return void_value{};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::move(*this).result();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<typename TASK_CONTAINER>
|
||||||
|
friend class when_all_ready_awaitable;
|
||||||
|
|
||||||
|
void start(when_all_counter& counter) noexcept
|
||||||
|
{
|
||||||
|
m_coroutine.promise().start(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutine_handle_t m_coroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
|
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
|
||||||
|
{
|
||||||
|
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
|
||||||
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||||
|
#else
|
||||||
|
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t,
|
||||||
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
|
when_all_task<void> make_when_all_task(AWAITABLE awaitable)
|
||||||
|
{
|
||||||
|
co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
|
||||||
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
|
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
|
||||||
|
{
|
||||||
|
#if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000
|
||||||
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield and never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await awaitable.get());
|
||||||
|
#else
|
||||||
|
co_yield co_await awaitable.get();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename cppcoro::awaitable_traits<AWAITABLE&>::await_result_t,
|
||||||
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
|
when_all_task<void> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
|
||||||
|
{
|
||||||
|
co_await awaitable.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_WIN32_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_WIN32_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#if !CPPCORO_OS_WINNT
|
||||||
|
# error <cppcoro/detail/win32.hpp> is only supported on the Windows platform.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct _OVERLAPPED;
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
namespace win32
|
||||||
|
{
|
||||||
|
using handle_t = void*;
|
||||||
|
using ulongptr_t = std::uintptr_t;
|
||||||
|
using longptr_t = std::intptr_t;
|
||||||
|
using dword_t = unsigned long;
|
||||||
|
using socket_t = std::uintptr_t;
|
||||||
|
using ulong_t = unsigned long;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4201) // Non-standard anonymous struct/union
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Structure needs to correspond exactly to the builtin
|
||||||
|
/// _OVERLAPPED structure from Windows.h.
|
||||||
|
struct overlapped
|
||||||
|
{
|
||||||
|
ulongptr_t Internal;
|
||||||
|
ulongptr_t InternalHigh;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
dword_t Offset;
|
||||||
|
dword_t OffsetHigh;
|
||||||
|
};
|
||||||
|
void* Pointer;
|
||||||
|
};
|
||||||
|
handle_t hEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct wsabuf
|
||||||
|
{
|
||||||
|
constexpr wsabuf() noexcept
|
||||||
|
: len(0)
|
||||||
|
, buf(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr wsabuf(void* ptr, std::size_t size)
|
||||||
|
: len(size <= ulong_t(-1) ? ulong_t(size) : ulong_t(-1))
|
||||||
|
, buf(static_cast<char*>(ptr))
|
||||||
|
{}
|
||||||
|
|
||||||
|
ulong_t len;
|
||||||
|
char* buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct io_state : win32::overlapped
|
||||||
|
{
|
||||||
|
using callback_type = void(
|
||||||
|
io_state* state,
|
||||||
|
win32::dword_t errorCode,
|
||||||
|
win32::dword_t numberOfBytesTransferred,
|
||||||
|
win32::ulongptr_t completionKey);
|
||||||
|
|
||||||
|
io_state(callback_type* callback = nullptr) noexcept
|
||||||
|
: io_state(std::uint64_t(0), callback)
|
||||||
|
{}
|
||||||
|
|
||||||
|
io_state(void* pointer, callback_type* callback) noexcept
|
||||||
|
: m_callback(callback)
|
||||||
|
{
|
||||||
|
this->Internal = 0;
|
||||||
|
this->InternalHigh = 0;
|
||||||
|
this->Pointer = pointer;
|
||||||
|
this->hEvent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
io_state(std::uint64_t offset, callback_type* callback) noexcept
|
||||||
|
: m_callback(callback)
|
||||||
|
{
|
||||||
|
this->Internal = 0;
|
||||||
|
this->InternalHigh = 0;
|
||||||
|
this->Offset = static_cast<dword_t>(offset);
|
||||||
|
this->OffsetHigh = static_cast<dword_t>(offset >> 32);
|
||||||
|
this->hEvent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback_type* m_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
class safe_handle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
safe_handle()
|
||||||
|
: m_handle(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit safe_handle(handle_t handle)
|
||||||
|
: m_handle(handle)
|
||||||
|
{}
|
||||||
|
|
||||||
|
safe_handle(const safe_handle& other) = delete;
|
||||||
|
|
||||||
|
safe_handle(safe_handle&& other) noexcept
|
||||||
|
: m_handle(other.m_handle)
|
||||||
|
{
|
||||||
|
other.m_handle = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~safe_handle()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_handle& operator=(safe_handle handle) noexcept
|
||||||
|
{
|
||||||
|
swap(handle);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr handle_t handle() const { return m_handle; }
|
||||||
|
|
||||||
|
/// Calls CloseHandle() and sets the handle to NULL.
|
||||||
|
void close() noexcept;
|
||||||
|
|
||||||
|
void swap(safe_handle& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(m_handle, other.m_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const safe_handle& other) const
|
||||||
|
{
|
||||||
|
return m_handle == other.m_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const safe_handle& other) const
|
||||||
|
{
|
||||||
|
return m_handle != other.m_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(handle_t handle) const
|
||||||
|
{
|
||||||
|
return m_handle == handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(handle_t handle) const
|
||||||
|
{
|
||||||
|
return m_handle != handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
handle_t m_handle;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_DETAIL_WIN32_OVERLAPPED_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/operation_cancelled.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/win32.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <system_error>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class win32_overlapped_operation_base
|
||||||
|
: protected detail::win32::io_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
win32_overlapped_operation_base(
|
||||||
|
detail::win32::io_state::callback_type* callback) noexcept
|
||||||
|
: detail::win32::io_state(callback)
|
||||||
|
, m_errorCode(0)
|
||||||
|
, m_numberOfBytesTransferred(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
win32_overlapped_operation_base(
|
||||||
|
void* pointer,
|
||||||
|
detail::win32::io_state::callback_type* callback) noexcept
|
||||||
|
: detail::win32::io_state(pointer, callback)
|
||||||
|
, m_errorCode(0)
|
||||||
|
, m_numberOfBytesTransferred(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
win32_overlapped_operation_base(
|
||||||
|
std::uint64_t offset,
|
||||||
|
detail::win32::io_state::callback_type* callback) noexcept
|
||||||
|
: detail::win32::io_state(offset, callback)
|
||||||
|
, m_errorCode(0)
|
||||||
|
, m_numberOfBytesTransferred(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
_OVERLAPPED* get_overlapped() noexcept
|
||||||
|
{
|
||||||
|
return reinterpret_cast<_OVERLAPPED*>(
|
||||||
|
static_cast<detail::win32::overlapped*>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t get_result()
|
||||||
|
{
|
||||||
|
if (m_errorCode != 0)
|
||||||
|
{
|
||||||
|
throw std::system_error{
|
||||||
|
static_cast<int>(m_errorCode),
|
||||||
|
std::system_category()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_numberOfBytesTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::win32::dword_t m_errorCode;
|
||||||
|
detail::win32::dword_t m_numberOfBytesTransferred;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename OPERATION>
|
||||||
|
class win32_overlapped_operation
|
||||||
|
: protected win32_overlapped_operation_base
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
win32_overlapped_operation() noexcept
|
||||||
|
: win32_overlapped_operation_base(
|
||||||
|
&win32_overlapped_operation::on_operation_completed)
|
||||||
|
{}
|
||||||
|
|
||||||
|
win32_overlapped_operation(void* pointer) noexcept
|
||||||
|
: win32_overlapped_operation_base(
|
||||||
|
pointer,
|
||||||
|
&win32_overlapped_operation::on_operation_completed)
|
||||||
|
{}
|
||||||
|
|
||||||
|
win32_overlapped_operation(std::uint64_t offset) noexcept
|
||||||
|
: win32_overlapped_operation_base(
|
||||||
|
offset,
|
||||||
|
&win32_overlapped_operation::on_operation_completed)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
CPPCORO_NOINLINE
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
|
||||||
|
{
|
||||||
|
static_assert(std::is_base_of_v<win32_overlapped_operation, OPERATION>);
|
||||||
|
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
return static_cast<OPERATION*>(this)->try_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
{
|
||||||
|
return static_cast<OPERATION*>(this)->get_result();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static void on_operation_completed(
|
||||||
|
detail::win32::io_state* ioState,
|
||||||
|
detail::win32::dword_t errorCode,
|
||||||
|
detail::win32::dword_t numberOfBytesTransferred,
|
||||||
|
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
|
||||||
|
{
|
||||||
|
auto* operation = static_cast<win32_overlapped_operation*>(ioState);
|
||||||
|
operation->m_errorCode = errorCode;
|
||||||
|
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
|
||||||
|
operation->m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename OPERATION>
|
||||||
|
class win32_overlapped_operation_cancellable
|
||||||
|
: protected win32_overlapped_operation_base
|
||||||
|
{
|
||||||
|
// ERROR_OPERATION_ABORTED value from <Windows.h>
|
||||||
|
static constexpr detail::win32::dword_t error_operation_aborted = 995L;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
win32_overlapped_operation_cancellable(cancellation_token&& ct) noexcept
|
||||||
|
: win32_overlapped_operation_base(&win32_overlapped_operation_cancellable::on_operation_completed)
|
||||||
|
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
|
||||||
|
, m_cancellationToken(std::move(ct))
|
||||||
|
{
|
||||||
|
m_errorCode = error_operation_aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
win32_overlapped_operation_cancellable(
|
||||||
|
void* pointer,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: win32_overlapped_operation_base(pointer, &win32_overlapped_operation_cancellable::on_operation_completed)
|
||||||
|
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
|
||||||
|
, m_cancellationToken(std::move(ct))
|
||||||
|
{
|
||||||
|
m_errorCode = error_operation_aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
win32_overlapped_operation_cancellable(
|
||||||
|
std::uint64_t offset,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: win32_overlapped_operation_base(offset, &win32_overlapped_operation_cancellable::on_operation_completed)
|
||||||
|
, m_state(ct.is_cancellation_requested() ? state::completed : state::not_started)
|
||||||
|
, m_cancellationToken(std::move(ct))
|
||||||
|
{
|
||||||
|
m_errorCode = error_operation_aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
win32_overlapped_operation_cancellable(
|
||||||
|
win32_overlapped_operation_cancellable&& other) noexcept
|
||||||
|
: win32_overlapped_operation_base(std::move(other))
|
||||||
|
, m_state(other.m_state.load(std::memory_order_relaxed))
|
||||||
|
, m_cancellationToken(std::move(other.m_cancellationToken))
|
||||||
|
{
|
||||||
|
assert(m_errorCode == other.m_errorCode);
|
||||||
|
assert(m_numberOfBytesTransferred == other.m_numberOfBytesTransferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_state.load(std::memory_order_relaxed) == state::completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
CPPCORO_NOINLINE
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine)
|
||||||
|
{
|
||||||
|
static_assert(std::is_base_of_v<win32_overlapped_operation_cancellable, OPERATION>);
|
||||||
|
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
|
||||||
|
// TRICKY: Register cancellation callback before starting the operation
|
||||||
|
// in case the callback registration throws due to insufficient
|
||||||
|
// memory. We need to make sure that the logic that occurs after
|
||||||
|
// starting the operation is noexcept, otherwise we run into the
|
||||||
|
// problem of not being able to cancel the started operation and
|
||||||
|
// the dilemma of what to do with the exception.
|
||||||
|
//
|
||||||
|
// However, doing this means that the cancellation callback may run
|
||||||
|
// prior to returning below so in the case that cancellation may
|
||||||
|
// occur we defer setting the state to 'started' until after
|
||||||
|
// the operation has finished starting. The cancellation callback
|
||||||
|
// will only attempt to request cancellation of the operation with
|
||||||
|
// CancelIoEx() once the state has been set to 'started'.
|
||||||
|
const bool canBeCancelled = m_cancellationToken.can_be_cancelled();
|
||||||
|
if (canBeCancelled)
|
||||||
|
{
|
||||||
|
m_cancellationCallback.emplace(
|
||||||
|
std::move(m_cancellationToken),
|
||||||
|
[this] { this->on_cancellation_requested(); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_state.store(state::started, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now start the operation.
|
||||||
|
const bool willCompleteAsynchronously = static_cast<OPERATION*>(this)->try_start();
|
||||||
|
if (!willCompleteAsynchronously)
|
||||||
|
{
|
||||||
|
// Operation completed synchronously, resume awaiting coroutine immediately.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canBeCancelled)
|
||||||
|
{
|
||||||
|
// Need to flag that the operation has finished starting now.
|
||||||
|
|
||||||
|
// However, the operation may have completed concurrently on
|
||||||
|
// another thread, transitioning directly from not_started -> complete.
|
||||||
|
// Or it may have had the cancellation callback execute and transition
|
||||||
|
// from not_started -> cancellation_requested. We use a compare-exchange
|
||||||
|
// to determine a winner between these potential racing cases.
|
||||||
|
state oldState = state::not_started;
|
||||||
|
if (!m_state.compare_exchange_strong(
|
||||||
|
oldState,
|
||||||
|
state::started,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
if (oldState == state::cancellation_requested)
|
||||||
|
{
|
||||||
|
// Request the operation be cancelled.
|
||||||
|
// Note that it may have already completed on a background
|
||||||
|
// thread by now so this request for cancellation may end up
|
||||||
|
// being ignored.
|
||||||
|
static_cast<OPERATION*>(this)->cancel();
|
||||||
|
|
||||||
|
if (!m_state.compare_exchange_strong(
|
||||||
|
oldState,
|
||||||
|
state::started,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
assert(oldState == state::completed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(oldState == state::completed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
{
|
||||||
|
// Free memory used by the cancellation callback now that the operation
|
||||||
|
// has completed rather than waiting until the operation object destructs.
|
||||||
|
// eg. If the operation is passed to when_all() then the operation object
|
||||||
|
// may not be destructed until all of the operations complete.
|
||||||
|
m_cancellationCallback.reset();
|
||||||
|
|
||||||
|
if (m_errorCode == error_operation_aborted)
|
||||||
|
{
|
||||||
|
throw operation_cancelled{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<OPERATION*>(this)->get_result();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum class state
|
||||||
|
{
|
||||||
|
not_started,
|
||||||
|
started,
|
||||||
|
cancellation_requested,
|
||||||
|
completed
|
||||||
|
};
|
||||||
|
|
||||||
|
void on_cancellation_requested() noexcept
|
||||||
|
{
|
||||||
|
auto oldState = m_state.load(std::memory_order_acquire);
|
||||||
|
if (oldState == state::not_started)
|
||||||
|
{
|
||||||
|
// This callback is running concurrently with await_suspend().
|
||||||
|
// The call to start the operation may not have returned yet so
|
||||||
|
// we can't safely request cancellation of it. Instead we try to
|
||||||
|
// notify the await_suspend() thread by transitioning the state
|
||||||
|
// to state::cancellation_requested so that the await_suspend()
|
||||||
|
// thread can request cancellation after it has finished starting
|
||||||
|
// the operation.
|
||||||
|
const bool transferredCancelResponsibility =
|
||||||
|
m_state.compare_exchange_strong(
|
||||||
|
oldState,
|
||||||
|
state::cancellation_requested,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_acquire);
|
||||||
|
if (transferredCancelResponsibility)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No point requesting cancellation if the operation has already completed.
|
||||||
|
if (oldState != state::completed)
|
||||||
|
{
|
||||||
|
static_cast<OPERATION*>(this)->cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_operation_completed(
|
||||||
|
detail::win32::io_state* ioState,
|
||||||
|
detail::win32::dword_t errorCode,
|
||||||
|
detail::win32::dword_t numberOfBytesTransferred,
|
||||||
|
[[maybe_unused]] detail::win32::ulongptr_t completionKey) noexcept
|
||||||
|
{
|
||||||
|
auto* operation = static_cast<win32_overlapped_operation_cancellable*>(ioState);
|
||||||
|
|
||||||
|
operation->m_errorCode = errorCode;
|
||||||
|
operation->m_numberOfBytesTransferred = numberOfBytesTransferred;
|
||||||
|
|
||||||
|
auto state = operation->m_state.load(std::memory_order_acquire);
|
||||||
|
if (state == state::started)
|
||||||
|
{
|
||||||
|
operation->m_state.store(state::completed, std::memory_order_relaxed);
|
||||||
|
operation->m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We are racing with await_suspend() call suspending.
|
||||||
|
// Try to mark it as completed using an atomic exchange and look
|
||||||
|
// at the previous value to determine whether the coroutine suspended
|
||||||
|
// first (in which case we resume it now) or we marked it as completed
|
||||||
|
// first (in which case await_suspend() will return false and immediately
|
||||||
|
// resume the coroutine).
|
||||||
|
state = operation->m_state.exchange(
|
||||||
|
state::completed,
|
||||||
|
std::memory_order_acq_rel);
|
||||||
|
if (state == state::started)
|
||||||
|
{
|
||||||
|
// The await_suspend() method returned (or will return) 'true' and so
|
||||||
|
// we need to resume the coroutine.
|
||||||
|
operation->m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<state> m_state;
|
||||||
|
cppcoro::cancellation_token m_cancellationToken;
|
||||||
|
std::optional<cppcoro::cancellation_registration> m_cancellationCallback;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/file_open_mode.hpp>
|
||||||
|
#include <cppcoro/file_share_mode.hpp>
|
||||||
|
#include <cppcoro/file_buffering_mode.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cppcoro/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class io_service;
|
||||||
|
|
||||||
|
class file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file(file&& other) noexcept = default;
|
||||||
|
|
||||||
|
virtual ~file();
|
||||||
|
|
||||||
|
/// Get the size of the file in bytes.
|
||||||
|
std::uint64_t size() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
file(detail::win32::safe_handle&& fileHandle) noexcept;
|
||||||
|
|
||||||
|
static detail::win32::safe_handle open(
|
||||||
|
detail::win32::dword_t fileAccess,
|
||||||
|
io_service& ioService,
|
||||||
|
const cppcoro::filesystem::path& path,
|
||||||
|
file_open_mode openMode,
|
||||||
|
file_share_mode shareMode,
|
||||||
|
file_buffering_mode bufferingMode);
|
||||||
|
|
||||||
|
detail::win32::safe_handle m_fileHandle;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
enum class file_buffering_mode
|
||||||
|
{
|
||||||
|
default_ = 0,
|
||||||
|
sequential = 1,
|
||||||
|
random_access = 2,
|
||||||
|
unbuffered = 4,
|
||||||
|
write_through = 8,
|
||||||
|
temporary = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr file_buffering_mode operator&(file_buffering_mode a, file_buffering_mode b)
|
||||||
|
{
|
||||||
|
return static_cast<file_buffering_mode>(
|
||||||
|
static_cast<int>(a) & static_cast<int>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr file_buffering_mode operator|(file_buffering_mode a, file_buffering_mode b)
|
||||||
|
{
|
||||||
|
return static_cast<file_buffering_mode>(static_cast<int>(a) | static_cast<int>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
enum class file_open_mode
|
||||||
|
{
|
||||||
|
/// Open an existing file.
|
||||||
|
///
|
||||||
|
/// If file does not already exist when opening the file then raises
|
||||||
|
/// an exception.
|
||||||
|
open_existing,
|
||||||
|
|
||||||
|
/// Create a new file, overwriting an existing file if one exists.
|
||||||
|
///
|
||||||
|
/// If a file exists at the path then it is overwitten with a new file.
|
||||||
|
/// If no file exists at the path then a new one is created.
|
||||||
|
create_always,
|
||||||
|
|
||||||
|
/// Create a new file.
|
||||||
|
///
|
||||||
|
/// If the file already exists then raises an exception.
|
||||||
|
create_new,
|
||||||
|
|
||||||
|
/// Open the existing file if one exists, otherwise create a new empty
|
||||||
|
/// file.
|
||||||
|
create_or_open,
|
||||||
|
|
||||||
|
/// Open the existing file, truncating the file size to zero.
|
||||||
|
///
|
||||||
|
/// If the file does not exist then raises an exception.
|
||||||
|
truncate_existing
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class file_read_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_read_operation_impl(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_fileHandle(fileHandle)
|
||||||
|
, m_buffer(buffer)
|
||||||
|
, m_byteCount(byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
detail::win32::handle_t m_fileHandle;
|
||||||
|
void* m_buffer;
|
||||||
|
std::size_t m_byteCount;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class file_read_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<file_read_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_read_operation(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
std::uint64_t fileOffset,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation<file_read_operation>(fileOffset)
|
||||||
|
, m_impl(fileHandle, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<file_read_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
|
||||||
|
file_read_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class file_read_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_read_operation_cancellable(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
std::uint64_t fileOffset,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& cancellationToken) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>(
|
||||||
|
fileOffset, std::move(cancellationToken))
|
||||||
|
, m_impl(fileHandle, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_read_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
|
||||||
|
file_read_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
enum class file_share_mode
|
||||||
|
{
|
||||||
|
/// Don't allow any other processes to open the file concurrently.
|
||||||
|
none = 0,
|
||||||
|
|
||||||
|
/// Allow other processes to open the file in read-only mode
|
||||||
|
/// concurrently with this process opening the file.
|
||||||
|
read = 1,
|
||||||
|
|
||||||
|
/// Allow other processes to open the file in write-only mode
|
||||||
|
/// concurrently with this process opening the file.
|
||||||
|
write = 2,
|
||||||
|
|
||||||
|
/// Allow other processes to open the file in read and/or write mode
|
||||||
|
/// concurrently with this process opening the file.
|
||||||
|
read_write = read | write,
|
||||||
|
|
||||||
|
/// Allow other processes to delete the file while this process
|
||||||
|
/// has the file open.
|
||||||
|
delete_ = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr file_share_mode operator|(file_share_mode a, file_share_mode b)
|
||||||
|
{
|
||||||
|
return static_cast<file_share_mode>(
|
||||||
|
static_cast<int>(a) | static_cast<int>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr file_share_mode operator&(file_share_mode a, file_share_mode b)
|
||||||
|
{
|
||||||
|
return static_cast<file_share_mode>(
|
||||||
|
static_cast<int>(a) & static_cast<int>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class file_write_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_write_operation_impl(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_fileHandle(fileHandle)
|
||||||
|
, m_buffer(buffer)
|
||||||
|
, m_byteCount(byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
detail::win32::handle_t m_fileHandle;
|
||||||
|
const void* m_buffer;
|
||||||
|
std::size_t m_byteCount;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class file_write_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<file_write_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_write_operation(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
std::uint64_t fileOffset,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation<file_write_operation>(fileOffset)
|
||||||
|
, m_impl(fileHandle, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<file_write_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
|
||||||
|
file_write_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class file_write_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
file_write_operation_cancellable(
|
||||||
|
detail::win32::handle_t fileHandle,
|
||||||
|
std::uint64_t fileOffset,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>(fileOffset, std::move(ct))
|
||||||
|
, m_impl(fileHandle, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<file_write_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
|
||||||
|
file_write_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef CPPCORO_FILESYSTEM_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FILESYSTEM_HPP_INCLUDED
|
||||||
|
|
||||||
|
#if __has_include(<filesystem>)
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace cppcoro {
|
||||||
|
namespace filesystem = std::filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif __has_include(<experimental/filesystem>)
|
||||||
|
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
|
||||||
|
namespace cppcoro {
|
||||||
|
namespace filesystem = std::experimental::filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Cppcoro requires a C++20 compiler with filesystem support
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_FMAP_HPP_INCLUDED
|
||||||
|
#define CPPCORO_FMAP_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/is_awaitable.hpp>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename FUNC, typename AWAITABLE>
|
||||||
|
class fmap_awaiter
|
||||||
|
{
|
||||||
|
using awaiter_t = typename awaitable_traits<AWAITABLE&&>::awaiter_t;
|
||||||
|
FUNC&& m_func;
|
||||||
|
awaiter_t m_awaiter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
fmap_awaiter(FUNC&& func, AWAITABLE&& awaitable)
|
||||||
|
noexcept(
|
||||||
|
std::is_nothrow_move_constructible_v<awaiter_t> &&
|
||||||
|
noexcept(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable))))
|
||||||
|
: m_func(static_cast<FUNC&&>(func))
|
||||||
|
, m_awaiter(detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
decltype(auto) await_ready()
|
||||||
|
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_ready()))
|
||||||
|
{
|
||||||
|
return static_cast<awaiter_t&&>(m_awaiter).await_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename PROMISE>
|
||||||
|
decltype(auto) await_suspend(cppcoro::coroutine_handle<PROMISE> coro)
|
||||||
|
noexcept(noexcept(static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro))))
|
||||||
|
{
|
||||||
|
return static_cast<awaiter_t&&>(m_awaiter).await_suspend(std::move(coro));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
|
||||||
|
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func))))
|
||||||
|
{
|
||||||
|
static_cast<awaiter_t&&>(m_awaiter).await_resume();
|
||||||
|
return std::invoke(static_cast<FUNC&&>(m_func));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAIT_RESULT = decltype(std::declval<awaiter_t>().await_resume()),
|
||||||
|
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
noexcept(noexcept(std::invoke(static_cast<FUNC&&>(m_func), static_cast<awaiter_t&&>(m_awaiter).await_resume())))
|
||||||
|
{
|
||||||
|
return std::invoke(
|
||||||
|
static_cast<FUNC&&>(m_func),
|
||||||
|
static_cast<awaiter_t&&>(m_awaiter).await_resume());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename FUNC, typename AWAITABLE>
|
||||||
|
class fmap_awaitable
|
||||||
|
{
|
||||||
|
static_assert(!std::is_lvalue_reference_v<FUNC>);
|
||||||
|
static_assert(!std::is_lvalue_reference_v<AWAITABLE>);
|
||||||
|
public:
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename FUNC_ARG,
|
||||||
|
typename AWAITABLE_ARG,
|
||||||
|
std::enable_if_t<
|
||||||
|
std::is_constructible_v<FUNC, FUNC_ARG&&> &&
|
||||||
|
std::is_constructible_v<AWAITABLE, AWAITABLE_ARG&&>, int> = 0>
|
||||||
|
explicit fmap_awaitable(FUNC_ARG&& func, AWAITABLE_ARG&& awaitable)
|
||||||
|
noexcept(
|
||||||
|
std::is_nothrow_constructible_v<FUNC, FUNC_ARG&&> &&
|
||||||
|
std::is_nothrow_constructible_v<AWAITABLE, AWAITABLE_ARG&&>)
|
||||||
|
: m_func(static_cast<FUNC_ARG&&>(func))
|
||||||
|
, m_awaitable(static_cast<AWAITABLE_ARG&&>(awaitable))
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto operator co_await() const &
|
||||||
|
{
|
||||||
|
return fmap_awaiter<const FUNC&, const AWAITABLE&>(m_func, m_awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() &
|
||||||
|
{
|
||||||
|
return fmap_awaiter<FUNC&, AWAITABLE&>(m_func, m_awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() &&
|
||||||
|
{
|
||||||
|
return fmap_awaiter<FUNC&&, AWAITABLE&&>(
|
||||||
|
static_cast<FUNC&&>(m_func),
|
||||||
|
static_cast<AWAITABLE&&>(m_awaitable));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FUNC m_func;
|
||||||
|
AWAITABLE m_awaitable;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FUNC>
|
||||||
|
struct fmap_transform
|
||||||
|
{
|
||||||
|
explicit fmap_transform(FUNC&& f)
|
||||||
|
noexcept(std::is_nothrow_move_constructible_v<FUNC>)
|
||||||
|
: func(std::forward<FUNC>(f))
|
||||||
|
{}
|
||||||
|
|
||||||
|
FUNC func;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename FUNC,
|
||||||
|
typename AWAITABLE,
|
||||||
|
std::enable_if_t<cppcoro::is_awaitable_v<AWAITABLE>, int> = 0>
|
||||||
|
auto fmap(FUNC&& func, AWAITABLE&& awaitable)
|
||||||
|
{
|
||||||
|
return detail::fmap_awaitable<
|
||||||
|
std::remove_cv_t<std::remove_reference_t<FUNC>>,
|
||||||
|
std::remove_cv_t<std::remove_reference_t<AWAITABLE>>>(
|
||||||
|
std::forward<FUNC>(func),
|
||||||
|
std::forward<AWAITABLE>(awaitable));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FUNC>
|
||||||
|
auto fmap(FUNC&& func)
|
||||||
|
{
|
||||||
|
return fmap_transform<FUNC>{ std::forward<FUNC>(func) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename FUNC>
|
||||||
|
decltype(auto) operator|(T&& value, fmap_transform<FUNC>&& transform)
|
||||||
|
{
|
||||||
|
// Use ADL for finding fmap() overload.
|
||||||
|
return fmap(std::forward<FUNC>(transform.func), std::forward<T>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename FUNC>
|
||||||
|
decltype(auto) operator|(T&& value, const fmap_transform<FUNC>& transform)
|
||||||
|
{
|
||||||
|
// Use ADL for finding fmap() overload.
|
||||||
|
return fmap(transform.func, std::forward<T>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename FUNC>
|
||||||
|
decltype(auto) operator|(T&& value, fmap_transform<FUNC>& transform)
|
||||||
|
{
|
||||||
|
// Use ADL for finding fmap() overload.
|
||||||
|
return fmap(transform.func, std::forward<T>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_GENERATOR_HPP_INCLUDED
|
||||||
|
#define CPPCORO_GENERATOR_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <exception>
|
||||||
|
#include <iterator>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class generator;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class generator_promise
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using value_type = std::remove_reference_t<T>;
|
||||||
|
using reference_type = std::conditional_t<std::is_reference_v<T>, T, T&>;
|
||||||
|
using pointer_type = value_type*;
|
||||||
|
|
||||||
|
generator_promise() = default;
|
||||||
|
|
||||||
|
generator<T> get_return_object() noexcept;
|
||||||
|
|
||||||
|
constexpr cppcoro::suspend_always initial_suspend() const noexcept { return {}; }
|
||||||
|
constexpr cppcoro::suspend_always final_suspend() const noexcept { return {}; }
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename U = T,
|
||||||
|
std::enable_if_t<!std::is_rvalue_reference<U>::value, int> = 0>
|
||||||
|
cppcoro::suspend_always yield_value(std::remove_reference_t<T>& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always yield_value(std::remove_reference_t<T>&& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception()
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
reference_type value() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<reference_type>(*m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow any use of 'co_await' inside the generator coroutine.
|
||||||
|
template<typename U>
|
||||||
|
cppcoro::suspend_never await_transform(U&& value) = delete;
|
||||||
|
|
||||||
|
void rethrow_if_exception()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
pointer_type m_value;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct generator_sentinel {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class generator_iterator
|
||||||
|
{
|
||||||
|
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
// What type should we use for counting elements of a potentially infinite sequence?
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = typename generator_promise<T>::value_type;
|
||||||
|
using reference = typename generator_promise<T>::reference_type;
|
||||||
|
using pointer = typename generator_promise<T>::pointer_type;
|
||||||
|
|
||||||
|
// Iterator needs to be default-constructible to satisfy the Range concept.
|
||||||
|
generator_iterator() noexcept
|
||||||
|
: m_coroutine(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit generator_iterator(coroutine_handle coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
friend bool operator==(const generator_iterator& it, generator_sentinel) noexcept
|
||||||
|
{
|
||||||
|
return !it.m_coroutine || it.m_coroutine.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const generator_iterator& it, generator_sentinel s) noexcept
|
||||||
|
{
|
||||||
|
return !(it == s);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(generator_sentinel s, const generator_iterator& it) noexcept
|
||||||
|
{
|
||||||
|
return (it == s);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(generator_sentinel s, const generator_iterator& it) noexcept
|
||||||
|
{
|
||||||
|
return it != s;
|
||||||
|
}
|
||||||
|
|
||||||
|
generator_iterator& operator++()
|
||||||
|
{
|
||||||
|
m_coroutine.resume();
|
||||||
|
if (m_coroutine.done())
|
||||||
|
{
|
||||||
|
m_coroutine.promise().rethrow_if_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to provide post-increment operator to implement the 'Range' concept.
|
||||||
|
void operator++(int)
|
||||||
|
{
|
||||||
|
(void)operator++();
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const noexcept
|
||||||
|
{
|
||||||
|
return m_coroutine.promise().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const noexcept
|
||||||
|
{
|
||||||
|
return std::addressof(operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
coroutine_handle m_coroutine;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class [[nodiscard]] generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using promise_type = detail::generator_promise<T>;
|
||||||
|
using iterator = detail::generator_iterator<T>;
|
||||||
|
|
||||||
|
generator() noexcept
|
||||||
|
: m_coroutine(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
generator(generator&& other) noexcept
|
||||||
|
: m_coroutine(other.m_coroutine)
|
||||||
|
{
|
||||||
|
other.m_coroutine = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
generator(const generator& other) = delete;
|
||||||
|
|
||||||
|
~generator()
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generator& operator=(generator other) noexcept
|
||||||
|
{
|
||||||
|
swap(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.resume();
|
||||||
|
if (m_coroutine.done())
|
||||||
|
{
|
||||||
|
m_coroutine.promise().rethrow_if_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterator{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::generator_sentinel end() noexcept
|
||||||
|
{
|
||||||
|
return detail::generator_sentinel{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(generator& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(m_coroutine, other.m_coroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class detail::generator_promise<T>;
|
||||||
|
|
||||||
|
explicit generator(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<promise_type> m_coroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void swap(generator<T>& a, generator<T>& b)
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
generator<T> generator_promise<T>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
using coroutine_handle = cppcoro::coroutine_handle<generator_promise<T>>;
|
||||||
|
return generator<T>{ coroutine_handle::from_promise(*this) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FUNC, typename T>
|
||||||
|
generator<std::invoke_result_t<FUNC&, typename generator<T>::iterator::reference>> fmap(FUNC func, generator<T> source)
|
||||||
|
{
|
||||||
|
for (auto&& value : source)
|
||||||
|
{
|
||||||
|
co_yield std::invoke(func, static_cast<decltype(value)>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class inline_scheduler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
inline_scheduler() noexcept = default;
|
||||||
|
|
||||||
|
cppcoro::suspend_never schedule() const noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_IO_SERVICE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_IO_SERVICE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <atomic>
|
||||||
|
#include <utility>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class io_service
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
class schedule_operation;
|
||||||
|
class timed_schedule_operation;
|
||||||
|
|
||||||
|
/// Initialises the io_service.
|
||||||
|
///
|
||||||
|
/// Does not set a concurrency hint. All threads that enter the
|
||||||
|
/// event loop will actively process events.
|
||||||
|
io_service();
|
||||||
|
|
||||||
|
/// Initialise the io_service with a concurrency hint.
|
||||||
|
///
|
||||||
|
/// \param concurrencyHint
|
||||||
|
/// Specifies the target maximum number of I/O threads to be
|
||||||
|
/// actively processing events.
|
||||||
|
/// Note that the number of active threads may temporarily go
|
||||||
|
/// above this number.
|
||||||
|
io_service(std::uint32_t concurrencyHint);
|
||||||
|
|
||||||
|
~io_service();
|
||||||
|
|
||||||
|
io_service(io_service&& other) = delete;
|
||||||
|
io_service(const io_service& other) = delete;
|
||||||
|
io_service& operator=(io_service&& other) = delete;
|
||||||
|
io_service& operator=(const io_service& other) = delete;
|
||||||
|
|
||||||
|
/// Returns an operation that when awaited suspends the awaiting
|
||||||
|
/// coroutine and reschedules it for resumption on an I/O thread
|
||||||
|
/// associated with this io_service.
|
||||||
|
[[nodiscard]]
|
||||||
|
schedule_operation schedule() noexcept;
|
||||||
|
|
||||||
|
/// Returns an operation that when awaited will suspend the
|
||||||
|
/// awaiting coroutine for the specified delay. Once the delay
|
||||||
|
/// has elapsed, the coroutine will resume execution on an
|
||||||
|
/// I/O thread associated with this io_service.
|
||||||
|
///
|
||||||
|
/// \param delay
|
||||||
|
/// The amount of time to delay scheduling resumption of the coroutine
|
||||||
|
/// on an I/O thread. There is no guarantee that the coroutine will
|
||||||
|
/// be resumed exactly after this delay.
|
||||||
|
///
|
||||||
|
/// \param cancellationToken [optional]
|
||||||
|
/// A cancellation token that can be used to communicate a request to
|
||||||
|
/// cancel the delayed schedule operation and schedule it for resumption
|
||||||
|
/// immediately.
|
||||||
|
/// The co_await operation will throw cppcoro::operation_cancelled if
|
||||||
|
/// cancellation was requested before the coroutine could be resumed.
|
||||||
|
template<typename REP, typename PERIOD>
|
||||||
|
[[nodiscard]]
|
||||||
|
timed_schedule_operation schedule_after(
|
||||||
|
const std::chrono::duration<REP, PERIOD>& delay,
|
||||||
|
cancellation_token cancellationToken = {}) noexcept;
|
||||||
|
|
||||||
|
/// Process events until the io_service is stopped.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The number of events processed during this call.
|
||||||
|
std::uint64_t process_events();
|
||||||
|
|
||||||
|
/// Process events until either the io_service is stopped or
|
||||||
|
/// there are no more pending events in the queue.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The number of events processed during this call.
|
||||||
|
std::uint64_t process_pending_events();
|
||||||
|
|
||||||
|
/// Block until either one event is processed or the io_service is stopped.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The number of events processed during this call.
|
||||||
|
/// This will either be 0 or 1.
|
||||||
|
std::uint64_t process_one_event();
|
||||||
|
|
||||||
|
/// Process one event if there are any events pending, otherwise if there
|
||||||
|
/// are no events pending or the io_service is stopped then return immediately.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The number of events processed during this call.
|
||||||
|
/// This will either be 0 or 1.
|
||||||
|
std::uint64_t process_one_pending_event();
|
||||||
|
|
||||||
|
/// Shut down the io_service.
|
||||||
|
///
|
||||||
|
/// This will cause any threads currently in a call to one of the process_xxx() methods
|
||||||
|
/// to return from that call once they finish processing the current event.
|
||||||
|
///
|
||||||
|
/// This call does not wait until all threads have exited the event loop so you
|
||||||
|
/// must use other synchronisation mechanisms to wait for those threads.
|
||||||
|
void stop() noexcept;
|
||||||
|
|
||||||
|
/// Reset an io_service to prepare it for resuming processing of events.
|
||||||
|
///
|
||||||
|
/// Call this after a call to stop() to allow calls to process_xxx() methods
|
||||||
|
/// to process events.
|
||||||
|
///
|
||||||
|
/// After calling stop() you should ensure that all threads have returned from
|
||||||
|
/// calls to process_xxx() methods before calling reset().
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool is_stop_requested() const noexcept;
|
||||||
|
|
||||||
|
void notify_work_started() noexcept;
|
||||||
|
|
||||||
|
void notify_work_finished() noexcept;
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
detail::win32::handle_t native_iocp_handle() noexcept;
|
||||||
|
void ensure_winsock_initialised();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
class timer_thread_state;
|
||||||
|
class timer_queue;
|
||||||
|
|
||||||
|
friend class schedule_operation;
|
||||||
|
friend class timed_schedule_operation;
|
||||||
|
|
||||||
|
void schedule_impl(schedule_operation* operation) noexcept;
|
||||||
|
|
||||||
|
void try_reschedule_overflow_operations() noexcept;
|
||||||
|
|
||||||
|
bool try_enter_event_loop() noexcept;
|
||||||
|
void exit_event_loop() noexcept;
|
||||||
|
|
||||||
|
bool try_process_one_event(bool waitForEvent);
|
||||||
|
|
||||||
|
void post_wake_up_event() noexcept;
|
||||||
|
|
||||||
|
timer_thread_state* ensure_timer_thread_started();
|
||||||
|
|
||||||
|
static constexpr std::uint32_t stop_requested_flag = 1;
|
||||||
|
static constexpr std::uint32_t active_thread_count_increment = 2;
|
||||||
|
|
||||||
|
// Bit 0: stop_requested_flag
|
||||||
|
// Bit 1-31: count of active threads currently running the event loop
|
||||||
|
std::atomic<std::uint32_t> m_threadState;
|
||||||
|
|
||||||
|
std::atomic<std::uint32_t> m_workCount;
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
detail::win32::safe_handle m_iocpHandle;
|
||||||
|
|
||||||
|
std::atomic<bool> m_winsockInitialised;
|
||||||
|
std::mutex m_winsockInitialisationMutex;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Head of a linked-list of schedule operations that are
|
||||||
|
// ready to run but that failed to be queued to the I/O
|
||||||
|
// completion port (eg. due to low memory).
|
||||||
|
std::atomic<schedule_operation*> m_scheduleOperations;
|
||||||
|
|
||||||
|
std::atomic<timer_thread_state*> m_timerState;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class io_service::schedule_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
schedule_operation(io_service& service) noexcept
|
||||||
|
: m_service(service)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept;
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class io_service;
|
||||||
|
friend class io_service::timed_schedule_operation;
|
||||||
|
|
||||||
|
io_service& m_service;
|
||||||
|
cppcoro::coroutine_handle<> m_awaiter;
|
||||||
|
schedule_operation* m_next;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class io_service::timed_schedule_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
timed_schedule_operation(
|
||||||
|
io_service& service,
|
||||||
|
std::chrono::high_resolution_clock::time_point resumeTime,
|
||||||
|
cppcoro::cancellation_token cancellationToken) noexcept;
|
||||||
|
|
||||||
|
timed_schedule_operation(timed_schedule_operation&& other) noexcept;
|
||||||
|
|
||||||
|
~timed_schedule_operation();
|
||||||
|
|
||||||
|
timed_schedule_operation& operator=(timed_schedule_operation&& other) = delete;
|
||||||
|
timed_schedule_operation(const timed_schedule_operation& other) = delete;
|
||||||
|
timed_schedule_operation& operator=(const timed_schedule_operation& other) = delete;
|
||||||
|
|
||||||
|
bool await_ready() const noexcept;
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<> awaiter);
|
||||||
|
void await_resume();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class io_service::timer_queue;
|
||||||
|
friend class io_service::timer_thread_state;
|
||||||
|
|
||||||
|
io_service::schedule_operation m_scheduleOperation;
|
||||||
|
std::chrono::high_resolution_clock::time_point m_resumeTime;
|
||||||
|
|
||||||
|
cppcoro::cancellation_token m_cancellationToken;
|
||||||
|
std::optional<cppcoro::cancellation_registration> m_cancellationRegistration;
|
||||||
|
|
||||||
|
timed_schedule_operation* m_next;
|
||||||
|
|
||||||
|
std::atomic<std::uint32_t> m_refCount;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class io_work_scope
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit io_work_scope(io_service& service) noexcept
|
||||||
|
: m_service(&service)
|
||||||
|
{
|
||||||
|
service.notify_work_started();
|
||||||
|
}
|
||||||
|
|
||||||
|
io_work_scope(const io_work_scope& other) noexcept
|
||||||
|
: m_service(other.m_service)
|
||||||
|
{
|
||||||
|
if (m_service != nullptr)
|
||||||
|
{
|
||||||
|
m_service->notify_work_started();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io_work_scope(io_work_scope&& other) noexcept
|
||||||
|
: m_service(other.m_service)
|
||||||
|
{
|
||||||
|
other.m_service = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~io_work_scope()
|
||||||
|
{
|
||||||
|
if (m_service != nullptr)
|
||||||
|
{
|
||||||
|
m_service->notify_work_finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(io_work_scope& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(m_service, other.m_service);
|
||||||
|
}
|
||||||
|
|
||||||
|
io_work_scope& operator=(io_work_scope other) noexcept
|
||||||
|
{
|
||||||
|
swap(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
io_service& service() noexcept
|
||||||
|
{
|
||||||
|
return *m_service;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
io_service* m_service;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void swap(io_work_scope& a, io_work_scope& b)
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename REP, typename RATIO>
|
||||||
|
cppcoro::io_service::timed_schedule_operation
|
||||||
|
cppcoro::io_service::schedule_after(
|
||||||
|
const std::chrono::duration<REP, RATIO>& duration,
|
||||||
|
cppcoro::cancellation_token cancellationToken) noexcept
|
||||||
|
{
|
||||||
|
return timed_schedule_operation{
|
||||||
|
*this,
|
||||||
|
std::chrono::high_resolution_clock::now() + duration,
|
||||||
|
std::move(cancellationToken)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_IS_AWAITABLE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_IS_AWAITABLE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/detail/get_awaiter.hpp>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T, typename = std::void_t<>>
|
||||||
|
struct is_awaitable : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct is_awaitable<T, std::void_t<decltype(cppcoro::detail::get_awaiter(std::declval<T>()))>>
|
||||||
|
: std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr bool is_awaitable_v = is_awaitable<T>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,829 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_MULTI_PRODUCER_SEQUENCER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/sequence_barrier.hpp>
|
||||||
|
#include <cppcoro/sequence_range.hpp>
|
||||||
|
#include <cppcoro/sequence_traits.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/manual_lifetime.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_one_operation;
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_operation;
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
class multi_producer_sequencer_wait_operation_base;
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_wait_operation;
|
||||||
|
|
||||||
|
/// A multi-producer sequencer is a thread-synchronisation primitive that can be
|
||||||
|
/// used to synchronise access to a ring-buffer of power-of-two size where you
|
||||||
|
/// have multiple producers concurrently claiming slots in the ring-buffer and
|
||||||
|
/// publishing items.
|
||||||
|
///
|
||||||
|
/// When a writer wants to write to a slot in the buffer it first atomically
|
||||||
|
/// increments a counter by the number of slots it wishes to allocate.
|
||||||
|
/// It then waits until all of those slots have become available and then
|
||||||
|
/// returns the range of sequence numbers allocated back to the caller.
|
||||||
|
/// The caller then writes to those slots and when done publishes them by
|
||||||
|
/// writing the sequence numbers published to each of the slots to the
|
||||||
|
/// corresponding element of an array of equal size to the ring buffer.
|
||||||
|
/// When a reader wants to check if the next sequence number is available
|
||||||
|
/// it then simply needs to read from the corresponding slot in this array
|
||||||
|
/// to check if the value stored there is equal to the sequence number it
|
||||||
|
/// is wanting to read.
|
||||||
|
///
|
||||||
|
/// This means concurrent writers are wait-free when there is space available
|
||||||
|
/// in the ring buffer, requiring a single atomic fetch-add operation as the
|
||||||
|
/// only contended write operation. All other writes are to memory locations
|
||||||
|
/// owned by a particular writer. Concurrent writers can publish items out of
|
||||||
|
/// order so that one writer does not hold up other writers until the ring
|
||||||
|
/// buffer fills up.
|
||||||
|
template<
|
||||||
|
typename SEQUENCE = std::size_t,
|
||||||
|
typename TRAITS = sequence_traits<SEQUENCE>>
|
||||||
|
class multi_producer_sequencer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
|
||||||
|
std::size_t bufferSize,
|
||||||
|
SEQUENCE initialSequence = TRAITS::initial_sequence);
|
||||||
|
|
||||||
|
/// The size of the circular buffer. This will be a power-of-two.
|
||||||
|
std::size_t buffer_size() const noexcept { return m_sequenceMask + 1; }
|
||||||
|
|
||||||
|
/// Lookup the last-known-published sequence number after the specified
|
||||||
|
/// sequence number.
|
||||||
|
SEQUENCE last_published_after(SEQUENCE lastKnownPublished) const noexcept;
|
||||||
|
|
||||||
|
/// Wait until the specified target sequence number has been published.
|
||||||
|
///
|
||||||
|
/// Returns an awaitable type that when co_awaited will suspend the awaiting
|
||||||
|
/// coroutine until the specified 'targetSequence' number and all prior sequence
|
||||||
|
/// numbers have been published.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SEQUENCE lastKnownPublished,
|
||||||
|
SCHEDULER& scheduler) const noexcept;
|
||||||
|
|
||||||
|
/// Query if there are currently any slots available for claiming.
|
||||||
|
///
|
||||||
|
/// Note that this return-value is only approximate if you have multiple producers
|
||||||
|
/// since immediately after returning true another thread may have claimed the
|
||||||
|
/// last available slot.
|
||||||
|
bool any_available() const noexcept;
|
||||||
|
|
||||||
|
/// Claim a single slot in the buffer and wait until that slot becomes available.
|
||||||
|
///
|
||||||
|
/// Returns an Awaitable type that yields the sequence number of the slot that
|
||||||
|
/// was claimed.
|
||||||
|
///
|
||||||
|
/// Once the producer has claimed a slot then they are free to write to that
|
||||||
|
/// slot within the ring buffer. Once the value has been initialised the item
|
||||||
|
/// must be published by calling the .publish() method, passing the sequence
|
||||||
|
/// number.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
claim_one(SCHEDULER& scheduler) noexcept;
|
||||||
|
|
||||||
|
/// Claim a contiguous range of sequence numbers corresponding to slots within
|
||||||
|
/// a ring-buffer.
|
||||||
|
///
|
||||||
|
/// This will claim at most the specified count of sequence numbers but may claim
|
||||||
|
/// fewer if there are only fewer entries available in the buffer. But will claim
|
||||||
|
/// at least one sequence number.
|
||||||
|
///
|
||||||
|
/// Returns an awaitable that will yield a sequence_range object containing the
|
||||||
|
/// sequence numbers that were claimed.
|
||||||
|
///
|
||||||
|
/// The caller is responsible for ensuring that they publish every element of the
|
||||||
|
/// returned sequence range by calling .publish().
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept;
|
||||||
|
|
||||||
|
/// Publish the element with the specified sequence number, making it available
|
||||||
|
/// to consumers.
|
||||||
|
///
|
||||||
|
/// Note that different sequence numbers may be published by different producer
|
||||||
|
/// threads out of order. A sequence number will not become available to consumers
|
||||||
|
/// until all preceding sequence numbers have also been published.
|
||||||
|
///
|
||||||
|
/// \param sequence
|
||||||
|
/// The sequence number of the elemnt to publish
|
||||||
|
/// This sequence number must have been previously acquired via a call to 'claim_one()'
|
||||||
|
/// or 'claim_up_to()'.
|
||||||
|
void publish(SEQUENCE sequence) noexcept;
|
||||||
|
|
||||||
|
/// Publish a contiguous range of sequence numbers, making each of them available
|
||||||
|
/// to consumers.
|
||||||
|
///
|
||||||
|
/// This is equivalent to calling publish(seq) for each sequence number, seq, in
|
||||||
|
/// the specified range, but is more efficient since it only checks to see if
|
||||||
|
/// there are coroutines that need to be woken up once.
|
||||||
|
void publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<typename SEQUENCE2, typename TRAITS2>
|
||||||
|
friend class multi_producer_sequencer_wait_operation_base;
|
||||||
|
|
||||||
|
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
|
||||||
|
friend class multi_producer_sequencer_claim_operation;
|
||||||
|
|
||||||
|
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
|
||||||
|
friend class multi_producer_sequencer_claim_one_operation;
|
||||||
|
|
||||||
|
void resume_ready_awaiters() noexcept;
|
||||||
|
void add_awaiter(multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
|
||||||
|
const std::size_t m_sequenceMask;
|
||||||
|
const std::unique_ptr<std::atomic<SEQUENCE>[]> m_published;
|
||||||
|
|
||||||
|
alignas(CPPCORO_CPU_CACHE_LINE)
|
||||||
|
std::atomic<SEQUENCE> m_nextToClaim;
|
||||||
|
|
||||||
|
alignas(CPPCORO_CPU_CACHE_LINE)
|
||||||
|
mutable std::atomic<multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>*> m_awaiters;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_awaiter(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
|
||||||
|
std::size_t bufferSize,
|
||||||
|
const sequence_range<SEQUENCE, TRAITS>& claimedRange,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_barrierWait(consumerBarrier, claimedRange.back() - bufferSize, scheduler)
|
||||||
|
, m_claimedRange(claimedRange)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_barrierWait.await_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_barrierWait.await_suspend(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_claimedRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_barrierWait;
|
||||||
|
sequence_range<SEQUENCE, TRAITS> m_claimedRange;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_operation(
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
std::size_t count,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_sequencer(sequencer)
|
||||||
|
, m_count(count < sequencer.buffer_size() ? count : sequencer.buffer_size())
|
||||||
|
, m_scheduler(scheduler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
|
||||||
|
{
|
||||||
|
// We wait until the awaitable is actually co_await'ed before we claim the
|
||||||
|
// range of elements. If we claimed them earlier, then it may be possible for
|
||||||
|
// the caller to fail to co_await the result eg. due to an exception, which
|
||||||
|
// would leave the sequence numbers unable to be published and would eventually
|
||||||
|
// deadlock consumers that waited on them.
|
||||||
|
//
|
||||||
|
// TODO: We could try and acquire only as many as are available if fewer than
|
||||||
|
// m_count elements are available. This would complicate the logic here somewhat
|
||||||
|
// as we'd need to use a compare-exchange instead.
|
||||||
|
const SEQUENCE first = m_sequencer.m_nextToClaim.fetch_add(m_count, std::memory_order_relaxed);
|
||||||
|
return multi_producer_sequencer_claim_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
|
||||||
|
m_sequencer.m_consumerBarrier,
|
||||||
|
m_sequencer.buffer_size(),
|
||||||
|
sequence_range<SEQUENCE, TRAITS>{ first, first + m_count },
|
||||||
|
m_scheduler
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
|
||||||
|
std::size_t m_count;
|
||||||
|
SCHEDULER& m_scheduler;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_one_awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_one_awaiter(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
|
||||||
|
std::size_t bufferSize,
|
||||||
|
SEQUENCE claimedSequence,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_waitOp(consumerBarrier, claimedSequence - bufferSize, scheduler)
|
||||||
|
, m_claimedSequence(claimedSequence)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_waitOp.await_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_waitOp.await_suspend(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_claimedSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_waitOp;
|
||||||
|
SEQUENCE m_claimedSequence;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_claim_one_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_one_operation(
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_sequencer(sequencer)
|
||||||
|
, m_scheduler(scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER> operator co_await() noexcept
|
||||||
|
{
|
||||||
|
return multi_producer_sequencer_claim_one_awaiter<SEQUENCE, TRAITS, SCHEDULER>{
|
||||||
|
m_sequencer.m_consumerBarrier,
|
||||||
|
m_sequencer.buffer_size(),
|
||||||
|
m_sequencer.m_nextToClaim.fetch_add(1, std::memory_order_relaxed),
|
||||||
|
m_scheduler
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
|
||||||
|
SCHEDULER& m_scheduler;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
class multi_producer_sequencer_wait_operation_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_wait_operation_base(
|
||||||
|
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SEQUENCE lastKnownPublished) noexcept
|
||||||
|
: m_sequencer(sequencer)
|
||||||
|
, m_targetSequence(targetSequence)
|
||||||
|
, m_lastKnownPublished(lastKnownPublished)
|
||||||
|
, m_readyToResume(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
multi_producer_sequencer_wait_operation_base(
|
||||||
|
const multi_producer_sequencer_wait_operation_base& other) noexcept
|
||||||
|
: m_sequencer(other.m_sequencer)
|
||||||
|
, m_targetSequence(other.m_targetSequence)
|
||||||
|
, m_lastKnownPublished(other.m_lastKnownPublished)
|
||||||
|
, m_readyToResume(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
|
||||||
|
m_sequencer.add_awaiter(this);
|
||||||
|
|
||||||
|
// Mark the waiter as ready to resume.
|
||||||
|
// If it was already marked as ready-to-resume within the call to add_awaiter() or
|
||||||
|
// on another thread then this exchange() will return true. In this case we want to
|
||||||
|
// resume immediately and continue execution by returning false.
|
||||||
|
return !m_readyToResume.exchange(true, std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_lastKnownPublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
friend class multi_producer_sequencer<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
void resume(SEQUENCE lastKnownPublished) noexcept
|
||||||
|
{
|
||||||
|
m_lastKnownPublished = lastKnownPublished;
|
||||||
|
if (m_readyToResume.exchange(true, std::memory_order_release))
|
||||||
|
{
|
||||||
|
resume_impl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void resume_impl() noexcept = 0;
|
||||||
|
|
||||||
|
const multi_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
|
||||||
|
SEQUENCE m_targetSequence;
|
||||||
|
SEQUENCE m_lastKnownPublished;
|
||||||
|
multi_producer_sequencer_wait_operation_base* m_next;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
std::atomic<bool> m_readyToResume;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class multi_producer_sequencer_wait_operation :
|
||||||
|
public multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>
|
||||||
|
{
|
||||||
|
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
multi_producer_sequencer_wait_operation(
|
||||||
|
const multi_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SEQUENCE lastKnownPublished,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(sequencer, targetSequence, lastKnownPublished)
|
||||||
|
, m_scheduler(scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
multi_producer_sequencer_wait_operation(
|
||||||
|
const multi_producer_sequencer_wait_operation& other) noexcept
|
||||||
|
: multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>(other)
|
||||||
|
, m_scheduler(other.m_scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~multi_producer_sequencer_wait_operation()
|
||||||
|
{
|
||||||
|
if (m_isScheduleAwaiterCreated)
|
||||||
|
{
|
||||||
|
m_scheduleAwaiter.destruct();
|
||||||
|
}
|
||||||
|
if (m_isScheduleOperationCreated)
|
||||||
|
{
|
||||||
|
m_scheduleOperation.destruct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE await_resume() noexcept(noexcept(m_scheduleOperation->await_resume()))
|
||||||
|
{
|
||||||
|
if (m_isScheduleOperationCreated)
|
||||||
|
{
|
||||||
|
m_scheduleOperation->await_resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
return multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void resume_impl() noexcept override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_scheduleOperation.construct(m_scheduler.schedule());
|
||||||
|
m_isScheduleOperationCreated = true;
|
||||||
|
|
||||||
|
m_scheduleAwaiter.construct(detail::get_awaiter(
|
||||||
|
static_cast<schedule_operation&&>(*m_scheduleOperation)));
|
||||||
|
m_isScheduleAwaiterCreated = true;
|
||||||
|
|
||||||
|
if (!m_scheduleAwaiter->await_ready())
|
||||||
|
{
|
||||||
|
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
|
||||||
|
if constexpr (std::is_void_v<await_suspend_result_t>)
|
||||||
|
{
|
||||||
|
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
|
||||||
|
{
|
||||||
|
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Assume it returns a coroutine_handle.
|
||||||
|
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Ignore failure to reschedule and resume inline?
|
||||||
|
// Should we catch the exception and rethrow from await_resume()?
|
||||||
|
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume outside the catch-block.
|
||||||
|
this->m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
SCHEDULER& m_scheduler;
|
||||||
|
// Can't use std::optional<T> here since T could be a reference.
|
||||||
|
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
|
||||||
|
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
|
||||||
|
bool m_isScheduleOperationCreated = false;
|
||||||
|
bool m_isScheduleAwaiterCreated = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>::multi_producer_sequencer(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
|
||||||
|
std::size_t bufferSize,
|
||||||
|
SEQUENCE initialSequence)
|
||||||
|
: m_consumerBarrier(consumerBarrier)
|
||||||
|
, m_sequenceMask(bufferSize - 1)
|
||||||
|
, m_published(std::make_unique<std::atomic<SEQUENCE>[]>(bufferSize))
|
||||||
|
, m_nextToClaim(initialSequence + 1)
|
||||||
|
, m_awaiters(nullptr)
|
||||||
|
{
|
||||||
|
// bufferSize must be a positive power-of-two
|
||||||
|
assert(bufferSize > 0 && (bufferSize & (bufferSize - 1)) == 0);
|
||||||
|
// but must be no larger than the max diff value.
|
||||||
|
using diff_t = typename TRAITS::difference_type;
|
||||||
|
using unsigned_diff_t = std::make_unsigned_t<diff_t>;
|
||||||
|
constexpr unsigned_diff_t maxSize = static_cast<unsigned_diff_t>(std::numeric_limits<diff_t>::max());
|
||||||
|
assert(bufferSize <= maxSize);
|
||||||
|
|
||||||
|
SEQUENCE seq = initialSequence - (bufferSize - 1);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
#ifdef __cpp_lib_atomic_value_initialization
|
||||||
|
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
|
||||||
|
#else // ^^^ __cpp_lib_atomic_value_initialization // !__cpp_lib_atomic_value_initialization vvv
|
||||||
|
std::atomic_init(&m_published[seq & m_sequenceMask], seq);
|
||||||
|
#endif // !__cpp_lib_atomic_value_initialization
|
||||||
|
} while (seq++ != initialSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
SEQUENCE multi_producer_sequencer<SEQUENCE, TRAITS>::last_published_after(
|
||||||
|
SEQUENCE lastKnownPublished) const noexcept
|
||||||
|
{
|
||||||
|
const auto mask = m_sequenceMask;
|
||||||
|
SEQUENCE seq = lastKnownPublished + 1;
|
||||||
|
while (m_published[seq & mask].load(std::memory_order_acquire) == seq)
|
||||||
|
{
|
||||||
|
lastKnownPublished = seq++;
|
||||||
|
}
|
||||||
|
return lastKnownPublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>::wait_until_published(
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SEQUENCE lastKnownPublished,
|
||||||
|
SCHEDULER& scheduler) const noexcept
|
||||||
|
{
|
||||||
|
return multi_producer_sequencer_wait_operation<SEQUENCE, TRAITS, SCHEDULER>{
|
||||||
|
*this, targetSequence, lastKnownPublished, scheduler
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
bool multi_producer_sequencer<SEQUENCE, TRAITS>::any_available() const noexcept
|
||||||
|
{
|
||||||
|
return TRAITS::precedes(
|
||||||
|
m_nextToClaim.load(std::memory_order_relaxed),
|
||||||
|
m_consumerBarrier.last_published() + buffer_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
|
||||||
|
{
|
||||||
|
return multi_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
multi_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
|
||||||
|
{
|
||||||
|
return multi_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, count, scheduler };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
|
||||||
|
{
|
||||||
|
m_published[sequence & m_sequenceMask].store(sequence, std::memory_order_seq_cst);
|
||||||
|
|
||||||
|
// Resume any waiters that might have been satisfied by this publish operation.
|
||||||
|
resume_ready_awaiters();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void multi_producer_sequencer<SEQUENCE, TRAITS>::publish(const sequence_range<SEQUENCE, TRAITS>& range) noexcept
|
||||||
|
{
|
||||||
|
if (range.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish all but the first sequence number using relaxed atomics.
|
||||||
|
// No consumer should be reading those subsequent sequence numbers until they've seen
|
||||||
|
// that the first sequence number in the range is published.
|
||||||
|
for (SEQUENCE seq : range.skip(1))
|
||||||
|
{
|
||||||
|
m_published[seq & m_sequenceMask].store(seq, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now publish the first sequence number with seq_cst semantics.
|
||||||
|
m_published[range.front() & m_sequenceMask].store(range.front(), std::memory_order_seq_cst);
|
||||||
|
|
||||||
|
// Resume any waiters that might have been satisfied by this publish operation.
|
||||||
|
resume_ready_awaiters();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void multi_producer_sequencer<SEQUENCE, TRAITS>::resume_ready_awaiters() noexcept
|
||||||
|
{
|
||||||
|
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
awaiter_t* awaiters = m_awaiters.load(std::memory_order_seq_cst);
|
||||||
|
if (awaiters == nullptr)
|
||||||
|
{
|
||||||
|
// No awaiters
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There were some awaiters. Try to acquire the list of waiters with an
|
||||||
|
// atomic exchange as we might be racing with other consumers/producers.
|
||||||
|
awaiters = m_awaiters.exchange(nullptr, std::memory_order_seq_cst);
|
||||||
|
if (awaiters == nullptr)
|
||||||
|
{
|
||||||
|
// Didn't acquire the list
|
||||||
|
// Some other thread is now responsible for resuming them. Our job is done.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE lastKnownPublished;
|
||||||
|
|
||||||
|
awaiter_t* awaitersToResume;
|
||||||
|
awaiter_t** awaitersToResumeTail = &awaitersToResume;
|
||||||
|
|
||||||
|
awaiter_t* awaitersToRequeue;
|
||||||
|
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
using diff_t = typename TRAITS::difference_type;
|
||||||
|
|
||||||
|
lastKnownPublished = last_published_after(awaiters->m_lastKnownPublished);
|
||||||
|
|
||||||
|
// First scan the list of awaiters and split them into 'requeue' and 'resume' lists.
|
||||||
|
auto minDiff = std::numeric_limits<diff_t>::max();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
|
||||||
|
if (diff > 0)
|
||||||
|
{
|
||||||
|
// Not ready yet.
|
||||||
|
minDiff = diff < minDiff ? diff : minDiff;
|
||||||
|
*awaitersToRequeueTail = awaiters;
|
||||||
|
awaitersToRequeueTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*awaitersToResumeTail = awaiters;
|
||||||
|
awaitersToResumeTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
awaiters->m_lastKnownPublished = lastKnownPublished;
|
||||||
|
awaiters = awaiters->m_next;
|
||||||
|
} while (awaiters != nullptr);
|
||||||
|
|
||||||
|
// Null-terinate the requeue list
|
||||||
|
*awaitersToRequeueTail = nullptr;
|
||||||
|
|
||||||
|
if (awaitersToRequeue != nullptr)
|
||||||
|
{
|
||||||
|
// Requeue the waiters that are not ready yet.
|
||||||
|
awaiter_t* oldHead = nullptr;
|
||||||
|
while (!m_awaiters.compare_exchange_weak(oldHead, awaitersToRequeue, std::memory_order_seq_cst, std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
*awaitersToRequeueTail = oldHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the awaitersToRequeue list
|
||||||
|
awaitersToRequeueTail = &awaitersToRequeue;
|
||||||
|
|
||||||
|
const SEQUENCE earliestTargetSequence = lastKnownPublished + minDiff;
|
||||||
|
|
||||||
|
// Now we need to check again to see if any of the waiters we just enqueued
|
||||||
|
// is now satisfied by a concurrent call to publish().
|
||||||
|
//
|
||||||
|
// We need to be a bit more careful here since we are no longer holding any
|
||||||
|
// awaiters and so producers/consumers may advance the sequence number arbitrarily
|
||||||
|
// far. If the sequence number advances more than buffer_size() ahead of the
|
||||||
|
// earliestTargetSequence then the m_published[] array may have sequence numbers
|
||||||
|
// that have advanced beyond earliestTargetSequence, potentially even wrapping
|
||||||
|
// sequence numbers around to then be preceding where they were before. If this
|
||||||
|
// happens then we don't need to worry about resuming any awaiters that were waiting
|
||||||
|
// for 'earliestTargetSequence' since some other thread has already resumed them.
|
||||||
|
// So the only case we need to worry about here is when all m_published entries for
|
||||||
|
// sequence numbers in range [lastKnownPublished + 1, earliestTargetSequence] have
|
||||||
|
// published sequence numbers that match the range.
|
||||||
|
const auto sequenceMask = m_sequenceMask;
|
||||||
|
SEQUENCE seq = lastKnownPublished + 1;
|
||||||
|
while (m_published[seq & sequenceMask].load(std::memory_order_seq_cst) == seq)
|
||||||
|
{
|
||||||
|
lastKnownPublished = seq;
|
||||||
|
if (seq == earliestTargetSequence)
|
||||||
|
{
|
||||||
|
// At least one of the awaiters we just published is now satisfied.
|
||||||
|
// Reacquire the list of awaiters and continue around the outer loop.
|
||||||
|
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++seq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (awaiters != nullptr);
|
||||||
|
|
||||||
|
// Null-terminate list of awaiters to resume.
|
||||||
|
*awaitersToResumeTail = nullptr;
|
||||||
|
|
||||||
|
while (awaitersToResume != nullptr)
|
||||||
|
{
|
||||||
|
awaiter_t* next = awaitersToResume->m_next;
|
||||||
|
awaitersToResume->resume(lastKnownPublished);
|
||||||
|
awaitersToResume = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void multi_producer_sequencer<SEQUENCE, TRAITS>::add_awaiter(
|
||||||
|
multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>* awaiter) const noexcept
|
||||||
|
{
|
||||||
|
using awaiter_t = multi_producer_sequencer_wait_operation_base<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
SEQUENCE targetSequence = awaiter->m_targetSequence;
|
||||||
|
SEQUENCE lastKnownPublished = awaiter->m_lastKnownPublished;
|
||||||
|
|
||||||
|
awaiter_t* awaitersToEnqueue = awaiter;
|
||||||
|
awaiter_t** awaitersToEnqueueTail = &awaiter->m_next;
|
||||||
|
|
||||||
|
awaiter_t* awaitersToResume;
|
||||||
|
awaiter_t** awaitersToResumeTail = &awaitersToResume;
|
||||||
|
|
||||||
|
const SEQUENCE sequenceMask = m_sequenceMask;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Enqueue the awaiters.
|
||||||
|
{
|
||||||
|
awaiter_t* oldHead = m_awaiters.load(std::memory_order_relaxed);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
*awaitersToEnqueueTail = oldHead;
|
||||||
|
} while (!m_awaiters.compare_exchange_weak(
|
||||||
|
oldHead,
|
||||||
|
awaitersToEnqueue,
|
||||||
|
std::memory_order_seq_cst,
|
||||||
|
std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset list of waiters
|
||||||
|
awaitersToEnqueueTail = &awaitersToEnqueue;
|
||||||
|
|
||||||
|
// Check to see if the last-known published sequence number has advanced
|
||||||
|
// while we were enqueuing the awaiters. Need to use seq_cst memory order
|
||||||
|
// here to ensure that if there are concurrent calls to publish() that would
|
||||||
|
// wake up any of the awaiters we just enqueued that either we will see their
|
||||||
|
// write to m_published slots or they will see our write to m_awaiters.
|
||||||
|
//
|
||||||
|
// Note also, that we are assuming that the last-known published sequence is
|
||||||
|
// not going to advance more than buffer_size() ahead of targetSequence since
|
||||||
|
// there is at least one consumer that won't be resumed and so thus can't
|
||||||
|
// publish the sequence number it's waiting for to its sequence_barrier and so
|
||||||
|
// producers won't be able to claim its slot in the buffer.
|
||||||
|
//
|
||||||
|
// TODO: Check whether we can weaken the memory order here to just use 'seq_cst' on the
|
||||||
|
// first .load() and then use 'acquire' on subsequent .load().
|
||||||
|
while (m_published[(lastKnownPublished + 1) & sequenceMask].load(std::memory_order_seq_cst) == (lastKnownPublished + 1))
|
||||||
|
{
|
||||||
|
++lastKnownPublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TRAITS::precedes(lastKnownPublished, targetSequence))
|
||||||
|
{
|
||||||
|
// At least one awaiter we just enqueued has now been satisified.
|
||||||
|
// To ensure it is woken up we need to reacquire the list of awaiters and resume
|
||||||
|
awaiter_t* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
|
||||||
|
using diff_t = typename TRAITS::difference_type;
|
||||||
|
|
||||||
|
diff_t minDiff = std::numeric_limits<diff_t>::max();
|
||||||
|
|
||||||
|
while (awaiters != nullptr)
|
||||||
|
{
|
||||||
|
diff_t diff = TRAITS::difference(targetSequence, lastKnownPublished);
|
||||||
|
if (diff > 0)
|
||||||
|
{
|
||||||
|
// Not yet ready.
|
||||||
|
minDiff = diff < minDiff ? diff : minDiff;
|
||||||
|
*awaitersToEnqueueTail = awaiters;
|
||||||
|
awaitersToEnqueueTail = &awaiters->m_next;
|
||||||
|
awaiters->m_lastKnownPublished = lastKnownPublished;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Now ready.
|
||||||
|
*awaitersToResumeTail = awaiters;
|
||||||
|
awaitersToResumeTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
awaiters = awaiters->m_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the earliest sequence number that any awaiters in the
|
||||||
|
// awaitersToEnqueue list are waiting for. We'll use this next time
|
||||||
|
// around the loop.
|
||||||
|
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null-terminate list of awaiters to enqueue.
|
||||||
|
*awaitersToEnqueueTail = nullptr;
|
||||||
|
|
||||||
|
} while (awaitersToEnqueue != nullptr);
|
||||||
|
|
||||||
|
// Null-terminate awaiters to resume.
|
||||||
|
*awaitersToResumeTail = nullptr;
|
||||||
|
|
||||||
|
// Finally, resume any awaiters we've found that are ready to go.
|
||||||
|
while (awaitersToResume != nullptr)
|
||||||
|
{
|
||||||
|
// Read m_next before calling .resume() as resuming could destroy the awaiter.
|
||||||
|
awaiter_t* next = awaitersToResume->m_next;
|
||||||
|
awaitersToResume->resume(lastKnownPublished);
|
||||||
|
awaitersToResume = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/net/ipv4_address.hpp>
|
||||||
|
#include <cppcoro/net/ipv6_address.hpp>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class ip_address
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Constructs to IPv4 address 0.0.0.0
|
||||||
|
ip_address() noexcept;
|
||||||
|
|
||||||
|
ip_address(ipv4_address address) noexcept;
|
||||||
|
ip_address(ipv6_address address) noexcept;
|
||||||
|
|
||||||
|
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
|
||||||
|
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
|
||||||
|
|
||||||
|
const ipv4_address& to_ipv4() const;
|
||||||
|
const ipv6_address& to_ipv6() const;
|
||||||
|
|
||||||
|
const std::uint8_t* bytes() const noexcept;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
static std::optional<ip_address> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
bool operator==(const ip_address& rhs) const noexcept;
|
||||||
|
bool operator!=(const ip_address& rhs) const noexcept;
|
||||||
|
|
||||||
|
// ipv4_address sorts less than ipv6_address
|
||||||
|
bool operator<(const ip_address& rhs) const noexcept;
|
||||||
|
bool operator>(const ip_address& rhs) const noexcept;
|
||||||
|
bool operator<=(const ip_address& rhs) const noexcept;
|
||||||
|
bool operator>=(const ip_address& rhs) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum class family
|
||||||
|
{
|
||||||
|
ipv4,
|
||||||
|
ipv6
|
||||||
|
};
|
||||||
|
|
||||||
|
family m_family;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ipv4_address m_ipv4;
|
||||||
|
ipv6_address m_ipv6;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ip_address::ip_address() noexcept
|
||||||
|
: m_family(family::ipv4)
|
||||||
|
, m_ipv4()
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline ip_address::ip_address(ipv4_address address) noexcept
|
||||||
|
: m_family(family::ipv4)
|
||||||
|
, m_ipv4(address)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline ip_address::ip_address(ipv6_address address) noexcept
|
||||||
|
: m_family(family::ipv6)
|
||||||
|
, m_ipv6(address)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const ipv4_address& ip_address::to_ipv4() const
|
||||||
|
{
|
||||||
|
assert(is_ipv4());
|
||||||
|
return m_ipv4;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const ipv6_address& ip_address::to_ipv6() const
|
||||||
|
{
|
||||||
|
assert(is_ipv6());
|
||||||
|
return m_ipv6;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::uint8_t* ip_address::bytes() const noexcept
|
||||||
|
{
|
||||||
|
return is_ipv4() ? m_ipv4.bytes() : m_ipv6.bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator==(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
if (is_ipv4())
|
||||||
|
{
|
||||||
|
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator!=(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator<(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
if (is_ipv4())
|
||||||
|
{
|
||||||
|
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator>(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return rhs < *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator<=(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(rhs < *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_address::operator>=(const ip_address& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(*this < rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IP_ENDPOINT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/net/ip_address.hpp>
|
||||||
|
#include <cppcoro/net/ipv4_endpoint.hpp>
|
||||||
|
#include <cppcoro/net/ipv6_endpoint.hpp>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class ip_endpoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Constructs to IPv4 end-point 0.0.0.0:0
|
||||||
|
ip_endpoint() noexcept;
|
||||||
|
|
||||||
|
ip_endpoint(ipv4_endpoint endpoint) noexcept;
|
||||||
|
ip_endpoint(ipv6_endpoint endpoint) noexcept;
|
||||||
|
|
||||||
|
bool is_ipv4() const noexcept { return m_family == family::ipv4; }
|
||||||
|
bool is_ipv6() const noexcept { return m_family == family::ipv6; }
|
||||||
|
|
||||||
|
const ipv4_endpoint& to_ipv4() const;
|
||||||
|
const ipv6_endpoint& to_ipv6() const;
|
||||||
|
|
||||||
|
ip_address address() const noexcept;
|
||||||
|
std::uint16_t port() const noexcept;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
static std::optional<ip_endpoint> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
bool operator==(const ip_endpoint& rhs) const noexcept;
|
||||||
|
bool operator!=(const ip_endpoint& rhs) const noexcept;
|
||||||
|
|
||||||
|
// ipv4_endpoint sorts less than ipv6_endpoint
|
||||||
|
bool operator<(const ip_endpoint& rhs) const noexcept;
|
||||||
|
bool operator>(const ip_endpoint& rhs) const noexcept;
|
||||||
|
bool operator<=(const ip_endpoint& rhs) const noexcept;
|
||||||
|
bool operator>=(const ip_endpoint& rhs) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum class family
|
||||||
|
{
|
||||||
|
ipv4,
|
||||||
|
ipv6
|
||||||
|
};
|
||||||
|
|
||||||
|
family m_family;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ipv4_endpoint m_ipv4;
|
||||||
|
ipv6_endpoint m_ipv6;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ip_endpoint::ip_endpoint() noexcept
|
||||||
|
: m_family(family::ipv4)
|
||||||
|
, m_ipv4()
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline ip_endpoint::ip_endpoint(ipv4_endpoint endpoint) noexcept
|
||||||
|
: m_family(family::ipv4)
|
||||||
|
, m_ipv4(endpoint)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline ip_endpoint::ip_endpoint(ipv6_endpoint endpoint) noexcept
|
||||||
|
: m_family(family::ipv6)
|
||||||
|
, m_ipv6(endpoint)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const ipv4_endpoint& ip_endpoint::to_ipv4() const
|
||||||
|
{
|
||||||
|
assert(is_ipv4());
|
||||||
|
return m_ipv4;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const ipv6_endpoint& ip_endpoint::to_ipv6() const
|
||||||
|
{
|
||||||
|
assert(is_ipv6());
|
||||||
|
return m_ipv6;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ip_address ip_endpoint::address() const noexcept
|
||||||
|
{
|
||||||
|
if (is_ipv4())
|
||||||
|
{
|
||||||
|
return m_ipv4.address();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m_ipv6.address();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::uint16_t ip_endpoint::port() const noexcept
|
||||||
|
{
|
||||||
|
return is_ipv4() ? m_ipv4.port() : m_ipv6.port();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator==(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
if (is_ipv4())
|
||||||
|
{
|
||||||
|
return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator!=(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator<(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
if (is_ipv4())
|
||||||
|
{
|
||||||
|
return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator>(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return rhs < *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator<=(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(rhs < *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ip_endpoint::operator>=(const ip_endpoint& rhs) const noexcept
|
||||||
|
{
|
||||||
|
return !(*this < rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class ipv4_address
|
||||||
|
{
|
||||||
|
using bytes_t = std::uint8_t[4];
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
constexpr ipv4_address()
|
||||||
|
: m_bytes{ 0, 0, 0, 0 }
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit constexpr ipv4_address(std::uint32_t integer)
|
||||||
|
: m_bytes{
|
||||||
|
static_cast<std::uint8_t>(integer >> 24),
|
||||||
|
static_cast<std::uint8_t>(integer >> 16),
|
||||||
|
static_cast<std::uint8_t>(integer >> 8),
|
||||||
|
static_cast<std::uint8_t>(integer) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit constexpr ipv4_address(const std::uint8_t(&bytes)[4])
|
||||||
|
: m_bytes{ bytes[0], bytes[1], bytes[2], bytes[3] }
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit constexpr ipv4_address(
|
||||||
|
std::uint8_t b0,
|
||||||
|
std::uint8_t b1,
|
||||||
|
std::uint8_t b2,
|
||||||
|
std::uint8_t b3)
|
||||||
|
: m_bytes{ b0, b1, b2, b3 }
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr const bytes_t& bytes() const { return m_bytes; }
|
||||||
|
|
||||||
|
constexpr std::uint32_t to_integer() const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
std::uint32_t(m_bytes[0]) << 24 |
|
||||||
|
std::uint32_t(m_bytes[1]) << 16 |
|
||||||
|
std::uint32_t(m_bytes[2]) << 8 |
|
||||||
|
std::uint32_t(m_bytes[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr ipv4_address loopback()
|
||||||
|
{
|
||||||
|
return ipv4_address(127, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool is_loopback() const
|
||||||
|
{
|
||||||
|
return m_bytes[0] == 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool is_private_network() const
|
||||||
|
{
|
||||||
|
return m_bytes[0] == 10 ||
|
||||||
|
(m_bytes[0] == 172 && (m_bytes[1] & 0xF0) == 0x10) ||
|
||||||
|
(m_bytes[0] == 192 && m_bytes[2] == 168);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
m_bytes[0] == other.m_bytes[0] &&
|
||||||
|
m_bytes[1] == other.m_bytes[1] &&
|
||||||
|
m_bytes[2] == other.m_bytes[2] &&
|
||||||
|
m_bytes[3] == other.m_bytes[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator<(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return to_integer() < other.to_integer();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator>(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return other < *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator<=(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return !(other < *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator>=(ipv4_address other) const
|
||||||
|
{
|
||||||
|
return !(*this < other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a string representation of an IP address.
|
||||||
|
///
|
||||||
|
/// Parses strings of the form:
|
||||||
|
/// - "num.num.num.num" where num is an integer in range [0, 255].
|
||||||
|
/// - A single integer value in range [0, 2^32).
|
||||||
|
///
|
||||||
|
/// \param string
|
||||||
|
/// The string to parse.
|
||||||
|
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The IP address if successful, otherwise std::nullopt if the string
|
||||||
|
/// could not be parsed as an IPv4 address.
|
||||||
|
static std::optional<ipv4_address> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
/// Convert the IP address to dotted decimal notation.
|
||||||
|
///
|
||||||
|
/// eg. "12.67.190.23"
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
alignas(std::uint32_t) std::uint8_t m_bytes[4];
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/net/ipv4_address.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class ipv4_endpoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Construct to 0.0.0.0:0
|
||||||
|
ipv4_endpoint() noexcept
|
||||||
|
: m_address()
|
||||||
|
, m_port(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit ipv4_endpoint(ipv4_address address, std::uint16_t port = 0) noexcept
|
||||||
|
: m_address(address)
|
||||||
|
, m_port(port)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const ipv4_address& address() const noexcept { return m_address; }
|
||||||
|
|
||||||
|
std::uint16_t port() const noexcept { return m_port; }
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
static std::optional<ipv4_endpoint> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ipv4_address m_address;
|
||||||
|
std::uint16_t m_port;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return a.address() == b.address() &&
|
||||||
|
a.port() == b.port();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator<(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return a.address() < b.address() ||
|
||||||
|
(a.address() == b.address() && a.port() < b.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator>(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return b < a;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator<=(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(b < a);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator>=(const ipv4_endpoint& a, const ipv4_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(a < b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IPV6_ADDRESS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class ipv4_address;
|
||||||
|
|
||||||
|
class ipv6_address
|
||||||
|
{
|
||||||
|
using bytes_t = std::uint8_t[16];
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
constexpr ipv6_address();
|
||||||
|
|
||||||
|
explicit constexpr ipv6_address(
|
||||||
|
std::uint64_t subnetPrefix,
|
||||||
|
std::uint64_t interfaceIdentifier);
|
||||||
|
|
||||||
|
constexpr ipv6_address(
|
||||||
|
std::uint16_t part0,
|
||||||
|
std::uint16_t part1,
|
||||||
|
std::uint16_t part2,
|
||||||
|
std::uint16_t part3,
|
||||||
|
std::uint16_t part4,
|
||||||
|
std::uint16_t part5,
|
||||||
|
std::uint16_t part6,
|
||||||
|
std::uint16_t part7);
|
||||||
|
|
||||||
|
explicit constexpr ipv6_address(
|
||||||
|
const std::uint16_t(&parts)[8]);
|
||||||
|
|
||||||
|
explicit constexpr ipv6_address(
|
||||||
|
const std::uint8_t(&bytes)[16]);
|
||||||
|
|
||||||
|
constexpr const bytes_t& bytes() const { return m_bytes; }
|
||||||
|
|
||||||
|
constexpr std::uint64_t subnet_prefix() const;
|
||||||
|
|
||||||
|
constexpr std::uint64_t interface_identifier() const;
|
||||||
|
|
||||||
|
/// Get the IPv6 unspedified address :: (all zeroes).
|
||||||
|
static constexpr ipv6_address unspecified();
|
||||||
|
|
||||||
|
/// Get the IPv6 loopback address ::1.
|
||||||
|
static constexpr ipv6_address loopback();
|
||||||
|
|
||||||
|
/// Parse a string representation of an IPv6 address.
|
||||||
|
///
|
||||||
|
/// \param string
|
||||||
|
/// The string to parse.
|
||||||
|
/// Must be in ASCII, UTF-8 or Latin-1 encoding.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The IP address if successful, otherwise std::nullopt if the string
|
||||||
|
/// could not be parsed as an IPv4 address.
|
||||||
|
static std::optional<ipv6_address> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
/// Convert the IP address to contracted string form.
|
||||||
|
///
|
||||||
|
/// Address is broken up into 16-bit parts, with each part represended in 1-4
|
||||||
|
/// lower-case hexadecimal with leading zeroes omitted. Parts are separated
|
||||||
|
/// by separated by a ':'. The longest contiguous run of zero parts is contracted
|
||||||
|
/// to "::".
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ipv6_address::unspecified() -> "::"
|
||||||
|
/// ipv6_address::loopback() -> "::1"
|
||||||
|
/// ipv6_address(0x0011223344556677, 0x8899aabbccddeeff) ->
|
||||||
|
/// "11:2233:4455:6677:8899:aabb:ccdd:eeff"
|
||||||
|
/// ipv6_address(0x0102030400000000, 0x003fc447ab991011) ->
|
||||||
|
/// "102:304::3f:c447:ab99:1011"
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
constexpr bool operator==(const ipv6_address& other) const;
|
||||||
|
constexpr bool operator!=(const ipv6_address& other) const;
|
||||||
|
constexpr bool operator<(const ipv6_address& other) const;
|
||||||
|
constexpr bool operator>(const ipv6_address& other) const;
|
||||||
|
constexpr bool operator<=(const ipv6_address& other) const;
|
||||||
|
constexpr bool operator>=(const ipv6_address& other) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
alignas(std::uint64_t) std::uint8_t m_bytes[16];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr ipv6_address::ipv6_address()
|
||||||
|
: m_bytes{
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0 }
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr ipv6_address::ipv6_address(
|
||||||
|
std::uint64_t subnetPrefix,
|
||||||
|
std::uint64_t interfaceIdentifier)
|
||||||
|
: m_bytes{
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 56),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 48),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 40),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 32),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 24),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 16),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix >> 8),
|
||||||
|
static_cast<std::uint8_t>(subnetPrefix),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 56),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 48),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 40),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 32),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 24),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 16),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier >> 8),
|
||||||
|
static_cast<std::uint8_t>(interfaceIdentifier) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr ipv6_address::ipv6_address(
|
||||||
|
std::uint16_t part0,
|
||||||
|
std::uint16_t part1,
|
||||||
|
std::uint16_t part2,
|
||||||
|
std::uint16_t part3,
|
||||||
|
std::uint16_t part4,
|
||||||
|
std::uint16_t part5,
|
||||||
|
std::uint16_t part6,
|
||||||
|
std::uint16_t part7)
|
||||||
|
: m_bytes{
|
||||||
|
static_cast<std::uint8_t>(part0 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part0),
|
||||||
|
static_cast<std::uint8_t>(part1 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part1),
|
||||||
|
static_cast<std::uint8_t>(part2 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part2),
|
||||||
|
static_cast<std::uint8_t>(part3 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part3),
|
||||||
|
static_cast<std::uint8_t>(part4 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part4),
|
||||||
|
static_cast<std::uint8_t>(part5 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part5),
|
||||||
|
static_cast<std::uint8_t>(part6 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part6),
|
||||||
|
static_cast<std::uint8_t>(part7 >> 8),
|
||||||
|
static_cast<std::uint8_t>(part7) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr ipv6_address::ipv6_address(
|
||||||
|
const std::uint16_t(&parts)[8])
|
||||||
|
: ipv6_address(
|
||||||
|
parts[0], parts[1], parts[2], parts[3],
|
||||||
|
parts[4], parts[5], parts[6], parts[7])
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr ipv6_address::ipv6_address(const std::uint8_t(&bytes)[16])
|
||||||
|
: m_bytes{
|
||||||
|
bytes[0], bytes[1], bytes[2], bytes[3],
|
||||||
|
bytes[4], bytes[5], bytes[6], bytes[7],
|
||||||
|
bytes[8], bytes[9], bytes[10], bytes[11],
|
||||||
|
bytes[12], bytes[13], bytes[14], bytes[15] }
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr std::uint64_t ipv6_address::subnet_prefix() const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
static_cast<std::uint64_t>(m_bytes[0]) << 56 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[1]) << 48 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[2]) << 40 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[3]) << 32 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[4]) << 24 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[5]) << 16 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[6]) << 8 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::uint64_t ipv6_address::interface_identifier() const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
static_cast<std::uint64_t>(m_bytes[8]) << 56 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[9]) << 48 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[10]) << 40 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[11]) << 32 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[12]) << 24 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[13]) << 16 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[14]) << 8 |
|
||||||
|
static_cast<std::uint64_t>(m_bytes[15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ipv6_address ipv6_address::unspecified()
|
||||||
|
{
|
||||||
|
return ipv6_address{};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ipv6_address ipv6_address::loopback()
|
||||||
|
{
|
||||||
|
return ipv6_address{ 0, 0, 0, 0, 0, 0, 0, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator==(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
if (m_bytes[i] != other.m_bytes[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator!=(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator<(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
if (m_bytes[i] != other.m_bytes[i])
|
||||||
|
return m_bytes[i] < other.m_bytes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator>(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
return (other < *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator<=(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
return !(other < *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool ipv6_address::operator>=(const ipv6_address& other) const
|
||||||
|
{
|
||||||
|
return !(*this < other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/net/ipv6_address.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class ipv6_endpoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Construct to [::]:0
|
||||||
|
ipv6_endpoint() noexcept
|
||||||
|
: m_address()
|
||||||
|
, m_port(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit ipv6_endpoint(ipv6_address address, std::uint16_t port = 0) noexcept
|
||||||
|
: m_address(address)
|
||||||
|
, m_port(port)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const ipv6_address& address() const noexcept { return m_address; }
|
||||||
|
|
||||||
|
std::uint16_t port() const noexcept { return m_port; }
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
static std::optional<ipv6_endpoint> from_string(std::string_view string) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ipv6_address m_address;
|
||||||
|
std::uint16_t m_port;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return a.address() == b.address() &&
|
||||||
|
a.port() == b.port();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator<(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return a.address() < b.address() ||
|
||||||
|
(a.address() == b.address() && a.port() < b.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator>(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return b < a;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator<=(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(b < a);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator>=(const ipv6_endpoint& a, const ipv6_endpoint& b)
|
||||||
|
{
|
||||||
|
return !(a < b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/net/ip_endpoint.hpp>
|
||||||
|
#include <cppcoro/net/socket_accept_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_connect_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_disconnect_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_recv_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_recv_from_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_send_operation.hpp>
|
||||||
|
#include <cppcoro/net/socket_send_to_operation.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class io_service;
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Create a socket that can be used to communicate using TCP/IPv4 protocol.
|
||||||
|
///
|
||||||
|
/// \param ioSvc
|
||||||
|
/// The I/O service the socket will use for dispatching I/O completion events.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The newly created socket.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be created for some reason.
|
||||||
|
static socket create_tcpv4(io_service& ioSvc);
|
||||||
|
|
||||||
|
/// Create a socket that can be used to communicate using TCP/IPv6 protocol.
|
||||||
|
///
|
||||||
|
/// \param ioSvc
|
||||||
|
/// The I/O service the socket will use for dispatching I/O completion events.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The newly created socket.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be created for some reason.
|
||||||
|
static socket create_tcpv6(io_service& ioSvc);
|
||||||
|
|
||||||
|
/// Create a socket that can be used to communicate using UDP/IPv4 protocol.
|
||||||
|
///
|
||||||
|
/// \param ioSvc
|
||||||
|
/// The I/O service the socket will use for dispatching I/O completion events.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The newly created socket.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be created for some reason.
|
||||||
|
static socket create_udpv4(io_service& ioSvc);
|
||||||
|
|
||||||
|
/// Create a socket that can be used to communicate using UDP/IPv6 protocol.
|
||||||
|
///
|
||||||
|
/// \param ioSvc
|
||||||
|
/// The I/O service the socket will use for dispatching I/O completion events.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// The newly created socket.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be created for some reason.
|
||||||
|
static socket create_udpv6(io_service& ioSvc);
|
||||||
|
|
||||||
|
socket(socket&& other) noexcept;
|
||||||
|
|
||||||
|
/// Closes the socket, releasing any associated resources.
|
||||||
|
///
|
||||||
|
/// If the socket still has an open connection then the connection will be
|
||||||
|
/// reset. The destructor will not block waiting for queueud data to be sent.
|
||||||
|
/// If you need to ensure that queued data is delivered then you must call
|
||||||
|
/// disconnect() and wait until the disconnect operation completes.
|
||||||
|
~socket();
|
||||||
|
|
||||||
|
socket& operator=(socket&& other) noexcept;
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
/// Get the Win32 socket handle assocaited with this socket.
|
||||||
|
cppcoro::detail::win32::socket_t native_handle() noexcept { return m_handle; }
|
||||||
|
|
||||||
|
/// Query whether I/O operations that complete synchronously will skip posting
|
||||||
|
/// an I/O completion event to the I/O completion port.
|
||||||
|
///
|
||||||
|
/// The operation class implementations can use this to determine whether or not
|
||||||
|
/// it should immediately resume the coroutine on the current thread upon an
|
||||||
|
/// operation completing synchronously or whether it should suspend the coroutine
|
||||||
|
/// and wait until the I/O completion event is dispatched to an I/O thread.
|
||||||
|
bool skip_completion_on_success() noexcept { return m_skipCompletionOnSuccess; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Get the address and port of the local end-point.
|
||||||
|
///
|
||||||
|
/// If the socket is not bound then this will be the unspecified end-point
|
||||||
|
/// of the socket's associated address-family.
|
||||||
|
const ip_endpoint& local_endpoint() const noexcept { return m_localEndPoint; }
|
||||||
|
|
||||||
|
/// Get the address and port of the remote end-point.
|
||||||
|
///
|
||||||
|
/// If the socket is not in the connected state then this will be the unspecified
|
||||||
|
/// end-point of the socket's associated address-family.
|
||||||
|
const ip_endpoint& remote_endpoint() const noexcept { return m_remoteEndPoint; }
|
||||||
|
|
||||||
|
/// Bind the local end of this socket to the specified local end-point.
|
||||||
|
///
|
||||||
|
/// \param localEndPoint
|
||||||
|
/// The end-point to bind to.
|
||||||
|
/// This can be either an unspecified address (in which case it binds to all available
|
||||||
|
/// interfaces) and/or an unspecified port (in which case a random port is allocated).
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be bound for some reason.
|
||||||
|
void bind(const ip_endpoint& localEndPoint);
|
||||||
|
|
||||||
|
/// Put the socket into a passive listening state that will start acknowledging
|
||||||
|
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
|
||||||
|
///
|
||||||
|
/// The backlog of connections ready to be accepted will be set to some default
|
||||||
|
/// suitable large value, depending on the network provider. If you need more
|
||||||
|
/// control over the size of the queue then use the overload of listen()
|
||||||
|
/// that accepts a 'backlog' parameter.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be placed into a listening mode.
|
||||||
|
void listen();
|
||||||
|
|
||||||
|
/// Put the socket into a passive listening state that will start acknowledging
|
||||||
|
/// and queueing up new connections ready to be accepted by a call to 'accept()'.
|
||||||
|
///
|
||||||
|
/// \param backlog
|
||||||
|
/// The maximum number of pending connections to allow in the queue of ready-to-accept
|
||||||
|
/// connections.
|
||||||
|
///
|
||||||
|
/// \throws std::system_error
|
||||||
|
/// If the socket could not be placed into a listening mode.
|
||||||
|
void listen(std::uint32_t backlog);
|
||||||
|
|
||||||
|
/// Connect the socket to the specified remote end-point.
|
||||||
|
///
|
||||||
|
/// The socket must be in a bound but unconnected state prior to this call.
|
||||||
|
///
|
||||||
|
/// \param remoteEndPoint
|
||||||
|
/// The IP address and port-number to connect to.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An awaitable object that must be co_await'ed to perform the async connect
|
||||||
|
/// operation. The result of the co_await expression is type void.
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_connect_operation connect(const ip_endpoint& remoteEndPoint) noexcept;
|
||||||
|
|
||||||
|
/// Connect to the specified remote end-point.
|
||||||
|
///
|
||||||
|
/// \param remoteEndPoint
|
||||||
|
/// The IP address and port of the remote end-point to connect to.
|
||||||
|
///
|
||||||
|
/// \param ct
|
||||||
|
/// A cancellation token that can be used to communicate a request to
|
||||||
|
/// later cancel the operation. If the operation is successfully
|
||||||
|
/// cancelled then it will complete by throwing a cppcoro::operation_cancelled
|
||||||
|
/// exception.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An awaitable object that will start the connect operation when co_await'ed
|
||||||
|
/// and will suspend the coroutine, resuming it when the operation completes.
|
||||||
|
/// The result of the co_await expression has type 'void'.
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_connect_operation_cancellable connect(
|
||||||
|
const ip_endpoint& remoteEndPoint,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_accept_operation accept(socket& acceptingSocket) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_accept_operation_cancellable accept(
|
||||||
|
socket& acceptingSocket,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_disconnect_operation disconnect() noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_disconnect_operation_cancellable disconnect(cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_send_operation send(
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t size) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_send_operation_cancellable send(
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t size,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_recv_operation recv(
|
||||||
|
void* buffer,
|
||||||
|
std::size_t size) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_recv_operation_cancellable recv(
|
||||||
|
void* buffer,
|
||||||
|
std::size_t size,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_recv_from_operation recv_from(
|
||||||
|
void* buffer,
|
||||||
|
std::size_t size) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_recv_from_operation_cancellable recv_from(
|
||||||
|
void* buffer,
|
||||||
|
std::size_t size,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_send_to_operation send_to(
|
||||||
|
const ip_endpoint& destination,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t size) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
socket_send_to_operation_cancellable send_to(
|
||||||
|
const ip_endpoint& destination,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t size,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
void close_send();
|
||||||
|
void close_recv();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class socket_accept_operation_impl;
|
||||||
|
friend class socket_connect_operation_impl;
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
explicit socket(
|
||||||
|
cppcoro::detail::win32::socket_t handle,
|
||||||
|
bool skipCompletionOnSuccess) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
cppcoro::detail::win32::socket_t m_handle;
|
||||||
|
bool m_skipCompletionOnSuccess;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ip_endpoint m_localEndPoint;
|
||||||
|
ip_endpoint m_remoteEndPoint;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_ACCEPT_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
# include <atomic>
|
||||||
|
# include <optional>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_accept_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_accept_operation_impl(
|
||||||
|
socket& listeningSocket,
|
||||||
|
socket& acceptingSocket) noexcept
|
||||||
|
: m_listeningSocket(listeningSocket)
|
||||||
|
, m_acceptingSocket(acceptingSocket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4324) // Structure padded due to alignment
|
||||||
|
#endif
|
||||||
|
|
||||||
|
socket& m_listeningSocket;
|
||||||
|
socket& m_acceptingSocket;
|
||||||
|
alignas(8) std::uint8_t m_addressBuffer[88];
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_accept_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_accept_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_accept_operation(
|
||||||
|
socket& listeningSocket,
|
||||||
|
socket& acceptingSocket) noexcept
|
||||||
|
: m_impl(listeningSocket, acceptingSocket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_accept_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void get_result() { m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_accept_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_accept_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_accept_operation_cancellable(
|
||||||
|
socket& listeningSocket,
|
||||||
|
socket& acceptingSocket,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(listeningSocket, acceptingSocket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_accept_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
void get_result() { m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_accept_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/net/ip_endpoint.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_connect_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_connect_operation_impl(
|
||||||
|
socket& socket,
|
||||||
|
const ip_endpoint& remoteEndPoint) noexcept
|
||||||
|
: m_socket(socket)
|
||||||
|
, m_remoteEndPoint(remoteEndPoint)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
ip_endpoint m_remoteEndPoint;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_connect_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_connect_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_connect_operation(
|
||||||
|
socket& socket,
|
||||||
|
const ip_endpoint& remoteEndPoint) noexcept
|
||||||
|
: m_impl(socket, remoteEndPoint)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_connect_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
decltype(auto) get_result() { return m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_connect_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_connect_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_connect_operation_cancellable(
|
||||||
|
socket& socket,
|
||||||
|
const ip_endpoint& remoteEndPoint,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(socket, remoteEndPoint)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_connect_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
void get_result() { m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_connect_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_disconnect_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_disconnect_operation_impl(socket& socket) noexcept
|
||||||
|
: m_socket(socket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void get_result(cppcoro::detail::win32_overlapped_operation_base& operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_disconnect_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_disconnect_operation(socket& socket) noexcept
|
||||||
|
: m_impl(socket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_disconnect_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void get_result() { m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_disconnect_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_disconnect_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_disconnect_operation_cancellable(socket& socket, cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(socket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_disconnect_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
void get_result() { m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_disconnect_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_RECV_FROM_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/net/ip_endpoint.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_recv_from_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_from_operation_impl(
|
||||||
|
socket& socket,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_socket(socket)
|
||||||
|
, m_buffer(buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
std::tuple<std::size_t, ip_endpoint> get_result(
|
||||||
|
cppcoro::detail::win32_overlapped_operation_base& operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
cppcoro::detail::win32::wsabuf m_buffer;
|
||||||
|
|
||||||
|
static constexpr std::size_t sockaddrStorageAlignment = 4;
|
||||||
|
|
||||||
|
// Storage suitable for either SOCKADDR_IN or SOCKADDR_IN6
|
||||||
|
alignas(sockaddrStorageAlignment) std::uint8_t m_sourceSockaddrStorage[28];
|
||||||
|
int m_sourceSockaddrLength;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_recv_from_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_from_operation(
|
||||||
|
socket& socket,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_impl(socket, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_from_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
decltype(auto) get_result() { return m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_recv_from_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_recv_from_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_from_operation_cancellable(
|
||||||
|
socket& socket,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(socket, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_from_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
decltype(auto) get_result() { return m_impl.get_result(*this); }
|
||||||
|
|
||||||
|
socket_recv_from_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_recv_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_operation_impl(
|
||||||
|
socket& s,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_socket(s)
|
||||||
|
, m_buffer(buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
cppcoro::detail::win32::wsabuf m_buffer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_recv_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_recv_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_operation(
|
||||||
|
socket& s,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_impl(s, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_recv_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
|
||||||
|
socket_recv_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_recv_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_recv_operation_cancellable(
|
||||||
|
socket& s,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(s, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_recv_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { m_impl.cancel(*this); }
|
||||||
|
|
||||||
|
socket_recv_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_send_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_operation_impl(
|
||||||
|
socket& s,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_socket(s)
|
||||||
|
, m_buffer(const_cast<void*>(buffer), byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
cppcoro::detail::win32::wsabuf m_buffer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_send_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_send_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_operation(
|
||||||
|
socket& s,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_impl(s, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_send_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
|
||||||
|
socket_send_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_send_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_operation_cancellable(
|
||||||
|
socket& s,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(s, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { return m_impl.cancel(*this); }
|
||||||
|
|
||||||
|
socket_send_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
|
||||||
|
#define CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/net/ip_endpoint.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
# include <cppcoro/detail/win32.hpp>
|
||||||
|
# include <cppcoro/detail/win32_overlapped_operation.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro::net
|
||||||
|
{
|
||||||
|
class socket;
|
||||||
|
|
||||||
|
class socket_send_to_operation_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_to_operation_impl(
|
||||||
|
socket& s,
|
||||||
|
const ip_endpoint& destination,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_socket(s)
|
||||||
|
, m_destination(destination)
|
||||||
|
, m_buffer(const_cast<void*>(buffer), byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool try_start(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
void cancel(cppcoro::detail::win32_overlapped_operation_base& operation) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
socket& m_socket;
|
||||||
|
ip_endpoint m_destination;
|
||||||
|
cppcoro::detail::win32::wsabuf m_buffer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_send_to_operation
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_to_operation(
|
||||||
|
socket& s,
|
||||||
|
const ip_endpoint& destination,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept
|
||||||
|
: m_impl(s, destination, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation<socket_send_to_operation>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
|
||||||
|
socket_send_to_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_send_to_operation_cancellable
|
||||||
|
: public cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
socket_send_to_operation_cancellable(
|
||||||
|
socket& s,
|
||||||
|
const ip_endpoint& destination,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token&& ct) noexcept
|
||||||
|
: cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>(std::move(ct))
|
||||||
|
, m_impl(s, destination, buffer, byteCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class cppcoro::detail::win32_overlapped_operation_cancellable<socket_send_to_operation_cancellable>;
|
||||||
|
|
||||||
|
bool try_start() noexcept { return m_impl.try_start(*this); }
|
||||||
|
void cancel() noexcept { return m_impl.cancel(*this); }
|
||||||
|
|
||||||
|
socket_send_to_operation_impl m_impl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPPCORO_OS_WINNT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ON_SCOPE_EXIT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename FUNC>
|
||||||
|
class scoped_lambda
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
scoped_lambda(FUNC&& func)
|
||||||
|
: m_func(std::forward<FUNC>(func))
|
||||||
|
, m_cancelled(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
scoped_lambda(const scoped_lambda& other) = delete;
|
||||||
|
|
||||||
|
scoped_lambda(scoped_lambda&& other)
|
||||||
|
: m_func(std::forward<FUNC>(other.m_func))
|
||||||
|
, m_cancelled(other.m_cancelled)
|
||||||
|
{
|
||||||
|
other.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
~scoped_lambda()
|
||||||
|
{
|
||||||
|
if (!m_cancelled)
|
||||||
|
{
|
||||||
|
m_func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel()
|
||||||
|
{
|
||||||
|
m_cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void call_now()
|
||||||
|
{
|
||||||
|
m_cancelled = true;
|
||||||
|
m_func();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FUNC m_func;
|
||||||
|
bool m_cancelled;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A scoped lambda that executes the lambda when the object destructs
|
||||||
|
/// but only if exiting due to an exception (CALL_ON_FAILURE = true) or
|
||||||
|
/// only if not exiting due to an exception (CALL_ON_FAILURE = false).
|
||||||
|
template<typename FUNC, bool CALL_ON_FAILURE>
|
||||||
|
class conditional_scoped_lambda
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
conditional_scoped_lambda(FUNC&& func)
|
||||||
|
: m_func(std::forward<FUNC>(func))
|
||||||
|
, m_uncaughtExceptionCount(std::uncaught_exceptions())
|
||||||
|
, m_cancelled(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
conditional_scoped_lambda(const conditional_scoped_lambda& other) = delete;
|
||||||
|
|
||||||
|
conditional_scoped_lambda(conditional_scoped_lambda&& other)
|
||||||
|
noexcept(std::is_nothrow_move_constructible<FUNC>::value)
|
||||||
|
: m_func(std::forward<FUNC>(other.m_func))
|
||||||
|
, m_uncaughtExceptionCount(other.m_uncaughtExceptionCount)
|
||||||
|
, m_cancelled(other.m_cancelled)
|
||||||
|
{
|
||||||
|
other.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
~conditional_scoped_lambda() noexcept(CALL_ON_FAILURE || noexcept(std::declval<FUNC>()()))
|
||||||
|
{
|
||||||
|
if (!m_cancelled && (is_unwinding_due_to_exception() == CALL_ON_FAILURE))
|
||||||
|
{
|
||||||
|
m_func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() noexcept
|
||||||
|
{
|
||||||
|
m_cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool is_unwinding_due_to_exception() const noexcept
|
||||||
|
{
|
||||||
|
return std::uncaught_exceptions() > m_uncaughtExceptionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNC m_func;
|
||||||
|
int m_uncaughtExceptionCount;
|
||||||
|
bool m_cancelled;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns an object that calls the provided function when it goes out
|
||||||
|
/// of scope either normally or due to an uncaught exception unwinding
|
||||||
|
/// the stack.
|
||||||
|
///
|
||||||
|
/// \param func
|
||||||
|
/// The function to call when the scope exits.
|
||||||
|
/// The function must be noexcept.
|
||||||
|
template<typename FUNC>
|
||||||
|
auto on_scope_exit(FUNC&& func)
|
||||||
|
{
|
||||||
|
return scoped_lambda<FUNC>{ std::forward<FUNC>(func) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an object that calls the provided function when it goes out
|
||||||
|
/// of scope due to an uncaught exception unwinding the stack.
|
||||||
|
///
|
||||||
|
/// \param func
|
||||||
|
/// The function to be called if unwinding due to an exception.
|
||||||
|
/// The function must be noexcept.
|
||||||
|
template<typename FUNC>
|
||||||
|
auto on_scope_failure(FUNC&& func)
|
||||||
|
{
|
||||||
|
return conditional_scoped_lambda<FUNC, true>{ std::forward<FUNC>(func) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an object that calls the provided function when it goes out
|
||||||
|
/// of scope via normal execution (ie. not unwinding due to an exception).
|
||||||
|
///
|
||||||
|
/// \param func
|
||||||
|
/// The function to call if the scope exits normally.
|
||||||
|
/// The function does not necessarily need to be noexcept.
|
||||||
|
template<typename FUNC>
|
||||||
|
auto on_scope_success(FUNC&& func)
|
||||||
|
{
|
||||||
|
return conditional_scoped_lambda<FUNC, false>{ std::forward<FUNC>(func) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
|
||||||
|
#define CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class operation_cancelled : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
operation_cancelled() noexcept
|
||||||
|
: std::exception()
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* what() const noexcept override { return "operation cancelled"; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_READ_ONLY_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/readable_file.hpp>
|
||||||
|
#include <cppcoro/file_share_mode.hpp>
|
||||||
|
#include <cppcoro/file_buffering_mode.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class read_only_file : public readable_file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Open a file for read-only access.
|
||||||
|
///
|
||||||
|
/// \param ioContext
|
||||||
|
/// The I/O context to use when dispatching I/O completion events.
|
||||||
|
/// When asynchronous read operations on this file complete the
|
||||||
|
/// completion events will be dispatched to an I/O thread associated
|
||||||
|
/// with the I/O context.
|
||||||
|
///
|
||||||
|
/// \param path
|
||||||
|
/// Path of the file to open.
|
||||||
|
///
|
||||||
|
/// \param shareMode
|
||||||
|
/// Specifies the access to be allowed on the file concurrently with this file access.
|
||||||
|
///
|
||||||
|
/// \param bufferingMode
|
||||||
|
/// Specifies the modes/hints to provide to the OS that affects the behaviour
|
||||||
|
/// of its file buffering.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An object that can be used to read from the file.
|
||||||
|
///
|
||||||
|
/// \throw std::system_error
|
||||||
|
/// If the file could not be opened for read.
|
||||||
|
[[nodiscard]]
|
||||||
|
static read_only_file open(
|
||||||
|
io_service& ioService,
|
||||||
|
const cppcoro::filesystem::path& path,
|
||||||
|
file_share_mode shareMode = file_share_mode::read,
|
||||||
|
file_buffering_mode bufferingMode = file_buffering_mode::default_);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
read_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_READ_WRITE_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/readable_file.hpp>
|
||||||
|
#include <cppcoro/writable_file.hpp>
|
||||||
|
#include <cppcoro/file_share_mode.hpp>
|
||||||
|
#include <cppcoro/file_buffering_mode.hpp>
|
||||||
|
#include <cppcoro/file_open_mode.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class read_write_file : public readable_file, public writable_file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Open a file for read-write access.
|
||||||
|
///
|
||||||
|
/// \param ioContext
|
||||||
|
/// The I/O context to use when dispatching I/O completion events.
|
||||||
|
/// When asynchronous write operations on this file complete the
|
||||||
|
/// completion events will be dispatched to an I/O thread associated
|
||||||
|
/// with the I/O context.
|
||||||
|
///
|
||||||
|
/// \param pathMode
|
||||||
|
/// Path of the file to open.
|
||||||
|
///
|
||||||
|
/// \param openMode
|
||||||
|
/// Specifies how the file should be opened and how to handle cases
|
||||||
|
/// when the file exists or doesn't exist.
|
||||||
|
///
|
||||||
|
/// \param shareMode
|
||||||
|
/// Specifies the access to be allowed on the file concurrently with this file access.
|
||||||
|
///
|
||||||
|
/// \param bufferingMode
|
||||||
|
/// Specifies the modes/hints to provide to the OS that affects the behaviour
|
||||||
|
/// of its file buffering.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An object that can be used to write to the file.
|
||||||
|
///
|
||||||
|
/// \throw std::system_error
|
||||||
|
/// If the file could not be opened for write.
|
||||||
|
[[nodiscard]]
|
||||||
|
static read_write_file open(
|
||||||
|
io_service& ioService,
|
||||||
|
const cppcoro::filesystem::path& path,
|
||||||
|
file_open_mode openMode = file_open_mode::create_or_open,
|
||||||
|
file_share_mode shareMode = file_share_mode::none,
|
||||||
|
file_buffering_mode bufferingMode = file_buffering_mode::default_);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
read_write_file(detail::win32::safe_handle&& fileHandle) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_READABLE_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_READABLE_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/file.hpp>
|
||||||
|
#include <cppcoro/file_read_operation.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class readable_file : virtual public file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Read some data from the file.
|
||||||
|
///
|
||||||
|
/// Reads \a byteCount bytes from the file starting at \a offset
|
||||||
|
/// into the specified \a buffer.
|
||||||
|
///
|
||||||
|
/// \param offset
|
||||||
|
/// The offset within the file to start reading from.
|
||||||
|
/// If the file has been opened using file_buffering_mode::unbuffered
|
||||||
|
/// then the offset must be a multiple of the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param buffer
|
||||||
|
/// The buffer to read the file contents into.
|
||||||
|
/// If the file has been opened using file_buffering_mode::unbuffered
|
||||||
|
/// then the address of the start of the buffer must be a multiple of
|
||||||
|
/// the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param byteCount
|
||||||
|
/// The number of bytes to read from the file.
|
||||||
|
/// If the file has been opeend using file_buffering_mode::unbuffered
|
||||||
|
/// then the byteCount must be a multiple of the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param ct
|
||||||
|
/// An optional cancellation_token that can be used to cancel the
|
||||||
|
/// read operation before it completes.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An object that represents the read-operation.
|
||||||
|
/// This object must be co_await'ed to start the read operation.
|
||||||
|
[[nodiscard]]
|
||||||
|
file_read_operation read(
|
||||||
|
std::uint64_t offset,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount) const noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
file_read_operation_cancellable read(
|
||||||
|
std::uint64_t offset,
|
||||||
|
void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token ct) const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
using file::file;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
|
||||||
|
#define CPPCORO_RECURSIVE_GENERATOR_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/generator.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <cassert>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class [[nodiscard]] recursive_generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
class promise_type final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
promise_type() noexcept
|
||||||
|
: m_value(nullptr)
|
||||||
|
, m_exception(nullptr)
|
||||||
|
, m_root(this)
|
||||||
|
, m_parentOrLeaf(this)
|
||||||
|
{}
|
||||||
|
|
||||||
|
promise_type(const promise_type&) = delete;
|
||||||
|
promise_type(promise_type&&) = delete;
|
||||||
|
|
||||||
|
auto get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return recursive_generator<T>{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always final_suspend() noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_void() noexcept {}
|
||||||
|
|
||||||
|
cppcoro::suspend_always yield_value(T& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::suspend_always yield_value(T&& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto yield_value(recursive_generator&& generator) noexcept
|
||||||
|
{
|
||||||
|
return yield_value(generator);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto yield_value(recursive_generator& generator) noexcept
|
||||||
|
{
|
||||||
|
struct awaitable
|
||||||
|
{
|
||||||
|
|
||||||
|
awaitable(promise_type* childPromise)
|
||||||
|
: m_childPromise(childPromise)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() noexcept
|
||||||
|
{
|
||||||
|
return this->m_childPromise == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<promise_type>) noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
void await_resume()
|
||||||
|
{
|
||||||
|
if (this->m_childPromise != nullptr)
|
||||||
|
{
|
||||||
|
this->m_childPromise->throw_if_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
promise_type* m_childPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (generator.m_promise != nullptr)
|
||||||
|
{
|
||||||
|
m_root->m_parentOrLeaf = generator.m_promise;
|
||||||
|
generator.m_promise->m_root = m_root;
|
||||||
|
generator.m_promise->m_parentOrLeaf = this;
|
||||||
|
generator.m_promise->resume();
|
||||||
|
|
||||||
|
if (!generator.m_promise->is_complete())
|
||||||
|
{
|
||||||
|
return awaitable{ generator.m_promise };
|
||||||
|
}
|
||||||
|
|
||||||
|
m_root->m_parentOrLeaf = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return awaitable{ nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow any use of 'co_await' inside the recursive_generator coroutine.
|
||||||
|
template<typename U>
|
||||||
|
cppcoro::suspend_never await_transform(U&& value) = delete;
|
||||||
|
|
||||||
|
void destroy() noexcept
|
||||||
|
{
|
||||||
|
cppcoro::coroutine_handle<promise_type>::from_promise(*this).destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_if_exception()
|
||||||
|
{
|
||||||
|
if (m_exception != nullptr)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(std::move(m_exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_complete() noexcept
|
||||||
|
{
|
||||||
|
return cppcoro::coroutine_handle<promise_type>::from_promise(*this).done();
|
||||||
|
}
|
||||||
|
|
||||||
|
T& value() noexcept
|
||||||
|
{
|
||||||
|
assert(this == m_root);
|
||||||
|
assert(!is_complete());
|
||||||
|
return *(m_parentOrLeaf->m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pull() noexcept
|
||||||
|
{
|
||||||
|
assert(this == m_root);
|
||||||
|
assert(!m_parentOrLeaf->is_complete());
|
||||||
|
|
||||||
|
m_parentOrLeaf->resume();
|
||||||
|
|
||||||
|
while (m_parentOrLeaf != this && m_parentOrLeaf->is_complete())
|
||||||
|
{
|
||||||
|
m_parentOrLeaf = m_parentOrLeaf->m_parentOrLeaf;
|
||||||
|
m_parentOrLeaf->resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void resume() noexcept
|
||||||
|
{
|
||||||
|
cppcoro::coroutine_handle<promise_type>::from_promise(*this).resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::add_pointer_t<T> m_value;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
promise_type* m_root;
|
||||||
|
|
||||||
|
// If this is the promise of the root generator then this field
|
||||||
|
// is a pointer to the leaf promise.
|
||||||
|
// For non-root generators this is a pointer to the parent promise.
|
||||||
|
promise_type* m_parentOrLeaf;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
recursive_generator() noexcept
|
||||||
|
: m_promise(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
recursive_generator(promise_type& promise) noexcept
|
||||||
|
: m_promise(&promise)
|
||||||
|
{}
|
||||||
|
|
||||||
|
recursive_generator(recursive_generator&& other) noexcept
|
||||||
|
: m_promise(other.m_promise)
|
||||||
|
{
|
||||||
|
other.m_promise = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
recursive_generator(const recursive_generator& other) = delete;
|
||||||
|
recursive_generator& operator=(const recursive_generator& other) = delete;
|
||||||
|
|
||||||
|
~recursive_generator()
|
||||||
|
{
|
||||||
|
if (m_promise != nullptr)
|
||||||
|
{
|
||||||
|
m_promise->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recursive_generator& operator=(recursive_generator&& other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
if (m_promise != nullptr)
|
||||||
|
{
|
||||||
|
m_promise->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_promise = other.m_promise;
|
||||||
|
other.m_promise = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
class iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
// What type should we use for counting elements of a potentially infinite sequence?
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = std::remove_reference_t<T>;
|
||||||
|
using reference = std::conditional_t<std::is_reference_v<T>, T, T&>;
|
||||||
|
using pointer = std::add_pointer_t<T>;
|
||||||
|
|
||||||
|
iterator() noexcept
|
||||||
|
: m_promise(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit iterator(promise_type* promise) noexcept
|
||||||
|
: m_promise(promise)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator==(const iterator& other) const noexcept
|
||||||
|
{
|
||||||
|
return m_promise == other.m_promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator& other) const noexcept
|
||||||
|
{
|
||||||
|
return m_promise != other.m_promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator& operator++()
|
||||||
|
{
|
||||||
|
assert(m_promise != nullptr);
|
||||||
|
assert(!m_promise->is_complete());
|
||||||
|
|
||||||
|
m_promise->pull();
|
||||||
|
if (m_promise->is_complete())
|
||||||
|
{
|
||||||
|
auto* temp = m_promise;
|
||||||
|
m_promise = nullptr;
|
||||||
|
temp->throw_if_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator++(int)
|
||||||
|
{
|
||||||
|
(void)operator++();
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const noexcept
|
||||||
|
{
|
||||||
|
assert(m_promise != nullptr);
|
||||||
|
return static_cast<reference>(m_promise->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const noexcept
|
||||||
|
{
|
||||||
|
return std::addressof(operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
promise_type* m_promise;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
if (m_promise != nullptr)
|
||||||
|
{
|
||||||
|
m_promise->pull();
|
||||||
|
if (!m_promise->is_complete())
|
||||||
|
{
|
||||||
|
return iterator(m_promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_promise->throw_if_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterator(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() noexcept
|
||||||
|
{
|
||||||
|
return iterator(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(recursive_generator& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(m_promise, other.m_promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class promise_type;
|
||||||
|
|
||||||
|
promise_type* m_promise;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void swap(recursive_generator<T>& a, recursive_generator<T>& b) noexcept
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: When applying fmap operator to a recursive_generator we just yield a non-recursive
|
||||||
|
// generator since we generally won't be using the result in a recursive context.
|
||||||
|
template<typename FUNC, typename T>
|
||||||
|
generator<std::invoke_result_t<FUNC&, typename recursive_generator<T>::iterator::reference>> fmap(FUNC func, recursive_generator<T> source)
|
||||||
|
{
|
||||||
|
for (auto&& value : source)
|
||||||
|
{
|
||||||
|
co_yield std::invoke(func, static_cast<decltype(value)>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_RESUME_ON_HPP_INCLUDED
|
||||||
|
#define CPPCORO_RESUME_ON_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/async_generator.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/detail/get_awaiter.hpp>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
struct resume_on_transform
|
||||||
|
{
|
||||||
|
explicit resume_on_transform(SCHEDULER& s) noexcept
|
||||||
|
: scheduler(s)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SCHEDULER& scheduler;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
resume_on_transform<SCHEDULER> resume_on(SCHEDULER& scheduler) noexcept
|
||||||
|
{
|
||||||
|
return resume_on_transform<SCHEDULER>(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename SCHEDULER>
|
||||||
|
decltype(auto) operator|(T&& value, resume_on_transform<SCHEDULER> transform)
|
||||||
|
{
|
||||||
|
return resume_on(transform.scheduler, std::forward<T>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename SCHEDULER,
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
|
||||||
|
std::enable_if_t<!std::is_void_v<AWAIT_RESULT>, int> = 0>
|
||||||
|
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
|
||||||
|
-> task<AWAIT_RESULT>
|
||||||
|
{
|
||||||
|
bool rescheduled = false;
|
||||||
|
std::exception_ptr ex;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// We manually get the awaiter here so that we can keep
|
||||||
|
// it alive across the call to `scheduler.schedule()`
|
||||||
|
// just in case the result is a reference to a value
|
||||||
|
// in the awaiter that would otherwise be a temporary
|
||||||
|
// and destructed before the value could be returned.
|
||||||
|
|
||||||
|
auto&& awaiter = detail::get_awaiter(static_cast<AWAITABLE&&>(awaitable));
|
||||||
|
|
||||||
|
auto&& result = co_await static_cast<decltype(awaiter)>(awaiter);
|
||||||
|
|
||||||
|
// Flag as rescheduled before scheduling in case it is the
|
||||||
|
// schedule() operation that throws an exception as we don't
|
||||||
|
// want to attempt to schedule twice if scheduling fails.
|
||||||
|
rescheduled = true;
|
||||||
|
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
|
||||||
|
co_return static_cast<decltype(result)>(result);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
ex = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still want to resume on the scheduler even in the presence
|
||||||
|
// of an exception.
|
||||||
|
if (!rescheduled)
|
||||||
|
{
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::rethrow_exception(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename SCHEDULER,
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename AWAIT_RESULT = detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>,
|
||||||
|
std::enable_if_t<std::is_void_v<AWAIT_RESULT>, int> = 0>
|
||||||
|
auto resume_on(SCHEDULER& scheduler, AWAITABLE awaitable)
|
||||||
|
-> task<>
|
||||||
|
{
|
||||||
|
std::exception_ptr ex;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
ex = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We're assuming that `schedule()` operation is noexcept
|
||||||
|
// here. If it were to throw what would we do if 'ex' was non-null?
|
||||||
|
// Presumably we'd treat it the same as throwing an exception while
|
||||||
|
// unwinding and call std::terminate()?
|
||||||
|
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
|
||||||
|
if (ex)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SCHEDULER, typename T>
|
||||||
|
async_generator<T> resume_on(SCHEDULER& scheduler, async_generator<T> source)
|
||||||
|
{
|
||||||
|
for (auto iter = co_await source.begin(); iter != source.end(); co_await ++iter)
|
||||||
|
{
|
||||||
|
auto& value = *iter;
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
co_yield value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
/// This is a scheduler class that schedules coroutines in a round-robin
|
||||||
|
/// fashion once N coroutines have been scheduled to it.
|
||||||
|
///
|
||||||
|
/// Only supports access from a single thread at a time so
|
||||||
|
///
|
||||||
|
/// This implementation was inspired by Gor Nishanov's CppCon 2018 talk
|
||||||
|
/// about nano-coroutines.
|
||||||
|
///
|
||||||
|
/// The implementation relies on symmetric transfer and noop_coroutine()
|
||||||
|
/// and so only works with a relatively recent version of Clang and does
|
||||||
|
/// not yet work with MSVC.
|
||||||
|
template<size_t N>
|
||||||
|
class round_robin_scheduler
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
N >= 2,
|
||||||
|
"Round robin scheduler must be configured to support at least two coroutines");
|
||||||
|
|
||||||
|
class schedule_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit schedule_operation(round_robin_scheduler& s) noexcept : m_scheduler(s) {}
|
||||||
|
|
||||||
|
bool await_ready() noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<> await_suspend(
|
||||||
|
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_scheduler.exchange_next(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
round_robin_scheduler& m_scheduler;
|
||||||
|
};
|
||||||
|
|
||||||
|
friend class schedule_operation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
round_robin_scheduler() noexcept
|
||||||
|
: m_index(0)
|
||||||
|
, m_noop(cppcoro::noop_coroutine())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < N - 1; ++i)
|
||||||
|
{
|
||||||
|
m_coroutines[i] = m_noop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~round_robin_scheduler()
|
||||||
|
{
|
||||||
|
// All tasks should have been joined before calling destructor.
|
||||||
|
assert(std::all_of(
|
||||||
|
m_coroutines.begin(),
|
||||||
|
m_coroutines.end(),
|
||||||
|
[&](auto h) { return h == m_noop; }));
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule_operation schedule() noexcept
|
||||||
|
{
|
||||||
|
return schedule_operation{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume any queued coroutines until there are no more coroutines.
|
||||||
|
void drain() noexcept
|
||||||
|
{
|
||||||
|
size_t countRemaining = N - 1;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto nextToResume = exchange_next(m_noop);
|
||||||
|
if (nextToResume != m_noop)
|
||||||
|
{
|
||||||
|
nextToResume.resume();
|
||||||
|
countRemaining = N - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
--countRemaining;
|
||||||
|
}
|
||||||
|
} while (countRemaining > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle exchange_next(
|
||||||
|
cppcoro::coroutine_handle<> coroutine) noexcept
|
||||||
|
{
|
||||||
|
auto coroutineToResume = std::exchange(
|
||||||
|
m_scheduler.m_coroutines[m_scheduler.m_index],
|
||||||
|
awaitingCoroutine);
|
||||||
|
m_scheduler.m_index = m_scheduler.m_index < (N - 2) ? m_scheduler.m_index + 1 : 0;
|
||||||
|
return coroutineToResume;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t m_index;
|
||||||
|
const cppcoro::coroutine_handle<> m_noop;
|
||||||
|
std::array<cppcoro::coroutine_handle<>, N - 1> m_coroutines;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SCHEDULE_ON_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SCHEDULE_ON_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/shared_task.hpp>
|
||||||
|
#include <cppcoro/async_generator.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/remove_rvalue_reference.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
struct schedule_on_transform
|
||||||
|
{
|
||||||
|
explicit schedule_on_transform(SCHEDULER& scheduler) noexcept
|
||||||
|
: scheduler(scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SCHEDULER& scheduler;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
schedule_on_transform<SCHEDULER> schedule_on(SCHEDULER& scheduler)
|
||||||
|
{
|
||||||
|
return schedule_on_transform<SCHEDULER>{ scheduler };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename SCHEDULER>
|
||||||
|
decltype(auto) operator|(T&& value, schedule_on_transform<SCHEDULER> transform)
|
||||||
|
{
|
||||||
|
return schedule_on(transform.scheduler, std::forward<T>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SCHEDULER, typename AWAITABLE>
|
||||||
|
auto schedule_on(SCHEDULER& scheduler, AWAITABLE awaitable)
|
||||||
|
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
|
||||||
|
{
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
co_return co_await std::move(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename SCHEDULER>
|
||||||
|
async_generator<T> schedule_on(SCHEDULER& scheduler, async_generator<T> source)
|
||||||
|
{
|
||||||
|
// Transfer exection to the scheduler before the implicit calls to
|
||||||
|
// 'co_await begin()' or subsequent calls to `co_await iterator::operator++()`
|
||||||
|
// below. This ensures that all calls to the generator's coroutine_handle<>::resume()
|
||||||
|
// are executed on the execution context of the scheduler.
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
|
||||||
|
const auto itEnd = source.end();
|
||||||
|
auto it = co_await source.begin();
|
||||||
|
while (it != itEnd)
|
||||||
|
{
|
||||||
|
co_yield *it;
|
||||||
|
|
||||||
|
co_await scheduler.schedule();
|
||||||
|
|
||||||
|
(void)co_await ++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,470 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SEQUENCE_BARRIER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/sequence_traits.hpp>
|
||||||
|
#include <cppcoro/detail/manual_lifetime.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
class sequence_barrier_wait_operation_base;
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class sequence_barrier_wait_operation;
|
||||||
|
|
||||||
|
/// A sequence barrier is a synchronisation primitive that allows a single-producer
|
||||||
|
/// and multiple-consumers to coordinate with respect to a monotonically increasing
|
||||||
|
/// sequence number.
|
||||||
|
///
|
||||||
|
/// A single producer advances the sequence number by publishing new sequence numbers in a
|
||||||
|
/// monotonically increasing order. One or more consumers can query the last-published
|
||||||
|
/// sequence number and can wait until a particular sequence number has been published.
|
||||||
|
///
|
||||||
|
/// A sequence barrier can be used to represent a cursor into a thread-safe producer/consumer
|
||||||
|
/// ring-buffer.
|
||||||
|
///
|
||||||
|
/// See the LMAX Disruptor pattern for more background:
|
||||||
|
/// https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf
|
||||||
|
template<
|
||||||
|
typename SEQUENCE = std::size_t,
|
||||||
|
typename TRAITS = sequence_traits<SEQUENCE>>
|
||||||
|
class sequence_barrier
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_integral_v<SEQUENCE>,
|
||||||
|
"sequence_barrier requires an integral sequence type");
|
||||||
|
|
||||||
|
using awaiter_t = sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Construct a sequence barrier with the specified initial sequence number
|
||||||
|
/// as the initial value 'last_published()'.
|
||||||
|
sequence_barrier(SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
|
||||||
|
: m_lastPublished(initialSequence)
|
||||||
|
, m_awaiters(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~sequence_barrier()
|
||||||
|
{
|
||||||
|
// Shouldn't be destructing a sequence barrier if there are still waiters.
|
||||||
|
assert(m_awaiters.load(std::memory_order_relaxed) == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the sequence number that was most recently published by the producer.
|
||||||
|
///
|
||||||
|
/// You can assume that all sequence numbers prior to the returned sequence number
|
||||||
|
/// have also been published. This means you can safely access all elements with
|
||||||
|
/// sequence numbers up to and including the returned sequence number without any
|
||||||
|
/// further synchronisation.
|
||||||
|
SEQUENCE last_published() const noexcept
|
||||||
|
{
|
||||||
|
return m_lastPublished.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until a particular sequence number has been published.
|
||||||
|
///
|
||||||
|
/// If the specified sequence number is not yet published then the awaiting coroutine
|
||||||
|
/// will be suspended and later resumed inside the call to publish() that publishes
|
||||||
|
/// the specified sequence number.
|
||||||
|
///
|
||||||
|
/// \param targetSequence
|
||||||
|
/// The sequence number to wait for.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An awaitable that when co_await'ed will suspend the awaiting coroutine until
|
||||||
|
/// the specified target sequence number has been published.
|
||||||
|
/// The result of the co_await expression will be the last-known published sequence
|
||||||
|
/// number. This is guaranteed not to precede \p targetSequence but may be a sequence
|
||||||
|
/// number after \p targetSequence, which indicates that more elements have been
|
||||||
|
/// published than you were waiting for.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> wait_until_published(
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SCHEDULER& scheduler) const noexcept;
|
||||||
|
|
||||||
|
/// Publish the specified sequence number to consumers.
|
||||||
|
///
|
||||||
|
/// This publishes all sequence numbers up to and including the specified sequence
|
||||||
|
/// number. This will resume any coroutine that was suspended waiting for a sequence
|
||||||
|
/// number that was published by this operation.
|
||||||
|
///
|
||||||
|
/// \param sequence
|
||||||
|
/// The sequence number to publish. This number must not precede the current
|
||||||
|
/// last_published() value. ie. the published sequence numbers must be monotonically
|
||||||
|
/// increasing.
|
||||||
|
void publish(SEQUENCE sequence) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
void add_awaiter(awaiter_t* awaiter) const noexcept;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// First cache-line is written to by the producer only
|
||||||
|
alignas(CPPCORO_CPU_CACHE_LINE)
|
||||||
|
std::atomic<SEQUENCE> m_lastPublished;
|
||||||
|
|
||||||
|
// Second cache-line is written to by both the producer and consumers
|
||||||
|
alignas(CPPCORO_CPU_CACHE_LINE)
|
||||||
|
mutable std::atomic<awaiter_t*> m_awaiters;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
class sequence_barrier_wait_operation_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit sequence_barrier_wait_operation_base(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
|
||||||
|
SEQUENCE targetSequence) noexcept
|
||||||
|
: m_barrier(barrier)
|
||||||
|
, m_targetSequence(targetSequence)
|
||||||
|
, m_lastKnownPublished(barrier.last_published())
|
||||||
|
, m_readyToResume(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation_base(
|
||||||
|
const sequence_barrier_wait_operation_base& other) noexcept
|
||||||
|
: m_barrier(other.m_barrier)
|
||||||
|
, m_targetSequence(other.m_targetSequence)
|
||||||
|
, m_lastKnownPublished(other.m_lastKnownPublished)
|
||||||
|
, m_readyToResume(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !TRAITS::precedes(m_lastKnownPublished, m_targetSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
m_barrier.add_awaiter(this);
|
||||||
|
return !m_readyToResume.exchange(true, std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE await_resume() noexcept
|
||||||
|
{
|
||||||
|
return m_lastKnownPublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
friend class sequence_barrier<SEQUENCE, TRAITS>;
|
||||||
|
|
||||||
|
void resume() noexcept
|
||||||
|
{
|
||||||
|
// This synchronises with the exchange(true, std::memory_order_acquire) in await_suspend().
|
||||||
|
if (m_readyToResume.exchange(true, std::memory_order_release))
|
||||||
|
{
|
||||||
|
resume_impl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void resume_impl() noexcept = 0;
|
||||||
|
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& m_barrier;
|
||||||
|
const SEQUENCE m_targetSequence;
|
||||||
|
SEQUENCE m_lastKnownPublished;
|
||||||
|
sequence_barrier_wait_operation_base* m_next;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
std::atomic<bool> m_readyToResume;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class sequence_barrier_wait_operation : public sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>
|
||||||
|
{
|
||||||
|
using schedule_operation = decltype(std::declval<SCHEDULER&>().schedule());
|
||||||
|
|
||||||
|
public:
|
||||||
|
sequence_barrier_wait_operation(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& barrier,
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(barrier, targetSequence)
|
||||||
|
, m_scheduler(scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation(
|
||||||
|
const sequence_barrier_wait_operation& other) noexcept
|
||||||
|
: sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>(other)
|
||||||
|
, m_scheduler(other.m_scheduler)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~sequence_barrier_wait_operation()
|
||||||
|
{
|
||||||
|
if (m_isScheduleAwaiterCreated)
|
||||||
|
{
|
||||||
|
m_scheduleAwaiter.destruct();
|
||||||
|
}
|
||||||
|
if (m_isScheduleOperationCreated)
|
||||||
|
{
|
||||||
|
m_scheduleOperation.destruct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) await_resume() noexcept(noexcept(m_scheduleAwaiter->await_resume()))
|
||||||
|
{
|
||||||
|
if (m_isScheduleAwaiterCreated)
|
||||||
|
{
|
||||||
|
m_scheduleAwaiter->await_resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence_barrier_wait_operation_base<SEQUENCE, TRAITS>::await_resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void resume_impl() noexcept override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_scheduleOperation.construct(m_scheduler.schedule());
|
||||||
|
m_isScheduleOperationCreated = true;
|
||||||
|
|
||||||
|
m_scheduleAwaiter.construct(detail::get_awaiter(
|
||||||
|
static_cast<schedule_operation&&>(*m_scheduleOperation)));
|
||||||
|
m_isScheduleAwaiterCreated = true;
|
||||||
|
|
||||||
|
if (!m_scheduleAwaiter->await_ready())
|
||||||
|
{
|
||||||
|
using await_suspend_result_t = decltype(m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine));
|
||||||
|
if constexpr (std::is_void_v<await_suspend_result_t>)
|
||||||
|
{
|
||||||
|
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<await_suspend_result_t, bool>)
|
||||||
|
{
|
||||||
|
if (m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Assume it returns a coroutine_handle.
|
||||||
|
m_scheduleAwaiter->await_suspend(this->m_awaitingCoroutine).resume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Ignore failure to reschedule and resume inline?
|
||||||
|
// Should we catch the exception and rethrow from await_resume()?
|
||||||
|
// Or should we require that 'co_await scheduler.schedule()' is noexcept?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume outside the catch-block.
|
||||||
|
this->m_awaitingCoroutine.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
SCHEDULER& m_scheduler;
|
||||||
|
// Can't use std::optional<T> here since T could be a reference.
|
||||||
|
detail::manual_lifetime<schedule_operation> m_scheduleOperation;
|
||||||
|
detail::manual_lifetime<typename awaitable_traits<schedule_operation>::awaiter_t> m_scheduleAwaiter;
|
||||||
|
bool m_isScheduleOperationCreated = false;
|
||||||
|
bool m_isScheduleAwaiterCreated = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> sequence_barrier<SEQUENCE, TRAITS>::wait_until_published(
|
||||||
|
SEQUENCE targetSequence,
|
||||||
|
SCHEDULER& scheduler) const noexcept
|
||||||
|
{
|
||||||
|
return sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, targetSequence, scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void sequence_barrier<SEQUENCE, TRAITS>::publish(SEQUENCE sequence) noexcept
|
||||||
|
{
|
||||||
|
m_lastPublished.store(sequence, std::memory_order_seq_cst);
|
||||||
|
|
||||||
|
// Cheaper check to see if there are any awaiting coroutines.
|
||||||
|
auto* awaiters = m_awaiters.load(std::memory_order_seq_cst);
|
||||||
|
if (awaiters == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire the list of awaiters.
|
||||||
|
// Note we may be racing with add_awaiter() which could also acquire the list of waiters
|
||||||
|
// so we need to check again whether we won the race and acquired the list.
|
||||||
|
awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
if (awaiters == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the list of awaiters for ones that are now satisfied by the sequence number
|
||||||
|
// we just published. Awaiters are added to either the 'awaitersToResume' list or to
|
||||||
|
// the 'awaitersToRequeue' list.
|
||||||
|
awaiter_t* awaitersToResume;
|
||||||
|
awaiter_t** awaitersToResumeTail = &awaitersToResume;
|
||||||
|
|
||||||
|
awaiter_t* awaitersToRequeue;
|
||||||
|
awaiter_t** awaitersToRequeueTail = &awaitersToRequeue;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (TRAITS::precedes(sequence, awaiters->m_targetSequence))
|
||||||
|
{
|
||||||
|
// Target sequence not reached. Append to 'requeue' list.
|
||||||
|
*awaitersToRequeueTail = awaiters;
|
||||||
|
awaitersToRequeueTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Target sequence reached. Append to 'resume' list.
|
||||||
|
*awaitersToResumeTail = awaiters;
|
||||||
|
awaitersToResumeTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
awaiters = awaiters->m_next;
|
||||||
|
} while (awaiters != nullptr);
|
||||||
|
|
||||||
|
// Null-terminate the two lists.
|
||||||
|
*awaitersToRequeueTail = nullptr;
|
||||||
|
*awaitersToResumeTail = nullptr;
|
||||||
|
|
||||||
|
if (awaitersToRequeue != nullptr)
|
||||||
|
{
|
||||||
|
awaiter_t* oldHead = nullptr;
|
||||||
|
while (!m_awaiters.compare_exchange_weak(
|
||||||
|
oldHead,
|
||||||
|
awaitersToRequeue,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
*awaitersToRequeueTail = oldHead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (awaitersToResume != nullptr)
|
||||||
|
{
|
||||||
|
auto* next = awaitersToResume->m_next;
|
||||||
|
awaitersToResume->m_lastKnownPublished = sequence;
|
||||||
|
awaitersToResume->resume();
|
||||||
|
awaitersToResume = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
void sequence_barrier<SEQUENCE, TRAITS>::add_awaiter(awaiter_t* awaiter) const noexcept
|
||||||
|
{
|
||||||
|
SEQUENCE targetSequence = awaiter->m_targetSequence;
|
||||||
|
awaiter_t* awaitersToRequeue = awaiter;
|
||||||
|
awaiter_t** awaitersToRequeueTail = &awaiter->m_next;
|
||||||
|
|
||||||
|
SEQUENCE lastKnownPublished;
|
||||||
|
awaiter_t* awaitersToResume;
|
||||||
|
awaiter_t** awaitersToResumeTail = &awaitersToResume;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Enqueue the awaiter(s)
|
||||||
|
{
|
||||||
|
auto* oldHead = m_awaiters.load(std::memory_order_relaxed);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
*awaitersToRequeueTail = oldHead;
|
||||||
|
} while (!m_awaiters.compare_exchange_weak(
|
||||||
|
oldHead,
|
||||||
|
awaitersToRequeue,
|
||||||
|
std::memory_order_seq_cst,
|
||||||
|
std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the sequence we were waiting for wasn't published while
|
||||||
|
// we were enqueueing the waiter.
|
||||||
|
// This needs to be seq_cst memory order to ensure that in the case that the producer
|
||||||
|
// publishes a new sequence number concurrently with this call that we either see
|
||||||
|
// their write to m_lastPublished after enqueueing our awaiter, or they see our
|
||||||
|
// write to m_awaiters after their write to m_lastPublished.
|
||||||
|
lastKnownPublished = m_lastPublished.load(std::memory_order_seq_cst);
|
||||||
|
if (TRAITS::precedes(lastKnownPublished, targetSequence))
|
||||||
|
{
|
||||||
|
// None of the the awaiters we enqueued have been satisfied yet.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the requeue list to empty
|
||||||
|
awaitersToRequeueTail = &awaitersToRequeue;
|
||||||
|
|
||||||
|
// At least one of the awaiters we just enqueued is now satisfied by a concurrently
|
||||||
|
// published sequence number. The producer thread may not have seen our write to m_awaiters
|
||||||
|
// so we need to try to re-acquire the list of awaiters to ensure that the waiters that
|
||||||
|
// are now satisfied are woken up.
|
||||||
|
auto* awaiters = m_awaiters.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
|
||||||
|
auto minDiff = std::numeric_limits<typename TRAITS::difference_type>::max();
|
||||||
|
|
||||||
|
while (awaiters != nullptr)
|
||||||
|
{
|
||||||
|
const auto diff = TRAITS::difference(awaiters->m_targetSequence, lastKnownPublished);
|
||||||
|
if (diff > 0)
|
||||||
|
{
|
||||||
|
*awaitersToRequeueTail = awaiters;
|
||||||
|
awaitersToRequeueTail = &awaiters->m_next;
|
||||||
|
minDiff = diff < minDiff ? diff : minDiff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*awaitersToResumeTail = awaiters;
|
||||||
|
awaitersToResumeTail = &awaiters->m_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
awaiters = awaiters->m_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null-terminate the list of awaiters to requeue.
|
||||||
|
*awaitersToRequeueTail = nullptr;
|
||||||
|
|
||||||
|
// Calculate the earliest target sequence required by any of the awaiters to requeue.
|
||||||
|
targetSequence = static_cast<SEQUENCE>(lastKnownPublished + minDiff);
|
||||||
|
|
||||||
|
} while (awaitersToRequeue != nullptr);
|
||||||
|
|
||||||
|
// Null-terminate the list of awaiters to resume
|
||||||
|
*awaitersToResumeTail = nullptr;
|
||||||
|
|
||||||
|
// Resume the awaiters that are ready
|
||||||
|
while (awaitersToResume != nullptr)
|
||||||
|
{
|
||||||
|
auto* next = awaitersToResume->m_next;
|
||||||
|
awaitersToResume->m_lastKnownPublished = lastKnownPublished;
|
||||||
|
awaitersToResume->resume();
|
||||||
|
awaitersToResume = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/sequence_traits.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SEQUENCE, typename TRAITS = sequence_traits<SEQUENCE>>
|
||||||
|
class sequence_range
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using value_type = SEQUENCE;
|
||||||
|
using difference_type = typename TRAITS::difference_type;
|
||||||
|
using size_type = typename TRAITS::size_type;
|
||||||
|
|
||||||
|
class const_iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
|
using value_type = SEQUENCE;
|
||||||
|
using difference_type = typename TRAITS::difference_type;
|
||||||
|
using reference = const SEQUENCE&;
|
||||||
|
using pointer = const SEQUENCE*;
|
||||||
|
|
||||||
|
explicit constexpr const_iterator(SEQUENCE value) noexcept : m_value(value) {}
|
||||||
|
|
||||||
|
const SEQUENCE& operator*() const noexcept { return m_value; }
|
||||||
|
const SEQUENCE* operator->() const noexcept { return std::addressof(m_value); }
|
||||||
|
|
||||||
|
const_iterator& operator++() noexcept { ++m_value; return *this; }
|
||||||
|
const_iterator& operator--() noexcept { --m_value; return *this; }
|
||||||
|
|
||||||
|
const_iterator operator++(int) noexcept { return const_iterator(m_value++); }
|
||||||
|
const_iterator operator--(int) noexcept { return const_iterator(m_value--); }
|
||||||
|
|
||||||
|
constexpr difference_type operator-(const_iterator other) const noexcept { return TRAITS::difference(m_value, other.m_value); }
|
||||||
|
constexpr const_iterator operator-(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value - delta) }; }
|
||||||
|
constexpr const_iterator operator+(difference_type delta) const noexcept { return const_iterator{ static_cast<SEQUENCE>(m_value + delta) }; }
|
||||||
|
|
||||||
|
constexpr bool operator==(const_iterator other) const noexcept { return m_value == other.m_value; }
|
||||||
|
constexpr bool operator!=(const_iterator other) const noexcept { return m_value != other.m_value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SEQUENCE m_value;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr sequence_range() noexcept
|
||||||
|
: m_begin()
|
||||||
|
, m_end()
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr sequence_range(SEQUENCE begin, SEQUENCE end) noexcept
|
||||||
|
: m_begin(begin)
|
||||||
|
, m_end(end)
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr const_iterator begin() const noexcept { return const_iterator(m_begin); }
|
||||||
|
constexpr const_iterator end() const noexcept { return const_iterator(m_end); }
|
||||||
|
|
||||||
|
constexpr SEQUENCE front() const noexcept { return m_begin; }
|
||||||
|
constexpr SEQUENCE back() const noexcept { return m_end - 1; }
|
||||||
|
|
||||||
|
constexpr size_type size() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<size_type>(TRAITS::difference(m_end, m_begin));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return m_begin == m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SEQUENCE operator[](size_type index) const noexcept
|
||||||
|
{
|
||||||
|
return m_begin + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sequence_range first(size_type count) const noexcept
|
||||||
|
{
|
||||||
|
return sequence_range{ m_begin, static_cast<SEQUENCE>(m_begin + std::min(size(), count)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sequence_range skip(size_type count) const noexcept
|
||||||
|
{
|
||||||
|
return sequence_range{ m_begin + std::min(size(), count), m_end };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SEQUENCE m_begin;
|
||||||
|
SEQUENCE m_end;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SEQUENCE>
|
||||||
|
struct sequence_traits
|
||||||
|
{
|
||||||
|
using value_type = SEQUENCE;
|
||||||
|
using difference_type = std::make_signed_t<SEQUENCE>;
|
||||||
|
using size_type = std::make_unsigned_t<SEQUENCE>;
|
||||||
|
|
||||||
|
static constexpr value_type initial_sequence = static_cast<value_type>(-1);
|
||||||
|
|
||||||
|
static constexpr difference_type difference(value_type a, value_type b)
|
||||||
|
{
|
||||||
|
return static_cast<difference_type>(a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool precedes(value_type a, value_type b)
|
||||||
|
{
|
||||||
|
return difference(a, b) < 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,511 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SHARED_LAZY_TASK_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/broken_promise.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/remove_rvalue_reference.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <exception>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class shared_task;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
struct shared_task_waiter
|
||||||
|
{
|
||||||
|
cppcoro::coroutine_handle<> m_continuation;
|
||||||
|
shared_task_waiter* m_next;
|
||||||
|
};
|
||||||
|
|
||||||
|
class shared_task_promise_base
|
||||||
|
{
|
||||||
|
friend struct final_awaiter;
|
||||||
|
|
||||||
|
struct final_awaiter
|
||||||
|
{
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
template<typename PROMISE>
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<PROMISE> h) noexcept
|
||||||
|
{
|
||||||
|
shared_task_promise_base& promise = h.promise();
|
||||||
|
|
||||||
|
// Exchange operation needs to be 'release' so that subsequent awaiters have
|
||||||
|
// visibility of the result. Also needs to be 'acquire' so we have visibility
|
||||||
|
// of writes to the waiters list.
|
||||||
|
void* const valueReadyValue = &promise;
|
||||||
|
void* waiters = promise.m_waiters.exchange(valueReadyValue, std::memory_order_acq_rel);
|
||||||
|
if (waiters != nullptr)
|
||||||
|
{
|
||||||
|
shared_task_waiter* waiter = static_cast<shared_task_waiter*>(waiters);
|
||||||
|
while (waiter->m_next != nullptr)
|
||||||
|
{
|
||||||
|
// Read the m_next pointer before resuming the coroutine
|
||||||
|
// since resuming the coroutine may destroy the shared_task_waiter value.
|
||||||
|
auto* next = waiter->m_next;
|
||||||
|
waiter->m_continuation.resume();
|
||||||
|
waiter = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume last waiter in tail position to allow it to potentially
|
||||||
|
// be compiled as a tail-call.
|
||||||
|
waiter->m_continuation.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
shared_task_promise_base() noexcept
|
||||||
|
: m_refCount(1)
|
||||||
|
, m_waiters(&this->m_waiters)
|
||||||
|
, m_exception(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
cppcoro::suspend_always initial_suspend() noexcept { return {}; }
|
||||||
|
final_awaiter final_suspend() noexcept { return {}; }
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
const void* const valueReadyValue = this;
|
||||||
|
return m_waiters.load(std::memory_order_acquire) == valueReadyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_ref() noexcept
|
||||||
|
{
|
||||||
|
m_refCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrement the reference count.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// true if successfully detached, false if this was the last
|
||||||
|
/// reference to the coroutine, in which case the caller must
|
||||||
|
/// call destroy() on the coroutine handle.
|
||||||
|
bool try_detach() noexcept
|
||||||
|
{
|
||||||
|
return m_refCount.fetch_sub(1, std::memory_order_acq_rel) != 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to enqueue a waiter to the list of waiters.
|
||||||
|
///
|
||||||
|
/// \param waiter
|
||||||
|
/// Pointer to the state from the waiter object.
|
||||||
|
/// Must have waiter->m_coroutine member populated with the coroutine
|
||||||
|
/// handle of the awaiting coroutine.
|
||||||
|
///
|
||||||
|
/// \param coroutine
|
||||||
|
/// Coroutine handle for this promise object.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// true if the waiter was successfully queued, in which case
|
||||||
|
/// waiter->m_coroutine will be resumed when the task completes.
|
||||||
|
/// false if the coroutine was already completed and the awaiting
|
||||||
|
/// coroutine can continue without suspending.
|
||||||
|
bool try_await(shared_task_waiter* waiter, cppcoro::coroutine_handle<> coroutine)
|
||||||
|
{
|
||||||
|
void* const valueReadyValue = this;
|
||||||
|
void* const notStartedValue = &this->m_waiters;
|
||||||
|
constexpr void* startedNoWaitersValue = static_cast<shared_task_waiter*>(nullptr);
|
||||||
|
|
||||||
|
// NOTE: If the coroutine is not yet started then the first waiter
|
||||||
|
// will start the coroutine before enqueuing itself up to the list
|
||||||
|
// of suspended waiters waiting for completion. We split this into
|
||||||
|
// two steps to allow the first awaiter to return without suspending.
|
||||||
|
// This avoids recursively resuming the first waiter inside the call to
|
||||||
|
// coroutine.resume() in the case that the coroutine completes
|
||||||
|
// synchronously, which could otherwise lead to stack-overflow if
|
||||||
|
// the awaiting coroutine awaited many synchronously-completing
|
||||||
|
// tasks in a row.
|
||||||
|
|
||||||
|
// Start the coroutine if not already started.
|
||||||
|
void* oldWaiters = m_waiters.load(std::memory_order_acquire);
|
||||||
|
if (oldWaiters == notStartedValue &&
|
||||||
|
m_waiters.compare_exchange_strong(
|
||||||
|
oldWaiters,
|
||||||
|
startedNoWaitersValue,
|
||||||
|
std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
// Start the task executing.
|
||||||
|
coroutine.resume();
|
||||||
|
oldWaiters = m_waiters.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue the waiter into the list of waiting coroutines.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (oldWaiters == valueReadyValue)
|
||||||
|
{
|
||||||
|
// Coroutine already completed, don't suspend.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
waiter->m_next = static_cast<shared_task_waiter*>(oldWaiters);
|
||||||
|
} while (!m_waiters.compare_exchange_weak(
|
||||||
|
oldWaiters,
|
||||||
|
static_cast<void*>(waiter),
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_acquire));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
bool completed_with_unhandled_exception()
|
||||||
|
{
|
||||||
|
return m_exception != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rethrow_if_unhandled_exception()
|
||||||
|
{
|
||||||
|
if (m_exception != nullptr)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::atomic<std::uint32_t> m_refCount;
|
||||||
|
|
||||||
|
// Value is either
|
||||||
|
// - nullptr - indicates started, no waiters
|
||||||
|
// - this - indicates value is ready
|
||||||
|
// - &this->m_waiters - indicates coroutine not started
|
||||||
|
// - other - pointer to head item in linked-list of waiters.
|
||||||
|
// values are of type 'cppcoro::shared_task_waiter'.
|
||||||
|
// indicates that the coroutine has been started.
|
||||||
|
std::atomic<void*> m_waiters;
|
||||||
|
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class shared_task_promise : public shared_task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
shared_task_promise() noexcept = default;
|
||||||
|
|
||||||
|
~shared_task_promise()
|
||||||
|
{
|
||||||
|
if (this->is_ready() && !this->completed_with_unhandled_exception())
|
||||||
|
{
|
||||||
|
reinterpret_cast<T*>(&m_valueStorage)->~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_task<T> get_return_object() noexcept;
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename VALUE,
|
||||||
|
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
|
||||||
|
void return_value(VALUE&& value)
|
||||||
|
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
|
||||||
|
{
|
||||||
|
new (&m_valueStorage) T(std::forward<VALUE>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
T& result()
|
||||||
|
{
|
||||||
|
this->rethrow_if_unhandled_exception();
|
||||||
|
return *reinterpret_cast<T*>(&m_valueStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Not using std::aligned_storage here due to bug in MSVC 2015 Update 2
|
||||||
|
// that means it doesn't work for types with alignof(T) > 8.
|
||||||
|
// See MS-Connect bug #2658635.
|
||||||
|
alignas(T) char m_valueStorage[sizeof(T)];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class shared_task_promise<void> : public shared_task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
shared_task_promise() noexcept = default;
|
||||||
|
|
||||||
|
shared_task<void> get_return_object() noexcept;
|
||||||
|
|
||||||
|
void return_void() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
void result()
|
||||||
|
{
|
||||||
|
this->rethrow_if_unhandled_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class shared_task_promise<T&> : public shared_task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
shared_task_promise() noexcept = default;
|
||||||
|
|
||||||
|
shared_task<T&> get_return_object() noexcept;
|
||||||
|
|
||||||
|
void return_value(T& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
T& result()
|
||||||
|
{
|
||||||
|
this->rethrow_if_unhandled_exception();
|
||||||
|
return *m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
T* m_value;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void>
|
||||||
|
class [[nodiscard]] shared_task
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using promise_type = detail::shared_task_promise<T>;
|
||||||
|
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct awaitable_base
|
||||||
|
{
|
||||||
|
cppcoro::coroutine_handle<promise_type> m_coroutine;
|
||||||
|
detail::shared_task_waiter m_waiter;
|
||||||
|
|
||||||
|
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !m_coroutine || m_coroutine.promise().is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept
|
||||||
|
{
|
||||||
|
m_waiter.m_continuation = awaiter;
|
||||||
|
return m_coroutine.promise().try_await(&m_waiter, m_coroutine);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
shared_task() noexcept
|
||||||
|
: m_coroutine(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit shared_task(cppcoro::coroutine_handle<promise_type> coroutine)
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{
|
||||||
|
// Don't increment the ref-count here since it has already been
|
||||||
|
// initialised to 2 (one for shared_task and one for coroutine)
|
||||||
|
// in the shared_task_promise constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_task(shared_task&& other) noexcept
|
||||||
|
: m_coroutine(other.m_coroutine)
|
||||||
|
{
|
||||||
|
other.m_coroutine = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_task(const shared_task& other) noexcept
|
||||||
|
: m_coroutine(other.m_coroutine)
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.promise().add_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~shared_task()
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_task& operator=(shared_task&& other) noexcept
|
||||||
|
{
|
||||||
|
if (&other != this)
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
m_coroutine = other.m_coroutine;
|
||||||
|
other.m_coroutine = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_task& operator=(const shared_task& other) noexcept
|
||||||
|
{
|
||||||
|
if (m_coroutine != other.m_coroutine)
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
m_coroutine = other.m_coroutine;
|
||||||
|
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.promise().add_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(shared_task& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(m_coroutine, other.m_coroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Query if the task result is complete.
|
||||||
|
///
|
||||||
|
/// Awaiting a task that is ready will not block.
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !m_coroutine || m_coroutine.promise().is_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() const noexcept
|
||||||
|
{
|
||||||
|
struct awaitable : awaitable_base
|
||||||
|
{
|
||||||
|
using awaitable_base::awaitable_base;
|
||||||
|
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
{
|
||||||
|
if (!this->m_coroutine)
|
||||||
|
{
|
||||||
|
throw broken_promise{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->m_coroutine.promise().result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaitable{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Returns an awaitable that will await completion of the task without
|
||||||
|
/// attempting to retrieve the result.
|
||||||
|
auto when_ready() const noexcept
|
||||||
|
{
|
||||||
|
struct awaitable : awaitable_base
|
||||||
|
{
|
||||||
|
using awaitable_base::awaitable_base;
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaitable{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
friend bool operator==(const shared_task<U>&, const shared_task<U>&) noexcept;
|
||||||
|
|
||||||
|
void destroy() noexcept
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
if (!m_coroutine.promise().try_detach())
|
||||||
|
{
|
||||||
|
m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<promise_type> m_coroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator==(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
|
||||||
|
{
|
||||||
|
return lhs.m_coroutine == rhs.m_coroutine;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator!=(const shared_task<T>& lhs, const shared_task<T>& rhs) noexcept
|
||||||
|
{
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void swap(shared_task<T>& a, shared_task<T>& b) noexcept
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
shared_task<T> shared_task_promise<T>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return shared_task<T>{
|
||||||
|
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
shared_task<T&> shared_task_promise<T&>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return shared_task<T&>{
|
||||||
|
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline shared_task<void> shared_task_promise<void>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return shared_task<void>{
|
||||||
|
cppcoro::coroutine_handle<shared_task_promise>::from_promise(*this)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
auto make_shared_task(AWAITABLE awaitable)
|
||||||
|
-> shared_task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
|
||||||
|
{
|
||||||
|
co_return co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class single_consumer_async_auto_reset_event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
single_consumer_async_auto_reset_event(bool initiallySet = false) noexcept
|
||||||
|
: m_state(initiallySet ? this : nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void set() noexcept
|
||||||
|
{
|
||||||
|
void* oldValue = m_state.exchange(this, std::memory_order_release);
|
||||||
|
if (oldValue != nullptr && oldValue != this)
|
||||||
|
{
|
||||||
|
// There was a waiting coroutine that we now need to resume.
|
||||||
|
auto handle = *static_cast<cppcoro::coroutine_handle<>*>(oldValue);
|
||||||
|
|
||||||
|
// We also need to transition the state back to 'not set' before
|
||||||
|
// resuming the coroutine. This operation needs to be 'acquire'
|
||||||
|
// so that it synchronises with other calls to .set() that execute
|
||||||
|
// concurrently with this call and execute the above m_state.exchange(this)
|
||||||
|
// operation with 'release' semantics.
|
||||||
|
// This needs to be an exchange() instead of a store() so that it can have
|
||||||
|
// 'acquire' semantics.
|
||||||
|
(void)m_state.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
|
||||||
|
// Finally, resume the waiting coroutine.
|
||||||
|
handle.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() const noexcept
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
awaiter(const single_consumer_async_auto_reset_event& event) noexcept
|
||||||
|
: m_event(event)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
m_awaitingCoroutine = awaitingCoroutine;
|
||||||
|
|
||||||
|
void* oldValue = nullptr;
|
||||||
|
if (!m_event.m_state.compare_exchange_strong(
|
||||||
|
oldValue,
|
||||||
|
&m_awaitingCoroutine,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
// This will only fail if the event was already 'set'
|
||||||
|
// In which case we can just reset back to 'not set'
|
||||||
|
// Need to use exchange() rather than store() here so we can make this
|
||||||
|
// operation an 'acquire' operation so that we get visibility of all
|
||||||
|
// writes prior to all preceding calls to .set().
|
||||||
|
assert(oldValue == &m_event);
|
||||||
|
(void)m_event.m_state.exchange(nullptr, std::memory_order_acquire);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const single_consumer_async_auto_reset_event& m_event;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// nullptr - not set, no waiter
|
||||||
|
// this - set
|
||||||
|
// other - not set, pointer is address of a coroutine_handle<> to resume.
|
||||||
|
mutable std::atomic<void*> m_state;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SINGLE_CONSUMER_EVENT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
/// \brief
|
||||||
|
/// A manual-reset event that supports only a single awaiting
|
||||||
|
/// coroutine at a time.
|
||||||
|
///
|
||||||
|
/// You can co_await the event to suspend the current coroutine until
|
||||||
|
/// some thread calls set(). If the event is already set then the
|
||||||
|
/// coroutine will not be suspended and will continue execution.
|
||||||
|
/// If the event was not yet set then the coroutine will be resumed
|
||||||
|
/// on the thread that calls set() within the call to set().
|
||||||
|
///
|
||||||
|
/// Callers must ensure that only one coroutine is executing a
|
||||||
|
/// co_await statement at any point in time.
|
||||||
|
class single_consumer_event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Construct a new event, initialising to either 'set' or 'not set' state.
|
||||||
|
///
|
||||||
|
/// \param initiallySet
|
||||||
|
/// If true then initialises the event to the 'set' state.
|
||||||
|
/// Otherwise, initialised the event to the 'not set' state.
|
||||||
|
single_consumer_event(bool initiallySet = false) noexcept
|
||||||
|
: m_state(initiallySet ? state::set : state::not_set)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// Query if this event has been set.
|
||||||
|
bool is_set() const noexcept
|
||||||
|
{
|
||||||
|
return m_state.load(std::memory_order_acquire) == state::set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Transition this event to the 'set' state if it is not already set.
|
||||||
|
///
|
||||||
|
/// If there was a coroutine awaiting the event then it will be resumed
|
||||||
|
/// inside this call.
|
||||||
|
void set()
|
||||||
|
{
|
||||||
|
const state oldState = m_state.exchange(state::set, std::memory_order_acq_rel);
|
||||||
|
if (oldState == state::not_set_consumer_waiting)
|
||||||
|
{
|
||||||
|
m_awaiter.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Transition this event to the 'non set' state if it was in the set state.
|
||||||
|
void reset() noexcept
|
||||||
|
{
|
||||||
|
state oldState = state::set;
|
||||||
|
m_state.compare_exchange_strong(oldState, state::not_set, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Wait until the event becomes set.
|
||||||
|
///
|
||||||
|
/// If the event is already set then the awaiting coroutine will not be suspended
|
||||||
|
/// and will continue execution. If the event was not yet set then the coroutine
|
||||||
|
/// will be suspended and will be later resumed inside a subsequent call to set()
|
||||||
|
/// on the thread that calls set().
|
||||||
|
auto operator co_await() noexcept
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
awaiter(single_consumer_event& event) : m_event(event) {}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_event.is_set();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaiter)
|
||||||
|
{
|
||||||
|
m_event.m_awaiter = awaiter;
|
||||||
|
|
||||||
|
state oldState = state::not_set;
|
||||||
|
return m_event.m_state.compare_exchange_strong(
|
||||||
|
oldState,
|
||||||
|
state::not_set_consumer_waiting,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
single_consumer_event& m_event;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaiter{ *this };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum class state
|
||||||
|
{
|
||||||
|
not_set,
|
||||||
|
not_set_consumer_waiting,
|
||||||
|
set
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Merge these two fields into a single std::atomic<std::uintptr_t>
|
||||||
|
// by encoding 'not_set' as 0 (nullptr), 'set' as 1 and
|
||||||
|
// 'not_set_consumer_waiting' as a coroutine handle pointer.
|
||||||
|
std::atomic<state> m_state;
|
||||||
|
cppcoro::coroutine_handle<> m_awaiter;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SINGLE_PRODUCER_SEQUENCER_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/sequence_barrier.hpp>
|
||||||
|
#include <cppcoro/sequence_range.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class single_producer_sequencer_claim_one_operation;
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class single_producer_sequencer_claim_operation;
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename SEQUENCE = std::size_t,
|
||||||
|
typename TRAITS = sequence_traits<SEQUENCE>>
|
||||||
|
class single_producer_sequencer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using size_type = typename sequence_range<SEQUENCE, TRAITS>::size_type;
|
||||||
|
|
||||||
|
single_producer_sequencer(
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& consumerBarrier,
|
||||||
|
std::size_t bufferSize,
|
||||||
|
SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept
|
||||||
|
: m_consumerBarrier(consumerBarrier)
|
||||||
|
, m_bufferSize(bufferSize)
|
||||||
|
, m_nextToClaim(initialSequence + 1)
|
||||||
|
, m_producerBarrier(initialSequence)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// Claim a slot in the ring buffer asynchronously.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// Returns an operation that when awaited will suspend the coroutine until
|
||||||
|
/// a slot is available for writing in the ring buffer. The result of the
|
||||||
|
/// co_await expression will be the sequence number of the slot.
|
||||||
|
/// The caller must publish() the claimed sequence number once they have written to
|
||||||
|
/// the ring-buffer.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
claim_one(SCHEDULER& scheduler) noexcept;
|
||||||
|
|
||||||
|
/// Claim one or more contiguous slots in the ring-buffer.
|
||||||
|
///
|
||||||
|
/// Use this method over many calls to claim_one() when you have multiple elements to
|
||||||
|
/// enqueue. This will claim as many slots as are available up to the specified count
|
||||||
|
/// but may claim as few as one slot if only one slot is available.
|
||||||
|
///
|
||||||
|
/// \param count
|
||||||
|
/// The maximum number of slots to claim.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// Returns an awaitable object that when awaited returns a sequence_range that contains
|
||||||
|
/// the range of sequence numbers that were claimed. Once you have written element values
|
||||||
|
/// to all of the claimed slots you must publish() the sequence range in order to make
|
||||||
|
/// the elements available to consumers.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER> claim_up_to(
|
||||||
|
std::size_t count, SCHEDULER& scheduler) noexcept;
|
||||||
|
|
||||||
|
/// Publish the specified sequence number.
|
||||||
|
///
|
||||||
|
/// This also implies that all prior sequence numbers have already been published.
|
||||||
|
void publish(SEQUENCE sequence) noexcept
|
||||||
|
{
|
||||||
|
m_producerBarrier.publish(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish a contiguous range of sequence numbers.
|
||||||
|
///
|
||||||
|
/// You must have already published all prior sequence numbers.
|
||||||
|
///
|
||||||
|
/// This is equivalent to just publishing the last sequence number in the range.
|
||||||
|
void publish(const sequence_range<SEQUENCE, TRAITS>& sequences) noexcept
|
||||||
|
{
|
||||||
|
m_producerBarrier.publish(sequences.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query what the last-published sequence number is.
|
||||||
|
///
|
||||||
|
/// You can assume that all prior sequence numbers are also published.
|
||||||
|
SEQUENCE last_published() const noexcept
|
||||||
|
{
|
||||||
|
return m_producerBarrier.last_published();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the specified sequence number is published.
|
||||||
|
///
|
||||||
|
/// \param targetSequence
|
||||||
|
/// The sequence number to wait for.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// Returns an Awaitable type that, when awaited, will suspend the awaiting coroutine until the
|
||||||
|
/// specified sequence number has been published.
|
||||||
|
///
|
||||||
|
/// The result of the 'co_await barrier.wait_until_published(seq)' expression will be the
|
||||||
|
/// last-published sequence number, which is guaranteed to be at least 'seq' but may be some
|
||||||
|
/// subsequent sequence number if additional items were published while waiting for the
|
||||||
|
/// the requested sequence number to be published.
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
auto wait_until_published(SEQUENCE targetSequence, SCHEDULER& scheduler) const noexcept
|
||||||
|
{
|
||||||
|
return m_producerBarrier.wait_until_published(targetSequence, scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
|
||||||
|
friend class single_producer_sequencer_claim_operation;
|
||||||
|
|
||||||
|
template<typename SEQUENCE2, typename TRAITS2, typename SCHEDULER>
|
||||||
|
friend class single_producer_sequencer_claim_one_operation;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const sequence_barrier<SEQUENCE, TRAITS>& m_consumerBarrier;
|
||||||
|
const std::size_t m_bufferSize;
|
||||||
|
|
||||||
|
alignas(CPPCORO_CPU_CACHE_LINE)
|
||||||
|
SEQUENCE m_nextToClaim;
|
||||||
|
|
||||||
|
sequence_barrier<SEQUENCE, TRAITS> m_producerBarrier;
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class single_producer_sequencer_claim_one_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
single_producer_sequencer_claim_one_operation(
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_consumerWaitOperation(
|
||||||
|
sequencer.m_consumerBarrier,
|
||||||
|
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
|
||||||
|
scheduler)
|
||||||
|
, m_sequencer(sequencer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_consumerWaitOperation.await_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE await_resume() const noexcept
|
||||||
|
{
|
||||||
|
return m_sequencer.m_nextToClaim++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS, typename SCHEDULER>
|
||||||
|
class single_producer_sequencer_claim_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit single_producer_sequencer_claim_operation(
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>& sequencer,
|
||||||
|
std::size_t count,
|
||||||
|
SCHEDULER& scheduler) noexcept
|
||||||
|
: m_consumerWaitOperation(
|
||||||
|
sequencer.m_consumerBarrier,
|
||||||
|
static_cast<SEQUENCE>(sequencer.m_nextToClaim - sequencer.m_bufferSize),
|
||||||
|
scheduler)
|
||||||
|
, m_sequencer(sequencer)
|
||||||
|
, m_count(count)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return m_consumerWaitOperation.await_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
return m_consumerWaitOperation.await_suspend(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence_range<SEQUENCE, TRAITS> await_resume() noexcept
|
||||||
|
{
|
||||||
|
const SEQUENCE lastAvailableSequence =
|
||||||
|
static_cast<SEQUENCE>(m_consumerWaitOperation.await_resume() + m_sequencer.m_bufferSize);
|
||||||
|
const SEQUENCE begin = m_sequencer.m_nextToClaim;
|
||||||
|
const std::size_t availableCount = static_cast<std::size_t>(lastAvailableSequence - begin) + 1;
|
||||||
|
const std::size_t countToClaim = std::min(m_count, availableCount);
|
||||||
|
const SEQUENCE end = static_cast<SEQUENCE>(begin + countToClaim);
|
||||||
|
m_sequencer.m_nextToClaim = end;
|
||||||
|
return sequence_range<SEQUENCE, TRAITS>(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
sequence_barrier_wait_operation<SEQUENCE, TRAITS, SCHEDULER> m_consumerWaitOperation;
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>& m_sequencer;
|
||||||
|
std::size_t m_count;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>::claim_one(SCHEDULER& scheduler) noexcept
|
||||||
|
{
|
||||||
|
return single_producer_sequencer_claim_one_operation<SEQUENCE, TRAITS, SCHEDULER>{ *this, scheduler };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SEQUENCE, typename TRAITS>
|
||||||
|
template<typename SCHEDULER>
|
||||||
|
[[nodiscard]]
|
||||||
|
single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>
|
||||||
|
single_producer_sequencer<SEQUENCE, TRAITS>::claim_up_to(std::size_t count, SCHEDULER& scheduler) noexcept
|
||||||
|
{
|
||||||
|
return single_producer_sequencer_claim_operation<SEQUENCE, TRAITS, SCHEDULER>(*this, count, scheduler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
|
||||||
|
#define CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class static_thread_pool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Initialise to a number of threads equal to the number of cores
|
||||||
|
/// on the current machine.
|
||||||
|
static_thread_pool();
|
||||||
|
|
||||||
|
/// Construct a thread pool with the specified number of threads.
|
||||||
|
///
|
||||||
|
/// \param threadCount
|
||||||
|
/// The number of threads in the pool that will be used to execute work.
|
||||||
|
explicit static_thread_pool(std::uint32_t threadCount);
|
||||||
|
|
||||||
|
~static_thread_pool();
|
||||||
|
|
||||||
|
class schedule_operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
schedule_operation(static_thread_pool* tp) noexcept : m_threadPool(tp) {}
|
||||||
|
|
||||||
|
bool await_ready() noexcept { return false; }
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept;
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class static_thread_pool;
|
||||||
|
|
||||||
|
static_thread_pool* m_threadPool;
|
||||||
|
cppcoro::coroutine_handle<> m_awaitingCoroutine;
|
||||||
|
schedule_operation* m_next;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
std::uint32_t thread_count() const noexcept { return m_threadCount; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
schedule_operation schedule() noexcept { return schedule_operation{ this }; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class schedule_operation;
|
||||||
|
|
||||||
|
void run_worker_thread(std::uint32_t threadIndex) noexcept;
|
||||||
|
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
void schedule_impl(schedule_operation* operation) noexcept;
|
||||||
|
|
||||||
|
void remote_enqueue(schedule_operation* operation) noexcept;
|
||||||
|
|
||||||
|
bool has_any_queued_work_for(std::uint32_t threadIndex) noexcept;
|
||||||
|
|
||||||
|
bool approx_has_any_queued_work_for(std::uint32_t threadIndex) const noexcept;
|
||||||
|
|
||||||
|
bool is_shutdown_requested() const noexcept;
|
||||||
|
|
||||||
|
void notify_intent_to_sleep(std::uint32_t threadIndex) noexcept;
|
||||||
|
void try_clear_intent_to_sleep(std::uint32_t threadIndex) noexcept;
|
||||||
|
|
||||||
|
schedule_operation* try_global_dequeue() noexcept;
|
||||||
|
|
||||||
|
/// Try to steal a task from another thread.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// A pointer to the operation that was stolen if one could be stolen
|
||||||
|
/// from another thread. Otherwise returns nullptr if none of the other
|
||||||
|
/// threads had any tasks that could be stolen.
|
||||||
|
schedule_operation* try_steal_from_other_thread(std::uint32_t thisThreadIndex) noexcept;
|
||||||
|
|
||||||
|
void wake_one_thread() noexcept;
|
||||||
|
|
||||||
|
class thread_state;
|
||||||
|
|
||||||
|
static thread_local thread_state* s_currentState;
|
||||||
|
static thread_local static_thread_pool* s_currentThreadPool;
|
||||||
|
|
||||||
|
const std::uint32_t m_threadCount;
|
||||||
|
const std::unique_ptr<thread_state[]> m_threadStates;
|
||||||
|
|
||||||
|
std::vector<std::thread> m_threads;
|
||||||
|
|
||||||
|
std::atomic<bool> m_stopRequested;
|
||||||
|
|
||||||
|
std::mutex m_globalQueueMutex;
|
||||||
|
std::atomic<schedule_operation*> m_globalQueueHead;
|
||||||
|
|
||||||
|
//alignas(std::hardware_destructive_interference_size)
|
||||||
|
std::atomic<schedule_operation*> m_globalQueueTail;
|
||||||
|
|
||||||
|
//alignas(std::hardware_destructive_interference_size)
|
||||||
|
std::atomic<std::uint32_t> m_sleepingThreadCount;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_SYNC_WAIT_HPP_INCLUDED
|
||||||
|
#define CPPCORO_SYNC_WAIT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
|
||||||
|
#include <cppcoro/detail/sync_wait_task.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <atomic>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
auto sync_wait(AWAITABLE&& awaitable)
|
||||||
|
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
|
||||||
|
{
|
||||||
|
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
||||||
|
detail::lightweight_manual_reset_event event;
|
||||||
|
task.start(event);
|
||||||
|
event.wait();
|
||||||
|
return task.result();
|
||||||
|
}
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
auto sync_wait(AWAITABLE&& awaitable, io_service& srv, std::chrono::system_clock::duration step)
|
||||||
|
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
|
||||||
|
{
|
||||||
|
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
||||||
|
detail::lightweight_manual_reset_event event;
|
||||||
|
task.start(event);
|
||||||
|
event.wait({ &srv, 1 }, step);
|
||||||
|
return task.result();
|
||||||
|
}
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
auto sync_wait(AWAITABLE&& awaitable, std::span<io_service> srvs, std::chrono::system_clock::duration step)
|
||||||
|
-> typename cppcoro::awaitable_traits<AWAITABLE&&>::await_result_t
|
||||||
|
{
|
||||||
|
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
||||||
|
detail::lightweight_manual_reset_event event;
|
||||||
|
task.start(event);
|
||||||
|
event.wait(srvs, step);
|
||||||
|
return task.result();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,481 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_TASK_HPP_INCLUDED
|
||||||
|
#define CPPCORO_TASK_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/broken_promise.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/remove_rvalue_reference.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <exception>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <cppcoro/coroutine.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<typename T> class task;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
class task_promise_base
|
||||||
|
{
|
||||||
|
friend struct final_awaitable;
|
||||||
|
|
||||||
|
struct final_awaitable
|
||||||
|
{
|
||||||
|
bool await_ready() const noexcept { return false; }
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
template<typename PROMISE>
|
||||||
|
cppcoro::coroutine_handle<> await_suspend(
|
||||||
|
cppcoro::coroutine_handle<PROMISE> coro) noexcept
|
||||||
|
{
|
||||||
|
return coro.promise().m_continuation;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// HACK: Need to add CPPCORO_NOINLINE to await_suspend() method
|
||||||
|
// to avoid MSVC 2017.8 from spilling some local variables in
|
||||||
|
// await_suspend() onto the coroutine frame in some cases.
|
||||||
|
// Without this, some tests in async_auto_reset_event_tests.cpp
|
||||||
|
// were crashing under x86 optimised builds.
|
||||||
|
template<typename PROMISE>
|
||||||
|
CPPCORO_NOINLINE
|
||||||
|
void await_suspend(cppcoro::coroutine_handle<PROMISE> coroutine) noexcept
|
||||||
|
{
|
||||||
|
task_promise_base& promise = coroutine.promise();
|
||||||
|
|
||||||
|
// Use 'release' memory semantics in case we finish before the
|
||||||
|
// awaiter can suspend so that the awaiting thread sees our
|
||||||
|
// writes to the resulting value.
|
||||||
|
// Use 'acquire' memory semantics in case the caller registered
|
||||||
|
// the continuation before we finished. Ensure we see their write
|
||||||
|
// to m_continuation.
|
||||||
|
if (promise.m_state.exchange(true, std::memory_order_acq_rel))
|
||||||
|
{
|
||||||
|
promise.m_continuation.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
task_promise_base() noexcept
|
||||||
|
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
: m_state(false)
|
||||||
|
#endif
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto initial_suspend() noexcept
|
||||||
|
{
|
||||||
|
return cppcoro::suspend_always{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept
|
||||||
|
{
|
||||||
|
return final_awaitable{};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
void set_continuation(cppcoro::coroutine_handle<> continuation) noexcept
|
||||||
|
{
|
||||||
|
m_continuation = continuation;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool try_set_continuation(cppcoro::coroutine_handle<> continuation)
|
||||||
|
{
|
||||||
|
m_continuation = continuation;
|
||||||
|
return !m_state.exchange(true, std::memory_order_acq_rel);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<> m_continuation;
|
||||||
|
|
||||||
|
#if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
// Initially false. Set to true when either a continuation is registered
|
||||||
|
// or when the coroutine has run to completion. Whichever operation
|
||||||
|
// successfully transitions from false->true got there first.
|
||||||
|
std::atomic<bool> m_state;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class task_promise final : public task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
task_promise() noexcept {}
|
||||||
|
|
||||||
|
~task_promise()
|
||||||
|
{
|
||||||
|
switch (m_resultType)
|
||||||
|
{
|
||||||
|
case result_type::value:
|
||||||
|
m_value.~T();
|
||||||
|
break;
|
||||||
|
case result_type::exception:
|
||||||
|
m_exception.~exception_ptr();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task<T> get_return_object() noexcept;
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(std::addressof(m_exception))) std::exception_ptr(
|
||||||
|
std::current_exception());
|
||||||
|
m_resultType = result_type::exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename VALUE,
|
||||||
|
typename = std::enable_if_t<std::is_convertible_v<VALUE&&, T>>>
|
||||||
|
void return_value(VALUE&& value)
|
||||||
|
noexcept(std::is_nothrow_constructible_v<T, VALUE&&>)
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(std::addressof(m_value))) T(std::forward<VALUE>(value));
|
||||||
|
m_resultType = result_type::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& result() &
|
||||||
|
{
|
||||||
|
if (m_resultType == result_type::exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(m_resultType == result_type::value);
|
||||||
|
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: Need to have co_await of task<int> return prvalue rather than
|
||||||
|
// rvalue-reference to work around an issue with MSVC where returning
|
||||||
|
// rvalue reference of a fundamental type from await_resume() will
|
||||||
|
// cause the value to be copied to a temporary. This breaks the
|
||||||
|
// sync_wait() implementation.
|
||||||
|
// See https://github.com/lewissbaker/cppcoro/issues/40#issuecomment-326864107
|
||||||
|
using rvalue_type = std::conditional_t<
|
||||||
|
std::is_arithmetic_v<T> || std::is_pointer_v<T>,
|
||||||
|
T,
|
||||||
|
T&&>;
|
||||||
|
|
||||||
|
rvalue_type result() &&
|
||||||
|
{
|
||||||
|
if (m_resultType == result_type::exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(m_resultType == result_type::value);
|
||||||
|
|
||||||
|
return std::move(m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum class result_type { empty, value, exception };
|
||||||
|
|
||||||
|
result_type m_resultType = result_type::empty;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
T m_value;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class task_promise<void> : public task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
task_promise() noexcept = default;
|
||||||
|
|
||||||
|
task<void> get_return_object() noexcept;
|
||||||
|
|
||||||
|
void return_void() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void result()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class task_promise<T&> : public task_promise_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
task_promise() noexcept = default;
|
||||||
|
|
||||||
|
task<T&> get_return_object() noexcept;
|
||||||
|
|
||||||
|
void unhandled_exception() noexcept
|
||||||
|
{
|
||||||
|
m_exception = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void return_value(T& value) noexcept
|
||||||
|
{
|
||||||
|
m_value = std::addressof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
T& result()
|
||||||
|
{
|
||||||
|
if (m_exception)
|
||||||
|
{
|
||||||
|
std::rethrow_exception(m_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
T* m_value = nullptr;
|
||||||
|
std::exception_ptr m_exception;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// A task represents an operation that produces a result both lazily
|
||||||
|
/// and asynchronously.
|
||||||
|
///
|
||||||
|
/// When you call a coroutine that returns a task, the coroutine
|
||||||
|
/// simply captures any passed parameters and returns exeuction to the
|
||||||
|
/// caller. Execution of the coroutine body does not start until the
|
||||||
|
/// coroutine is first co_await'ed.
|
||||||
|
template<typename T = void>
|
||||||
|
class [[nodiscard]] task
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using promise_type = detail::task_promise<T>;
|
||||||
|
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct awaitable_base
|
||||||
|
{
|
||||||
|
cppcoro::coroutine_handle<promise_type> m_coroutine;
|
||||||
|
|
||||||
|
awaitable_base(cppcoro::coroutine_handle<promise_type> coroutine) noexcept
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool await_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !m_coroutine || m_coroutine.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER
|
||||||
|
cppcoro::coroutine_handle<> await_suspend(
|
||||||
|
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
m_coroutine.promise().set_continuation(awaitingCoroutine);
|
||||||
|
return m_coroutine;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||||
|
{
|
||||||
|
// NOTE: We are using the bool-returning version of await_suspend() here
|
||||||
|
// to work around a potential stack-overflow issue if a coroutine
|
||||||
|
// awaits many synchronously-completing tasks in a loop.
|
||||||
|
//
|
||||||
|
// We first start the task by calling resume() and then conditionally
|
||||||
|
// attach the continuation if it has not already completed. This allows us
|
||||||
|
// to immediately resume the awaiting coroutine without increasing
|
||||||
|
// the stack depth, avoiding the stack-overflow problem. However, it has
|
||||||
|
// the down-side of requiring a std::atomic to arbitrate the race between
|
||||||
|
// the coroutine potentially completing on another thread concurrently
|
||||||
|
// with registering the continuation on this thread.
|
||||||
|
//
|
||||||
|
// We can eliminate the use of the std::atomic once we have access to
|
||||||
|
// coroutine_handle-returning await_suspend() on both MSVC and Clang
|
||||||
|
// as this will provide ability to suspend the awaiting coroutine and
|
||||||
|
// resume another coroutine with a guaranteed tail-call to resume().
|
||||||
|
m_coroutine.resume();
|
||||||
|
return m_coroutine.promise().try_set_continuation(awaitingCoroutine);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
task() noexcept
|
||||||
|
: m_coroutine(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit task(cppcoro::coroutine_handle<promise_type> coroutine)
|
||||||
|
: m_coroutine(coroutine)
|
||||||
|
{}
|
||||||
|
|
||||||
|
task(task&& t) noexcept
|
||||||
|
: m_coroutine(t.m_coroutine)
|
||||||
|
{
|
||||||
|
t.m_coroutine = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable copy construction/assignment.
|
||||||
|
task(const task&) = delete;
|
||||||
|
task& operator=(const task&) = delete;
|
||||||
|
|
||||||
|
/// Frees resources used by this task.
|
||||||
|
~task()
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task& operator=(task&& other) noexcept
|
||||||
|
{
|
||||||
|
if (std::addressof(other) != this)
|
||||||
|
{
|
||||||
|
if (m_coroutine)
|
||||||
|
{
|
||||||
|
m_coroutine.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_coroutine = other.m_coroutine;
|
||||||
|
other.m_coroutine = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Query if the task result is complete.
|
||||||
|
///
|
||||||
|
/// Awaiting a task that is ready is guaranteed not to block/suspend.
|
||||||
|
bool is_ready() const noexcept
|
||||||
|
{
|
||||||
|
return !m_coroutine || m_coroutine.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() const & noexcept
|
||||||
|
{
|
||||||
|
struct awaitable : awaitable_base
|
||||||
|
{
|
||||||
|
using awaitable_base::awaitable_base;
|
||||||
|
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
{
|
||||||
|
if (!this->m_coroutine)
|
||||||
|
{
|
||||||
|
throw broken_promise{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->m_coroutine.promise().result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaitable{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() const && noexcept
|
||||||
|
{
|
||||||
|
struct awaitable : awaitable_base
|
||||||
|
{
|
||||||
|
using awaitable_base::awaitable_base;
|
||||||
|
|
||||||
|
decltype(auto) await_resume()
|
||||||
|
{
|
||||||
|
if (!this->m_coroutine)
|
||||||
|
{
|
||||||
|
throw broken_promise{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(this->m_coroutine.promise()).result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaitable{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief
|
||||||
|
/// Returns an awaitable that will await completion of the task without
|
||||||
|
/// attempting to retrieve the result.
|
||||||
|
auto when_ready() const noexcept
|
||||||
|
{
|
||||||
|
struct awaitable : awaitable_base
|
||||||
|
{
|
||||||
|
using awaitable_base::awaitable_base;
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return awaitable{ m_coroutine };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
cppcoro::coroutine_handle<promise_type> m_coroutine;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
task<T> task_promise<T>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return task<T>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
|
||||||
|
}
|
||||||
|
|
||||||
|
inline task<void> task_promise<void>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return task<void>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
task<T&> task_promise<T&>::get_return_object() noexcept
|
||||||
|
{
|
||||||
|
return task<T&>{ cppcoro::coroutine_handle<task_promise>::from_promise(*this) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename AWAITABLE>
|
||||||
|
auto make_task(AWAITABLE awaitable)
|
||||||
|
-> task<detail::remove_rvalue_reference_t<typename awaitable_traits<AWAITABLE>::await_result_t>>
|
||||||
|
{
|
||||||
|
co_return co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_WHEN_ALL_HPP_INCLUDED
|
||||||
|
#define CPPCORO_WHEN_ALL_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/when_all_ready.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/is_awaitable.hpp>
|
||||||
|
#include <cppcoro/fmap.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/unwrap_reference.hpp>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
//////////
|
||||||
|
// Variadic when_all()
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename... AWAITABLES,
|
||||||
|
std::enable_if_t<
|
||||||
|
std::conjunction_v<is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>,
|
||||||
|
int> = 0>
|
||||||
|
[[nodiscard]] auto when_all(AWAITABLES&&... awaitables)
|
||||||
|
{
|
||||||
|
return fmap([](auto&& taskTuple)
|
||||||
|
{
|
||||||
|
return std::apply([](auto&&... tasks) {
|
||||||
|
return std::make_tuple(static_cast<decltype(tasks)>(tasks).non_void_result()...);
|
||||||
|
}, static_cast<decltype(taskTuple)>(taskTuple));
|
||||||
|
}, when_all_ready(std::forward<AWAITABLES>(awaitables)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////
|
||||||
|
// when_all() with vector of awaitable
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
|
||||||
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
|
[[nodiscard]]
|
||||||
|
auto when_all(std::vector<AWAITABLE> awaitables)
|
||||||
|
{
|
||||||
|
return fmap([](auto&& taskVector) {
|
||||||
|
for (auto& task : taskVector)
|
||||||
|
{
|
||||||
|
task.result();
|
||||||
|
}
|
||||||
|
}, when_all_ready(std::move(awaitables)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t,
|
||||||
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
|
[[nodiscard]]
|
||||||
|
auto when_all(std::vector<AWAITABLE> awaitables)
|
||||||
|
{
|
||||||
|
using result_t = std::conditional_t<
|
||||||
|
std::is_lvalue_reference_v<RESULT>,
|
||||||
|
std::reference_wrapper<std::remove_reference_t<RESULT>>,
|
||||||
|
std::remove_reference_t<RESULT>>;
|
||||||
|
|
||||||
|
return fmap([](auto&& taskVector) {
|
||||||
|
std::vector<result_t> results;
|
||||||
|
results.reserve(taskVector.size());
|
||||||
|
for (auto& task : taskVector)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_rvalue_reference_v<decltype(taskVector)>)
|
||||||
|
{
|
||||||
|
results.emplace_back(std::move(task).result());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.emplace_back(task.result());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}, when_all_ready(std::move(awaitables)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
|
||||||
|
#define CPPCORO_WHEN_ALL_READY_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/config.hpp>
|
||||||
|
#include <cppcoro/awaitable_traits.hpp>
|
||||||
|
#include <cppcoro/is_awaitable.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/detail/when_all_ready_awaitable.hpp>
|
||||||
|
#include <cppcoro/detail/when_all_task.hpp>
|
||||||
|
#include <cppcoro/detail/unwrap_reference.hpp>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
template<
|
||||||
|
typename... AWAITABLES,
|
||||||
|
std::enable_if_t<std::conjunction_v<
|
||||||
|
is_awaitable<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>...>, int> = 0>
|
||||||
|
[[nodiscard]]
|
||||||
|
CPPCORO_FORCE_INLINE auto when_all_ready(AWAITABLES&&... awaitables)
|
||||||
|
{
|
||||||
|
return detail::when_all_ready_awaitable<std::tuple<detail::when_all_task<
|
||||||
|
typename awaitable_traits<detail::unwrap_reference_t<std::remove_reference_t<AWAITABLES>>>::await_result_t>...>>(
|
||||||
|
std::make_tuple(detail::make_when_all_task(std::forward<AWAITABLES>(awaitables))...));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Generalise this from vector<AWAITABLE> to arbitrary sequence of awaitable.
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename AWAITABLE,
|
||||||
|
typename RESULT = typename awaitable_traits<detail::unwrap_reference_t<AWAITABLE>>::await_result_t>
|
||||||
|
[[nodiscard]] auto when_all_ready(std::vector<AWAITABLE> awaitables)
|
||||||
|
{
|
||||||
|
std::vector<detail::when_all_task<RESULT>> tasks;
|
||||||
|
|
||||||
|
tasks.reserve(awaitables.size());
|
||||||
|
|
||||||
|
for (auto& awaitable : awaitables)
|
||||||
|
{
|
||||||
|
tasks.emplace_back(detail::make_when_all_task(std::move(awaitable)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail::when_all_ready_awaitable<std::vector<detail::when_all_task<RESULT>>>(
|
||||||
|
std::move(tasks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_WRITABLE_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_WRITABLE_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/file.hpp>
|
||||||
|
#include <cppcoro/file_write_operation.hpp>
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class writable_file : virtual public file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Set the size of the file.
|
||||||
|
///
|
||||||
|
/// \param fileSize
|
||||||
|
/// The new size of the file in bytes.
|
||||||
|
void set_size(std::uint64_t fileSize);
|
||||||
|
|
||||||
|
/// Write some data to the file.
|
||||||
|
///
|
||||||
|
/// Writes \a byteCount bytes from the file starting at \a offset
|
||||||
|
/// into the specified \a buffer.
|
||||||
|
///
|
||||||
|
/// \param offset
|
||||||
|
/// The offset within the file to start writing from.
|
||||||
|
/// If the file has been opened using file_buffering_mode::unbuffered
|
||||||
|
/// then the offset must be a multiple of the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param buffer
|
||||||
|
/// The buffer containing the data to be written to the file.
|
||||||
|
/// If the file has been opened using file_buffering_mode::unbuffered
|
||||||
|
/// then the address of the start of the buffer must be a multiple of
|
||||||
|
/// the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param byteCount
|
||||||
|
/// The number of bytes to write to the file.
|
||||||
|
/// If the file has been opeend using file_buffering_mode::unbuffered
|
||||||
|
/// then the byteCount must be a multiple of the file-system's sector size.
|
||||||
|
///
|
||||||
|
/// \param ct
|
||||||
|
/// An optional cancellation_token that can be used to cancel the
|
||||||
|
/// write operation before it completes.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An object that represents the write operation.
|
||||||
|
/// This object must be co_await'ed to start the write operation.
|
||||||
|
[[nodiscard]]
|
||||||
|
file_write_operation write(
|
||||||
|
std::uint64_t offset,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount) noexcept;
|
||||||
|
[[nodiscard]]
|
||||||
|
file_write_operation_cancellable write(
|
||||||
|
std::uint64_t offset,
|
||||||
|
const void* buffer,
|
||||||
|
std::size_t byteCount,
|
||||||
|
cancellation_token ct) noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
using file::file;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
|
||||||
|
#define CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <cppcoro/writable_file.hpp>
|
||||||
|
#include <cppcoro/file_share_mode.hpp>
|
||||||
|
#include <cppcoro/file_buffering_mode.hpp>
|
||||||
|
#include <cppcoro/file_open_mode.hpp>
|
||||||
|
|
||||||
|
#include <cppcoro/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace cppcoro
|
||||||
|
{
|
||||||
|
class write_only_file : public writable_file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Open a file for write-only access.
|
||||||
|
///
|
||||||
|
/// \param ioContext
|
||||||
|
/// The I/O context to use when dispatching I/O completion events.
|
||||||
|
/// When asynchronous write operations on this file complete the
|
||||||
|
/// completion events will be dispatched to an I/O thread associated
|
||||||
|
/// with the I/O context.
|
||||||
|
///
|
||||||
|
/// \param pathMode
|
||||||
|
/// Path of the file to open.
|
||||||
|
///
|
||||||
|
/// \param openMode
|
||||||
|
/// Specifies how the file should be opened and how to handle cases
|
||||||
|
/// when the file exists or doesn't exist.
|
||||||
|
///
|
||||||
|
/// \param shareMode
|
||||||
|
/// Specifies the access to be allowed on the file concurrently with this file access.
|
||||||
|
///
|
||||||
|
/// \param bufferingMode
|
||||||
|
/// Specifies the modes/hints to provide to the OS that affects the behaviour
|
||||||
|
/// of its file buffering.
|
||||||
|
///
|
||||||
|
/// \return
|
||||||
|
/// An object that can be used to write to the file.
|
||||||
|
///
|
||||||
|
/// \throw std::system_error
|
||||||
|
/// If the file could not be opened for write.
|
||||||
|
[[nodiscard]]
|
||||||
|
static write_only_file open(
|
||||||
|
io_service& ioService,
|
||||||
|
const cppcoro::filesystem::path& path,
|
||||||
|
file_open_mode openMode = file_open_mode::create_or_open,
|
||||||
|
file_share_mode shareMode = file_share_mode::none,
|
||||||
|
file_buffering_mode bufferingMode = file_buffering_mode::default_);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if CPPCORO_OS_WINNT
|
||||||
|
write_only_file(detail::win32::safe_handle&& fileHandle) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -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)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
@@ -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,
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
Reference in New Issue
Block a user