├── .gitignore ├── .gitmodules ├── examples ├── CMakeLists.txt └── echo.cpp ├── ufiberConfig.cmake ├── tools └── coverage.sh ├── .clang-format ├── vcpkg.json ├── tests ├── CMakeLists.txt └── ufiber │ ├── yield_token_conversion.cpp │ ├── common.hpp │ ├── spawn.cpp │ └── spawn_discard.cpp ├── include └── ufiber │ ├── detail │ ├── config.hpp │ └── ufiber.hpp │ ├── impl │ ├── ufiber.hpp │ └── ufiber.ipp │ └── ufiber.hpp ├── LICENSE_1_0.txt ├── CMakeLists.txt ├── .github └── workflows │ └── hosted-latest-vcpkg.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .directory 3 | .vscode 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg"] 2 | path = vcpkg 3 | url = https://github.com/microsoft/vcpkg.git 4 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #find_package(ufiber REQUIRED) 2 | 3 | add_executable(echo_example echo.cpp) 4 | target_link_libraries(echo_example PRIVATE 5 | ufiber::ufiber 6 | Boost::system) 7 | -------------------------------------------------------------------------------- /ufiberConfig.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(Boost COMPONENTS context system) 4 | find_dependency(Threads) 5 | 6 | include("${CMAKE_CURRENT_LIST_DIR}/ufiberTargets.cmake") 7 | -------------------------------------------------------------------------------- /tools/coverage.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | lcov --directory $1 --capture --output-file $1/coverage.info && \ 5 | lcov --extract $1/coverage.info $(pwd)'/include/*' --output-file $1/coverage.info && \ 6 | lcov --list $1/coverage.info 7 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Mozilla 3 | BreakBeforeBraces: Allman 4 | ColumnLimit: '80' 5 | Cpp11BracedListStyle: 'true' 6 | IndentWidth: '4' 7 | Language: Cpp 8 | SortIncludes: 'true' 9 | Standard: Cpp11 10 | UseTab: Never 11 | AccessModifierOffset: -4 12 | AllowShortFunctionsOnASingleLine: 'false' 13 | ... 14 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ufiber", 3 | "maintainers": "Damian Jarek ", 4 | "version-string": "2", 5 | "license": "BSL-1.0", 6 | "homepage": "https://github.com/djarek/ufiber", 7 | "documentation": "https://github.com/djarek/ufiber#api", 8 | "description": "Minimalistic, header-only fiber library compatible with Boost.ASIO", 9 | "dependencies": ["boost-asio"], 10 | "supports": "linux" 11 | } 12 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (ufiber_tests_srcs 2 | ufiber/spawn.cpp 3 | ufiber/spawn_discard.cpp 4 | ufiber/yield_token_conversion.cpp) 5 | 6 | function (ufiber_add_test test_file) 7 | get_filename_component(target_name ${test_file} NAME_WE) 8 | add_executable(${target_name} ${test_file}) 9 | target_link_libraries(${target_name} ufiber::ufiber) 10 | 11 | add_test(NAME "${target_name}_tests" 12 | COMMAND ${target_name}) 13 | endfunction(ufiber_add_test) 14 | 15 | foreach(test_src_name IN ITEMS ${ufiber_tests_srcs}) 16 | ufiber_add_test(${test_src_name}) 17 | endforeach() 18 | -------------------------------------------------------------------------------- /include/ufiber/detail/config.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_DETAIL_CONFIG_HPP 11 | #define UFIBER_DETAIL_CONFIG_HPP 12 | 13 | #ifndef UFIBER_SEPARATE_COMPILATION 14 | #define UFIBER_INLINE_DECL inline 15 | #else 16 | #define UFIBER_INLINE_DECL 17 | #endif // UFIBER_SEPARATE_COMPILATION 18 | 19 | #endif // UFIBER_DETAIL_CONFIG_HPP 20 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /tests/ufiber/yield_token_conversion.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | int 17 | main() 18 | { 19 | // Check if it's possible to do yield_token conversions (e.g. to type erase 20 | // the executor) 21 | int count = 0; 22 | auto f = 23 | [&](ufiber::yield_token y) { 24 | ++count; 25 | ufiber::yield_token yield{y}; 26 | // Check we don't get spawned into the system executor by accident 27 | BOOST_TEST(yield.get_executor().running_in_this_thread()); 28 | boost::asio::post(yield); 29 | BOOST_TEST(yield.get_executor().running_in_this_thread()); 30 | ++count; 31 | ufiber::yield_token yield2{y}; 32 | boost::asio::post(yield2); 33 | BOOST_TEST(yield.get_executor().running_in_this_thread()); 34 | ++count; 35 | }; 36 | 37 | count = 0; 38 | boost::asio::io_context io{}; 39 | ufiber::spawn(io, f); 40 | 41 | BOOST_TEST(io.run() > 0); 42 | BOOST_TEST(count == 3); 43 | 44 | return boost::report_errors(); 45 | } 46 | -------------------------------------------------------------------------------- /include/ufiber/impl/ufiber.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_IMPL_UFIBER_HPP 11 | #define UFIBER_IMPL_UFIBER_HPP 12 | 13 | #include 14 | 15 | namespace ufiber 16 | { 17 | 18 | template 19 | yield_token::yield_token(Executor const& ex, 20 | detail::fiber_context& ctx) 21 | : ctx_{ctx} 22 | , executor_{ex} 23 | { 24 | } 25 | template 26 | typename yield_token::executor_type 27 | yield_token::get_executor() noexcept 28 | { 29 | return executor_; 30 | } 31 | 32 | template 33 | auto 34 | spawn(E const& ex, F&& f) -> 35 | typename std::enable_if::value>::type 36 | { 37 | detail::initial_resume( 38 | boost::context::fiber{detail::fiber_main::type, E>{ 39 | std::forward(f), ex}}); 40 | } 41 | 42 | template 43 | auto 44 | spawn(Ctx& ctx, F&& f) -> typename std::enable_if< 45 | std::is_convertible::value>::type 46 | { 47 | detail::initial_resume( 48 | boost::context::fiber{detail::fiber_main::type, 49 | typename Ctx::executor_type>{ 50 | std::forward(f), ctx.get_executor()}}); 51 | } 52 | 53 | template 54 | void 55 | spawn(std::allocator_arg_t arg, Alloc&& sa, Executor const& ex, F&& f) 56 | { 57 | detail::initial_resume(boost::context::fiber{ 58 | arg, 59 | std::forward(sa), 60 | detail::fiber_main::type, Executor>{ 61 | std::forward(f), ex}}); 62 | } 63 | } // namespace ufiber 64 | 65 | #endif // UFIBER_IMPL_UFIBER_HPP 66 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(ufiber VERSION 2 LANGUAGES CXX) 3 | 4 | option(UFIBER_SANITIZE "Build ufiber tests and examples with address & undefined sanitization enabled" OFF) 5 | if (UFIBER_SANITIZE) 6 | message(STATUS "ufiber: address & undefined sanitizers enabled") 7 | add_compile_options(-fsanitize=address,undefined) 8 | link_libraries(-fsanitize=address,undefined) 9 | endif() 10 | 11 | find_package(Boost 1.70 12 | COMPONENTS 13 | context 14 | system 15 | REQUIRED) 16 | 17 | find_package (Threads) 18 | 19 | 20 | add_library(ufiber INTERFACE) 21 | add_library(ufiber::ufiber ALIAS ufiber) 22 | 23 | target_include_directories(ufiber INTERFACE 24 | $ 25 | $ 26 | ) 27 | 28 | target_link_libraries( 29 | ufiber 30 | INTERFACE 31 | Boost::system 32 | Boost::context 33 | Threads::Threads) 34 | 35 | target_compile_features(ufiber INTERFACE cxx_std_11) 36 | 37 | include(CTest) 38 | if(BUILD_TESTING) 39 | enable_testing() 40 | add_subdirectory(tests) 41 | add_subdirectory(examples) 42 | endif() 43 | 44 | 45 | include(GNUInstallDirs) 46 | 47 | install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ 48 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 49 | FILES_MATCHING 50 | PATTERN "*.hpp" 51 | PATTERN "*.ipp") 52 | 53 | install(TARGETS ufiber 54 | EXPORT ufiberTargets 55 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 56 | 57 | set(UFIBER_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) 58 | unset(CMAKE_SIZEOF_VOID_P) 59 | 60 | include(CMakePackageConfigHelpers) 61 | write_basic_package_version_file( 62 | "ufiberConfigVersion.cmake" 63 | COMPATIBILITY AnyNewerVersion) 64 | 65 | set(CMAKE_SIZEOF_VOID_P ${UFIBER_SIZEOF_VOID_P}) 66 | unset(UFIBER_SIZEOF_VOID_P) 67 | 68 | install(FILES 69 | "ufiberConfig.cmake" 70 | "${CMAKE_BINARY_DIR}/ufiberConfigVersion.cmake" 71 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ufiber) 72 | 73 | install(EXPORT ufiberTargets 74 | FILE ufiberTargets.cmake 75 | NAMESPACE ufiber:: 76 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ufiber) 77 | -------------------------------------------------------------------------------- /include/ufiber/impl/ufiber.ipp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_IMPL_UFIBER_IPP 11 | #define UFIBER_IMPL_UFIBER_IPP 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace ufiber 18 | { 19 | 20 | char const* 21 | broken_promise::what() const noexcept 22 | { 23 | return "Broken fiber promise"; 24 | } 25 | 26 | namespace detail 27 | { 28 | 29 | #ifndef BOOST_NO_EXCEPTIONS 30 | [[noreturn]] void 31 | throw_broken_promise() 32 | { 33 | // If you disable exceptions you'll need to provide your own definition of 34 | // this function, which is called when an asynchronous operation is 35 | // abandoned (e.g. during a hard shutdown of an `io_context`). 36 | throw broken_promise{}; 37 | } 38 | #endif // BOOST_NO_EXCEPTIONS 39 | 40 | fiber_context::fiber_context(boost::context::fiber&& f) 41 | : fiber_{std::move(f)} 42 | { 43 | } 44 | 45 | void 46 | fiber_context::resumer::operator()(void*) noexcept 47 | { 48 | // Move onto stack, because resume() may invalidate ctx if fiber terminates 49 | auto fiber = std::move(ctx_.fiber_); 50 | fiber = std::move(fiber).resume(); 51 | // At this point the fiber has either suspended in a different async op 52 | // or it terminated, so it's impossible for us to get a fiber back here 53 | assert(!fiber && "Unexpected fiber"); 54 | } 55 | 56 | boost::context::fiber 57 | fiber_context::final_suspend() noexcept 58 | { 59 | return std::move(fiber_); 60 | } 61 | 62 | void 63 | initial_resume(boost::context::fiber&& f) 64 | { 65 | f = std::move(f).resume(); 66 | assert(!f && "Unexpected fiber"); 67 | } 68 | 69 | void 70 | promise<>::set_result() noexcept 71 | { 72 | state_ = result_state::value; 73 | } 74 | 75 | void 76 | promise<>::get_value() 77 | { 78 | switch (state_) 79 | { 80 | case result_state::value: 81 | return; 82 | default: 83 | throw broken_promise{}; 84 | } 85 | } 86 | 87 | } // detail 88 | } // ufiber 89 | 90 | #endif // UFIBER_IMPL_UFIBER_IPP 91 | -------------------------------------------------------------------------------- /tests/ufiber/common.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_TEST_COMMON 11 | #define UFIBER_TEST_COMMON 12 | 13 | #include 14 | #include 15 | 16 | namespace ufiber 17 | { 18 | namespace test 19 | { 20 | template 21 | auto 22 | async_op_1arg(boost::asio::io_context& ctx, int val, CompletionToken&& tok) 23 | -> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(int)) 24 | { 25 | using handler_t = BOOST_ASIO_HANDLER_TYPE(CompletionToken, void(int)); 26 | struct op 27 | { 28 | void operator()() 29 | { 30 | handler_(val_); 31 | } 32 | 33 | handler_t handler_; 34 | int val_; 35 | }; 36 | 37 | return boost::asio::async_initiate( 38 | [&](handler_t&& handler) { 39 | auto ex = 40 | boost::asio::get_associated_executor(handler, ctx.get_executor()); 41 | boost::asio::post(ex, op{std::move(handler), val}); 42 | }, 43 | std::forward(tok)); 44 | } 45 | 46 | template 47 | auto 48 | async_op_2arg(boost::asio::io_context& ctx, 49 | int val1, 50 | std::unique_ptr val2, 51 | CompletionToken&& tok) 52 | -> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, 53 | void(int, std::unique_ptr)) 54 | { 55 | using handler_t = 56 | BOOST_ASIO_HANDLER_TYPE(CompletionToken, void(int, std::unique_ptr)); 57 | struct op 58 | { 59 | void operator()() 60 | { 61 | handler_(val1_, std::move(val2_)); 62 | } 63 | 64 | handler_t handler_; 65 | int val1_; 66 | std::unique_ptr val2_; 67 | }; 68 | 69 | return boost::asio::async_initiate)>( 71 | [&](handler_t&& handler) { 72 | auto ex = 73 | boost::asio::get_associated_executor(handler, ctx.get_executor()); 74 | boost::asio::post(ex, op{std::move(handler), val1, std::move(val2)}); 75 | }, 76 | std::forward(tok)); 77 | } 78 | 79 | } // namespace test 80 | } // namespace ufiber 81 | 82 | #endif // UFIBER_TEST_COMMON 83 | -------------------------------------------------------------------------------- /tests/ufiber/spawn.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "common.hpp" 17 | 18 | int 19 | main() 20 | { 21 | using yield_token_t = 22 | ufiber::yield_token; 23 | int count = 0; 24 | std::function suspension_op = [](yield_token_t yield) { 25 | boost::asio::post(yield); 26 | }; 27 | auto f = [&](yield_token_t yield) { 28 | ++count; 29 | // Check we don't get spawned into the system executor by accident 30 | BOOST_TEST(yield.get_executor().running_in_this_thread()); 31 | suspension_op(yield); 32 | BOOST_TEST(yield.get_executor().running_in_this_thread()); 33 | ++count; 34 | }; 35 | 36 | { 37 | count = 0; 38 | boost::asio::io_context io{}; 39 | ufiber::spawn(io, f); 40 | 41 | BOOST_TEST(io.run() > 0); 42 | } 43 | BOOST_TEST(count == 2); 44 | 45 | { 46 | count = 0; 47 | boost::asio::io_context io{}; 48 | ufiber::spawn(io.get_executor(), f); 49 | 50 | BOOST_TEST(io.run() > 0); 51 | } 52 | BOOST_TEST(count == 2); 53 | 54 | { 55 | count = 0; 56 | boost::asio::io_context io{}; 57 | ufiber::spawn(std::allocator_arg, 58 | boost::context::default_stack{}, 59 | io.get_executor(), 60 | f); 61 | 62 | BOOST_TEST(io.run() > 0); 63 | } 64 | BOOST_TEST(count == 2); 65 | 66 | { 67 | // Check if a single argument is returned without wrapping into tuple 68 | count = 0; 69 | boost::asio::io_context io{}; 70 | suspension_op = [&](yield_token_t yield) { 71 | int n = ufiber::test::async_op_1arg(io, 42, yield); 72 | BOOST_TEST(n == 42); 73 | }; 74 | ufiber::spawn(io, f); 75 | 76 | BOOST_TEST(io.run() > 0); 77 | } 78 | BOOST_TEST(count == 2); 79 | 80 | { 81 | // Check if a 2 arguments are wrapped into a tuple and that non-trivial 82 | // objects are handled properly 83 | count = 0; 84 | boost::asio::io_context io{}; 85 | suspension_op = [&](yield_token_t yield) { 86 | int n; 87 | std::unique_ptr p; 88 | 89 | std::tie(n, p) = ufiber::test::async_op_2arg( 90 | io, 42, boost::make_unique(43), yield); 91 | BOOST_TEST(n == 42); 92 | BOOST_TEST(p != nullptr && *p == 43); 93 | }; 94 | ufiber::spawn(io, f); 95 | 96 | BOOST_TEST(io.run() > 0); 97 | } 98 | BOOST_TEST(count == 2); 99 | 100 | return boost::report_errors(); 101 | } 102 | -------------------------------------------------------------------------------- /.github/workflows/hosted-latest-vcpkg.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Luca Cappa 2 | # Copyright (c) 2020 Damian Jarek 3 | # SPDX short identifier: MIT 4 | 5 | name: hosted-latest-vcpkg 6 | on: [push] 7 | 8 | jobs: 9 | job: 10 | name: ${{ matrix.os }}-hosted-basic 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest, windows-latest] 16 | include: 17 | - os: windows-latest 18 | triplet: x64-windows 19 | vcpkgCommitId: 'ec6fe06e8da05a8157dc8581fa96b36b571c1bd5' 20 | cmakeAppendedArgs: '-GNinja' 21 | - os: ubuntu-latest 22 | triplet: x64-linux 23 | vcpkgCommitId: 'ec6fe06e8da05a8157dc8581fa96b36b571c1bd5' 24 | cmakeAppendedArgs: '-GNinja -DCMAKE_CXX_FLAGS="-fprofile-arcs --coverage -Wall -Wextra -Werror -pedantic"' 25 | collectCoverage: true 26 | 27 | steps: 28 | - uses: actions/checkout@v1 29 | with: 30 | submodules: true 31 | 32 | - uses: lukka/get-cmake@latest 33 | - name: dir 34 | run: find $RUNNER_WORKSPACE 35 | shell: bash 36 | - name: Restore artifacts, or setup vcpkg (do not install any package) 37 | uses: lukka/run-vcpkg@v6 38 | with: 39 | setupOnly: true 40 | vcpkgDirectory: '${{ github.workspace }}/vcpkg' 41 | appendedCacheKey: ${{ hashFiles( '**/vcpkg_manifest/vcpkg.json' ) }} 42 | vcpkgTriplet: ${{ matrix.triplet }} 43 | - name: dir 44 | run: find $RUNNER_WORKSPACE 45 | shell: bash 46 | - name: Prints outputs of run-vcpkg task 47 | run: echo "'${{ steps.runvcpkg.outputs.RUNVCPKG_VCPKG_ROOT_OUT }}' '${{ steps.runvcpkg.outputs.RUNVCPKG_VCPKG_TRIPLET_OUT }}' " 48 | - name: Run CMake+Ninja with triplet 49 | uses: lukka/run-cmake@v3 50 | id: runcmake_cmd 51 | with: 52 | cmakeGenerator: 'Ninja' 53 | cmakeListsOrSettingsJson: 'CMakeListsTxtAdvanced' 54 | cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt' 55 | useVcpkgToolchainFile: true 56 | buildWithCMakeArgs: '-- -v' 57 | vcpkgTriplet: ${{ steps.runvcpkg.outputs.RUNVCPKG_VCPKG_TRIPLET_OUT }} 58 | buildDirectory: '${{ runner.workspace }}/b/ninja/' 59 | cmakeAppendedArgs: ${{ matrix.cmakeAppendedArgs }} 60 | - name: Run tests 61 | run: cd ${{ runner.workspace }}/b/ninja/ && ctest -V 62 | - name: Install lcov 63 | if: ${{ matrix.collectCoverage }} 64 | run: sudo apt install -y lcov 65 | shell: bash 66 | - name: Run lcov 67 | if: ${{ matrix.collectCoverage }} 68 | run: ./tools/coverage.sh ${{ runner.workspace }}/b/ninja/ 69 | shell: bash 70 | - name: Upload coverage to Codecov 71 | if: ${{ matrix.collectCoverage }} 72 | uses: codecov/codecov-action@v1 73 | with: 74 | token: ${{ secrets.CODECOV_TOKEN }} 75 | file: ${{ runner.workspace }}/b/ninja/coverage.info 76 | directory: ${{ runner.workspace }}/b/ninja/reports/ 77 | fail_ci_if_error: true 78 | path_to_write_report: ${{ runner.workspace }}/b/ninja/codecov_report.txt 79 | verbose: true 80 | -------------------------------------------------------------------------------- /tests/ufiber/spawn_discard.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "common.hpp" 17 | 18 | int 19 | main() 20 | { 21 | using yield_token_t = 22 | ufiber::yield_token; 23 | // Check if destroying the io_context properly destroys the fibers if the 24 | // operations are abandoned. 25 | int count = 0; 26 | bool abandoned = false; 27 | std::function suspension_op = [](yield_token_t yield) { 28 | boost::asio::post(yield); 29 | }; 30 | auto f = [&](yield_token_t yield) { 31 | ++count; 32 | try 33 | { 34 | suspension_op(yield); 35 | } 36 | catch (ufiber::broken_promise const& ex) 37 | { 38 | BOOST_TEST(ex.what() == std::string{"Broken fiber promise"}); 39 | abandoned = true; 40 | throw; 41 | } 42 | ++count; 43 | }; 44 | 45 | { 46 | abandoned = false; 47 | count = 0; 48 | boost::asio::io_context io{}; 49 | ufiber::spawn(io, f); 50 | 51 | BOOST_TEST(io.run_one() > 0); 52 | } 53 | BOOST_TEST(abandoned == true); 54 | BOOST_TEST(count == 1); 55 | 56 | { 57 | abandoned = false; 58 | count = 0; 59 | boost::asio::io_context io{}; 60 | ufiber::spawn(io.get_executor(), f); 61 | 62 | BOOST_TEST(io.run_one() > 0); 63 | } 64 | BOOST_TEST(abandoned == true); 65 | BOOST_TEST(count == 1); 66 | 67 | { 68 | abandoned = false; 69 | count = 0; 70 | boost::asio::io_context io{}; 71 | ufiber::spawn(std::allocator_arg, 72 | boost::context::default_stack{}, 73 | io.get_executor(), 74 | f); 75 | 76 | BOOST_TEST(io.run_one() > 0); 77 | } 78 | BOOST_TEST(abandoned == true); 79 | BOOST_TEST(count == 1); 80 | 81 | { 82 | abandoned = false; 83 | count = 0; 84 | boost::asio::io_context io{}; 85 | suspension_op = [&](yield_token_t yield) { 86 | int n = ufiber::test::async_op_1arg(io, 42, yield); 87 | (void)n; 88 | BOOST_ERROR("Abandoned fiber has been resumed normally"); 89 | }; 90 | ufiber::spawn(io, f); 91 | 92 | BOOST_TEST(io.run_one() > 0); 93 | } 94 | BOOST_TEST(abandoned == true); 95 | BOOST_TEST(count == 1); 96 | 97 | { 98 | abandoned = false; 99 | count = 0; 100 | boost::asio::io_context io{}; 101 | suspension_op = [&](yield_token_t yield) { 102 | int n; 103 | std::unique_ptr p; 104 | 105 | std::tie(n, p) = ufiber::test::async_op_2arg( 106 | io, 42, boost::make_unique(43), yield); 107 | BOOST_ERROR("Abandoned fiber has been resumed normally"); 108 | }; 109 | ufiber::spawn(io, f); 110 | 111 | BOOST_TEST(io.run_one() > 0); 112 | } 113 | BOOST_TEST(abandoned == true); 114 | BOOST_TEST(count == 1); 115 | 116 | return boost::report_errors(); 117 | } 118 | -------------------------------------------------------------------------------- /examples/echo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace echo 8 | { 9 | namespace net = boost::asio; 10 | 11 | using tcp_socket = 12 | net::basic_stream_socket; 13 | 14 | using tcp_acceptor = 15 | net::basic_socket_acceptor; 16 | 17 | namespace 18 | { 19 | // We use a function object here so that we can easily bind together the socket 20 | // and a function that operates on it. We could use `std::bind` instead, but 21 | // that uses a lot of template machinery for the same effect. 22 | struct echo_session 23 | { 24 | void operator()(ufiber::yield_token yield) 25 | { 26 | // Make use of the fiber's stack by placing a static buffer on it. 27 | std::uint8_t buffer[8192]; 28 | for (;;) 29 | { 30 | boost::system::error_code ec; 31 | std::size_t n; 32 | // We can make use of `std::tie` to conveniently assign the results 33 | // of an async operation into stack variables. In C++17, structured 34 | // bindings can be used instead. 35 | std::tie(ec, n) = 36 | socket_.async_read_some(boost::asio::buffer(buffer), yield); 37 | if (ec) 38 | { 39 | std::cerr << "Error while reading from socket: " << ec.message() 40 | << '\n'; 41 | return; 42 | } 43 | 44 | std::tie(ec, n) = 45 | boost::asio::async_write(socket_, boost::asio::buffer(buffer, n), yield); 46 | if (ec) 47 | { 48 | std::cerr << "Error while writing to socket: " << ec.message() 49 | << '\n'; 50 | return; 51 | } 52 | } 53 | } 54 | 55 | tcp_socket socket_; 56 | }; 57 | 58 | // μfiber uses a yield_token that has an Executor parameter, so no type-erasure 59 | // is used. The only allocations that will be performed when using μfiber and 60 | // boost::asio are the alocations of the fiber stacks and temporary allocations 61 | // done by ASIO for async operations. The latter are recycled internally by 62 | // ASIO, so in practice you'll see very few allocations that actually touch the 63 | // global heap. 64 | void 65 | accept(ufiber::yield_token yield) 66 | { 67 | auto ex = yield.get_executor(); 68 | tcp_acceptor acceptor{ 69 | ex.context(), net::ip::tcp::endpoint{net::ip::address_v6::any(), 8000}}; 70 | 71 | tcp_socket s{ex.context()}; 72 | for (;;) 73 | { 74 | // When an async operation returns 1 result, it's not wrapped into a 75 | // tuple, so we can just use it directly. 76 | boost::system::error_code ec = acceptor.async_accept(s, yield); 77 | if (ec) 78 | { 79 | std::cerr << "Error when trying to accept: " << ec.message() 80 | << '\n'; 81 | return; 82 | } 83 | // Spawn a new fiber, use a function object as its entry point. Note 84 | // that the entry point is not copyable, but is move constructible. Here 85 | // we use the overload which accepts an Executor, rather than an 86 | // ExecutionContext. 87 | ufiber::spawn(ex, echo_session{std::move(s)}); 88 | // Note that a moved-from socket is guaranteed to be in a state usable 89 | // for `async_accept`, so we can hoist construction of `s` out of the 90 | // loop, because the "default" constructor of `socket` is fairly 91 | // expensive (requires searching for services in the `io_context`'s 92 | // service registry). 93 | } 94 | } 95 | 96 | void 97 | run() 98 | { 99 | net::io_context io; 100 | // Spawn a new fiber, we use a function pointer as its entry point. The 101 | // newly created fiber will run on the provided ExecutionContext 102 | // and the current thread of execution is not blocked. 103 | ufiber::spawn(io, &echo::accept); 104 | // The spawned fiber will not run until there is a thread that "runs" within 105 | // the `io_context`. 106 | io.run(); 107 | } 108 | 109 | } // namespace 110 | } // namespace echo 111 | 112 | int 113 | main() 114 | { 115 | echo::run(); 116 | } 117 | -------------------------------------------------------------------------------- /include/ufiber/ufiber.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_UFIBER_HPP 11 | #define UFIBER_UFIBER_HPP 12 | 13 | #include 14 | 15 | /** 16 | * @file 17 | * Main API header of the library. 18 | */ 19 | 20 | namespace ufiber 21 | { 22 | 23 | /** 24 | * @mainpage μfiber library 25 | * 26 | * @ref ufiber 27 | */ 28 | 29 | /** 30 | * Exception thrown if an asynchronous operation is abandoned. If this exception 31 | * escapes the fiber's main function, the fiber will complete normally. This 32 | * behavior is required so that an `io_context` abandoning work does not result 33 | * in a call to std::terminate. 34 | * 35 | * @remark If this exception is thrown, the fiber may have been resumed outside 36 | * its associated executor's context. 37 | */ 38 | struct broken_promise final : std::exception 39 | { 40 | UFIBER_INLINE_DECL char const* what() const noexcept final; 41 | }; 42 | 43 | /** 44 | * A lightweight handle to the currently running fiber. 45 | * 46 | * @remark Use of a token outside the fiber it was created on results in 47 | * undefined behavior, but it may safely by passed to functions that run on the 48 | * current fiber's stack. 49 | * 50 | * @tparam Executor executor to be used to execute the asynchronous operations. 51 | */ 52 | template 53 | class yield_token 54 | { 55 | public: 56 | /** 57 | * Executor type associated with this yield_token object. 58 | */ 59 | using executor_type = Executor; 60 | 61 | /** 62 | * Converting constructor. 63 | * This function participates in overload resolution only if Executor is 64 | * Constructible from E const&. 65 | */ 66 | template::value>::type> 69 | yield_token(yield_token const& other) 70 | : ctx_{other.ctx_} 71 | , executor_{other.executor_} 72 | { 73 | } 74 | 75 | /** 76 | * Returns the executor object associated with this yield_token object. 77 | */ 78 | executor_type get_executor() noexcept; 79 | 80 | private: 81 | yield_token(Executor const& ex, detail::fiber_context&); 82 | 83 | template 84 | friend class yield_token; 85 | 86 | template 87 | friend struct detail::fiber_main; 88 | 89 | template 90 | friend detail::fiber_context& detail::get_fiber(yield_token& yt); 91 | 92 | detail::fiber_context& ctx_; 93 | Executor executor_; 94 | }; 95 | 96 | /** 97 | * Spawns a new fiber on the provided executor. The fiber will invoke a 98 | * `DECAY_COPY` of F. This function participates in overload resolution if and 99 | * only if E is an Executor. 100 | * 101 | * @param ex the executor that will be associated with the fiber. 102 | * @param f the function object that will be invoked as the fiber's main 103 | * function. 104 | */ 105 | template 106 | auto 107 | spawn(E const& ex, F&& f) -> 108 | typename std::enable_if::value>::type; 109 | 110 | /** 111 | * Spawns a new fiber on the provided ExecutionContext. The fiber will invoke a 112 | * `DECAY_COPY` of F. This function participates in overload resolution if and 113 | * only if ExecutionContext is publicly derived from 114 | * `boost::asio::execution_context`. 115 | * 116 | * @param e the ExecutionContext that will be associated with the fiber. 117 | * @param f the function object that will be invoked as the fiber's main 118 | * function. 119 | */ 120 | template 121 | auto 122 | spawn(Ctx& ctx, F&& f) -> typename std::enable_if< 123 | std::is_convertible::value>::type; 124 | 125 | /** 126 | * Spawns a new fiber on the provided executor. The fiber will invoke a 127 | * `DECAY_COPY` of F. The provided StackAllocator will be used to allocate the 128 | * fiber's stack. Refer to the StackAllocator concept in boost::context for more 129 | * information. 130 | * 131 | * @param arg std::allocator_arg tag to disambiguate overloads. 132 | * @param sa an object that satisfies the requirements of the StackAllocator 133 | * concept. 134 | * @param ex the executor that will be associated with the fiber. 135 | * @param f the function object that will be invoked as the fiber's main 136 | * function. 137 | */ 138 | template 139 | void 140 | spawn(std::allocator_arg_t arg, Alloc&& sa, E const& ex, F&& f); 141 | 142 | } // namespace ufiber 143 | 144 | #include 145 | 146 | #ifndef UFIBER_SEPARATE_COMPILATION 147 | #include 148 | #endif // UFIBER_SEPARATE_COMPILATION 149 | 150 | #endif // UFIBER_UFIBER_HPP 151 | -------------------------------------------------------------------------------- /include/ufiber/detail/ufiber.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018 Damian Jarek (damian dot jarek93 at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/djarek/ufiber 8 | // 9 | 10 | #ifndef UFIBER_DETAIL_UFIBER_HPP 11 | #define UFIBER_DETAIL_UFIBER_HPP 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | namespace ufiber 23 | { 24 | 25 | template 26 | class yield_token; 27 | 28 | struct broken_promise; 29 | 30 | namespace detail 31 | { 32 | 33 | class void_fn_ref; 34 | 35 | [[noreturn]] UFIBER_INLINE_DECL void 36 | throw_broken_promise(); 37 | 38 | class fiber_context 39 | { 40 | public: 41 | struct resumer 42 | { 43 | UFIBER_INLINE_DECL void operator()(void* promise) noexcept; 44 | fiber_context& ctx_; 45 | }; 46 | 47 | UFIBER_INLINE_DECL explicit fiber_context(boost::context::fiber&& f); 48 | fiber_context(fiber_context&&) = delete; 49 | fiber_context(fiber_context const&) = delete; 50 | 51 | fiber_context& operator=(fiber_context&&) = delete; 52 | fiber_context& operator=(fiber_context const&) = delete; 53 | 54 | template 55 | void suspend_with(F&& init) noexcept 56 | { 57 | fiber_ = std::move(fiber_).resume_with( 58 | [this, &init](boost::context::fiber&& f) { 59 | fiber_ = std::move(f); 60 | init(); 61 | return boost::context::fiber{}; 62 | }); 63 | assert(fiber_ && "Expected caller fiber"); 64 | // fiber_ should contain the main thread's stack at this point 65 | } 66 | 67 | UFIBER_INLINE_DECL void resume() noexcept; 68 | 69 | UFIBER_INLINE_DECL boost::context::fiber final_suspend() noexcept; 70 | 71 | private: 72 | boost::context::fiber fiber_; 73 | }; 74 | 75 | template 76 | detail::fiber_context& 77 | get_fiber(yield_token& yt) 78 | { 79 | return yt.ctx_; 80 | } 81 | 82 | UFIBER_INLINE_DECL void 83 | initial_resume(boost::context::fiber&& f); 84 | 85 | enum class result_state 86 | { 87 | broken_promise, 88 | value, 89 | }; 90 | 91 | template 92 | struct select_result 93 | { 94 | using type = std::tuple; 95 | }; 96 | 97 | template 98 | struct select_result 99 | { 100 | using type = Arg; 101 | }; 102 | 103 | template<> 104 | struct select_result<> 105 | { 106 | using type = void; 107 | }; 108 | 109 | template 110 | using result_t = typename select_result::type; 111 | 112 | template 113 | struct promise 114 | { 115 | using result_type = result_t; 116 | 117 | promise() 118 | { 119 | } 120 | 121 | promise(promise&&) = delete; 122 | promise(promise const&) = delete; 123 | promise& operator=(promise&&) = delete; 124 | promise& operator=(promise const&) = delete; 125 | 126 | ~promise() 127 | { 128 | switch (state_) 129 | { 130 | case result_state::broken_promise: 131 | break; 132 | case result_state::value: 133 | result_.~result_type(); 134 | break; 135 | } 136 | } 137 | 138 | template 139 | void set_result(Ts&&... ts) 140 | { 141 | ::new (static_cast(std::addressof(result_))) 142 | result_type(std::forward(ts)...); 143 | state_ = result_state::value; 144 | } 145 | 146 | result_type get_value() 147 | { 148 | switch (state_) 149 | { 150 | case result_state::value: 151 | return std::move(result_); 152 | default: 153 | detail::throw_broken_promise(); 154 | } 155 | } 156 | 157 | private: 158 | result_state state_ = result_state::broken_promise; 159 | union { 160 | result_type result_; 161 | }; 162 | }; 163 | 164 | template<> 165 | struct promise<> 166 | { 167 | promise() = default; 168 | UFIBER_INLINE_DECL void set_result() noexcept; 169 | UFIBER_INLINE_DECL void get_value(); 170 | 171 | private: 172 | result_state state_ = result_state::broken_promise; 173 | }; 174 | 175 | template 176 | struct completion_handler 177 | { 178 | explicit completion_handler(void* promise, 179 | yield_token& yt, 180 | fiber_context& ctx) 181 | : promise_{promise, fiber_context::resumer{ctx}} 182 | , executor_{yt.get_executor()} 183 | { 184 | } 185 | 186 | template 187 | void operator()(Ts&&... ts) 188 | { 189 | static_cast*>(this->promise_.get()) 190 | ->set_result(std::forward(ts)...); 191 | this->promise_.reset(); 192 | } 193 | 194 | std::unique_ptr promise_; 195 | Executor executor_; 196 | }; 197 | 198 | template 199 | struct fiber_main 200 | { 201 | boost::context::fiber operator()(boost::context::fiber&& fiber) 202 | { 203 | fiber_context ctx{std::move(fiber)}; 204 | BOOST_TRY 205 | { 206 | yield_token token{std::move(executor_), ctx}; 207 | boost::asio::post(token); 208 | f_(std::move(token)); 209 | } 210 | BOOST_CATCH(Exception const&) 211 | { 212 | // Ignoring this exception allows an application to cleanup properly 213 | // if there are pending operations when 214 | // execution_context::shutdown() is called. 215 | } 216 | BOOST_CATCH_END 217 | return ctx.final_suspend(); 218 | } 219 | 220 | F f_; 221 | Executor executor_; 222 | }; 223 | 224 | } // namespace detail 225 | } // namespace ufiber 226 | 227 | namespace boost 228 | { 229 | namespace asio 230 | { 231 | 232 | template 233 | class async_result<::ufiber::yield_token, void(Args...)> 234 | { 235 | public: 236 | using completion_handler_type = 237 | ::ufiber::detail::completion_handler; 238 | 239 | using return_type = ::ufiber::detail::result_t; 240 | 241 | template 242 | static return_type initiate(Op&& op, Token&& token, Ts&&... ts) 243 | { 244 | ::ufiber::detail::promise promise; 245 | ::ufiber::detail::fiber_context& ctx = 246 | ::ufiber::detail::get_fiber(token); 247 | completion_handler_type handler{&promise, token, ctx}; 248 | ctx.suspend_with([&]() noexcept { 249 | op(std::move(handler), std::forward(ts)...); 250 | }); 251 | return promise.get_value(); 252 | } 253 | 254 | return_type get() = delete; 255 | }; 256 | 257 | template 258 | class associated_executor< 259 | ::ufiber::detail::completion_handler, 260 | E> 261 | { 262 | public: 263 | using type = Executor; 264 | 265 | static type get( 266 | ::ufiber::detail::completion_handler const& h, 267 | E const& = E()) 268 | { 269 | return h.executor_; 270 | } 271 | }; 272 | 273 | } // asio 274 | 275 | } // boost 276 | 277 | #endif // UFIBER_DETAIL_UFIBER_HPP 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # μfiber 2 | 3 | Language|Build | Coverage| License | 4 | |-------|------|---------|---------| 5 | |[![Standard](https://img.shields.io/badge/C%2B%2B-11-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) | ![hosted-latest-vcpkg](https://github.com/djarek/ufiber/workflows/hosted-latest-vcpkg/badge.svg)| [![codecov](https://codecov.io/gh/djarek/ufiber/branch/master/graph/badge.svg)](https://codecov.io/gh/djarek/ufiber) | [![License](https://img.shields.io/badge/license-BSL%201.0-blue.svg)](https://opensource.org/licenses/BSL-1.0) 6 | 7 | 8 | ## Introduction 9 | **μfiber** is a minimalistic, header-only fiber library compatible with the 10 | asynchronous operation model specified in the C++ Networking TS and implemented 11 | in Boost.ASIO. It implements an interface similar to `boost::asio::spawn()`, but 12 | takes advantage of C++11 move semantics to avoid additional memory allocations 13 | that are necessary in `boost::asio::spawn()` and supports more than 2 results of 14 | an asynchronous operation. Additionally, this library does not use deprecated 15 | Boost libraries, so it generates no deprecation warnings when used. 16 | 17 | ## Dependencies 18 | **μfiber** depends on: 19 | - Boost.Context 20 | - Boost.ASIO 21 | 22 | ## Installation 23 | **μfiber** is header-only, so you only need to add the include directory to the 24 | include paths in your build system. An `install` target is available in CMake 25 | which will install the headers and a CMake `find_package` configuration script 26 | for easy consumption in projects built with CMake: 27 | ```bash 28 | mkdir build 29 | cd build 30 | cmake .. 31 | make install 32 | ``` 33 | 34 | After installation, a project built with CMake can consume the library using 35 | `find_package`: 36 | ```cmake 37 | find_package(ufiber REQUIRED) 38 | target_link_libraries(my_target PUBLIC ufiber::ufiber) 39 | ``` 40 | 41 | ## Examples 42 | - [Echo](https://github.com/djarek/ufiber/blob/master/examples/echo.cpp) - a 43 | well documented, echo server that uses fibers to accept clients and then 44 | sends back octets to the client. 45 | 46 | 47 | ## API 48 | ### `yield_token` 49 | ```c++ 50 | template 51 | class yield_token 52 | { 53 | public: 54 | using executor_type = Executor; 55 | 56 | executor_type get_executor() noexcept; 57 | }; 58 | 59 | struct broken_promise final : std::exception 60 | { 61 | char const* what() const noexcept final; 62 | }; 63 | 64 | ``` 65 | `yield_token` is a lightweight handle to the currently running fiber, which may 66 | be used as a `CompletionToken` in an asynchronous initiation function. The 67 | behavior of a `yield_token`, outside the fiber it was created on, is undefined. 68 | `Executor` shall satisfy the constraints of 69 | [Executor](https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/Executor1.html). 70 | The executor on which the current fiber runs can be retrieved via 71 | `get_executor()`. 72 | 73 | When `yield_token` is used as a 74 | [CompletionToken](https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/asynchronous_operations.html), 75 | the asynchronous initiation function that it is passed to as an argument will 76 | suspend execution of the current fiber. Once the operation completes, the fiber 77 | is resumed and the async function will return the results of the operation as 78 | its return value. The return type depends on the number of results: 79 | - 0 results: returns `void` 80 | - 1 result: returns `T` 81 | - N results: returns `std::tuple` 82 | 83 | -------------------------- 84 | 85 | ### Exceptions 86 | If an asynchronous operation has been abandoned, the fiber will be resumed but 87 | the asynchronous initiation function will throw `ufiber::broken_promise`. This 88 | happens if an `io_context` is destroyed with pending asynchronous operations. If 89 | exceptions are disabled, the user will need to provide their own definition of 90 | `[[noreturn]] ufiber::detail::throw_broken_promise()`. Note that, if an 91 | operation has been abandoned, the fiber may not be running within the context of 92 | its executor, so catching `ufiber::broken_promise` is not recommended. 93 | 94 | Any exception (except for `ufiber::broken_promise`) that excapes the fiber's 95 | main function, will result in a call to `std::terminate()` (same behavior as in 96 | `std::thread`). 97 | 98 | -------------------------- 99 | 100 | ### Spawning fibers 101 | Fibers are created by calling one of the provided overloads of the `spawn()` 102 | function. Fibers are scheduled on the executor they are spawned on, migration 103 | between threads is managed by the executor. Note that when using this library 104 | with executors provided by ASIO, one needs to either avoid using TLS 105 | variables or ensure that the executor cannot schedule the fiber on another 106 | thread. 107 | 108 | 1) Spawn a fiber on `ex` with the function object `f` as its main function. The 109 | implementation will construct an object from `f` via `DECAY_COPY`. This function 110 | participates in overload resolution if and only if, `E` satisfies the 111 | constraints of 112 | [Executor](https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/Executor1.html): 113 | 114 | ```c++ 115 | template 116 | auto 117 | spawn(E const& ex, F&& f) -> /* SFINAE */ void; 118 | ``` 119 | 120 | Requires: 121 | - `F` shall be invokable with the signature `void(yield_token)` 122 | 123 | Example usage: 124 | ```c++ 125 | boost::asio::io_context io; 126 | boost::asio::strand strand{io.get_executor()}; 127 | ufiber::spawn( 128 | strand, 129 | [](ufiber::yield_token yield) 130 | { 131 | // The fiber will run on the strand 132 | boost::asio::steady_timer timer{yield.get_executor().context()}; 133 | timer.expires_from_now(std::chrono::seconds{1}); 134 | auto ec = timer.async_wait(yield); 135 | // Handle the timer expiration 136 | }); 137 | ``` 138 | 139 | -------------------------- 140 | 141 | 2) Spawn a fiber on `ctx`'s executor with the function object `f` as its main 142 | function. The implementation will construct an object from `f` via `DECAY_COPY`. 143 | This function participates in overload resolution if and only if, `Ctx` 144 | satisfies the constraints of 145 | [ExecutionContext](https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/ExecutionContext.html): 146 | 147 | ```c++ 148 | template 149 | auto 150 | spawn(Ctx& ctx, F&& f) -> /* SFINAE */ void; 151 | ``` 152 | 153 | Requires: 154 | - `F` shall be invokable with the signature 155 | `void(yield_token)`. 156 | 157 | Example usage: 158 | ```c++ 159 | boost::asio::io_context io; 160 | 161 | ufiber::spawn( 162 | io, 163 | [](auto yield) 164 | { 165 | boost::asio::steady_timer timer{yield.get_executor().context()}; 166 | timer.expires_from_now(std::chrono::seconds{1}); 167 | auto ec = timer.async_wait(yield); 168 | // Handle the timer expiration 169 | }); 170 | ``` 171 | 172 | -------------------------- 173 | 174 | 2) Spawn a fiber on `ex` with the function object `f` as its main 175 | function, `sa` will be used to allocate the fiber's stack. The implementation will construct an object from `f` via 176 | `DECAY_COPY`: 177 | 178 | ```c++ 179 | template 180 | void 181 | spawn(std::allocator_arg_t arg, StackAllocator&& sa, E const& ex, F&& f); 182 | ``` 183 | 184 | Requires: 185 | - `E` shall satisfy the constraints of 186 | [Executor](https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/Executor1.html). 187 | - `F` shall be invokable with the signature `void(yield_token)`. 188 | - `Alloc` shall satisfy the constraints of 189 | [StackAllocator](https://www.boost.org/doc/libs/1_69_0/libs/context/doc/html/context/stack.html). 190 | 191 | Example usage: 192 | ```c++ 193 | boost::asio::io_context io; 194 | 195 | ufiber::spawn( 196 | std::allocator_arg, 197 | boost::context::protected_fixedsize{PTHREAD_STACK_MIN}, 198 | io.get_executor(), 199 | [](auto yield) 200 | { 201 | boost::asio::signal_set set{yield.get_executor().context()}; 202 | set.add(SIGINT); 203 | set.add(SIGHUP); 204 | auto [ec, sig_num] = set.async_wait(yield); 205 | // Handle the signal 206 | }); 207 | ``` 208 | 209 | ## License 210 | Distributed under the Boost Software License, Version 1.0. (See accompanying 211 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 212 | --------------------------------------------------------------------------------