├── vcpkg-configuration.json ├── overlays └── liburing │ ├── portfile.cmake │ └── vcpkg.json ├── .gitignore ├── src ├── detail │ ├── buffers_adaptor.hpp │ └── awaitable_base.hpp ├── buffers_adaptor.cpp ├── awaitable_base.cpp ├── ip.cpp ├── buffer.cpp ├── dns.cpp ├── common.cpp ├── time.cpp ├── io_context.cpp └── file.cpp ├── test ├── cmake_subdir_test │ ├── CMakeLists.txt │ ├── vcpkg.json │ └── main.cpp ├── tls │ └── botan │ │ ├── server.csr │ │ ├── server.crt.pem │ │ ├── ca.crt.pem │ │ ├── ca.key.pem │ │ └── server.key.pem ├── CMakeLists.txt ├── file_test.cpp ├── ipv6_test.cpp ├── helpers.hpp ├── waker_test.cpp ├── dns_test.cpp ├── timer_test.cpp ├── post_test.cpp └── buffer_test.cpp ├── vcpkg.json ├── benches ├── echo │ ├── echo.cpp │ ├── common.hpp │ ├── fiona.cpp │ └── asio.cpp ├── post │ ├── post.cpp │ ├── common.hpp │ ├── fiona.cpp │ └── asio.cpp ├── recv │ ├── recv.cpp │ ├── common.hpp │ ├── fiona.cpp │ └── asio.cpp ├── timer │ ├── timer.cpp │ ├── common.hpp │ ├── fiona.cpp │ └── asio.cpp └── CMakeLists.txt ├── include └── fiona │ ├── params.hpp │ ├── detail │ ├── time.hpp │ ├── get_sqe.hpp │ └── common.hpp │ ├── ip.hpp │ ├── error.hpp │ ├── io_context.hpp │ ├── dns.hpp │ ├── time.hpp │ ├── tls.hpp │ ├── file.hpp │ ├── task.hpp │ ├── tcp.hpp │ └── buffer.hpp ├── .clang-format ├── cmake ├── fiona-config.cmake.in └── Findliburing.cmake ├── LICENSE.txt ├── CMakeLists.txt └── README.md /vcpkg-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "overlay-ports": [] 3 | } 4 | -------------------------------------------------------------------------------- /overlays/liburing/portfile.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_POLICY_EMPTY_PACKAGE enabled) 2 | -------------------------------------------------------------------------------- /overlays/liburing/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liburing", 3 | "version": "2.6", 4 | "port-version": 0 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __build__/ 3 | toolchain.cmake 4 | *.sh 5 | __ci_build__/ 6 | .cache 7 | compile_commands.json 8 | .clangd 9 | -------------------------------------------------------------------------------- /src/detail/buffers_adaptor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | namespace fiona { 8 | } // namespace fiona 9 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(fiona_subdir_test LANGUAGES CXX) 3 | 4 | add_subdirectory(../.. fiona) 5 | 6 | add_executable(main main.cpp) 7 | target_link_libraries(main PRIVATE Fiona::fiona) 8 | 9 | include(CTest) 10 | add_test(NAME main COMMAND main) 11 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "boost-asio", 4 | "boost-assert", 5 | "boost-beast", 6 | "boost-config", 7 | "boost-container-hash", 8 | "boost-core", 9 | "boost-smart-ptr", 10 | "boost-system", 11 | "boost-unordered", 12 | "botan", 13 | "catch2", 14 | "liburing" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "boost-asio", 4 | "boost-assert", 5 | "boost-beast", 6 | "boost-config", 7 | "boost-container-hash", 8 | "boost-core", 9 | "boost-smart-ptr", 10 | "boost-system", 11 | "boost-unordered", 12 | "catch2", 13 | "liburing" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /benches/echo/echo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | TEST_CASE( "asio echo bench" ) { asio_echo_bench(); } 8 | TEST_CASE( "fiona echo bench" ) { fiona_echo_bench(); } 9 | -------------------------------------------------------------------------------- /benches/post/post.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | TEST_CASE( "asio post bench" ) { asio_post_bench(); } 8 | TEST_CASE( "fiona post bench" ) { fiona_post_bench(); } 9 | -------------------------------------------------------------------------------- /benches/recv/recv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | TEST_CASE( "asio recv bench" ) { asio_recv_bench(); } 8 | TEST_CASE( "fiona recv bench" ) { fiona_recv_bench(); } 9 | -------------------------------------------------------------------------------- /src/buffers_adaptor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "detail/buffers_adaptor.hpp" 6 | 7 | namespace fiona { 8 | namespace detail { 9 | } // namespace detail 10 | } // namespace fiona 11 | -------------------------------------------------------------------------------- /benches/timer/timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | void asio_timer_bench(); 8 | 9 | void fiona_timer_bench(); 10 | 11 | TEST_CASE( "asio timer bench" ) { asio_timer_bench(); } 12 | TEST_CASE( "fiona timer bench" ) { fiona_timer_bench(); } 13 | -------------------------------------------------------------------------------- /src/awaitable_base.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "detail/awaitable_base.hpp" 6 | 7 | namespace fiona { 8 | namespace detail { 9 | 10 | ref_count::~ref_count() = default; 11 | awaitable_base::~awaitable_base() = default; 12 | 13 | } // namespace detail 14 | } // namespace fiona 15 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int 7 | main() 8 | { 9 | fiona::io_context ioc; 10 | fiona::timer timer( ioc.get_executor() ); 11 | 12 | ioc.post( []( fiona::timer timer ) -> fiona::task 13 | { 14 | auto mok = co_await timer.async_wait( std::chrono::milliseconds{ 250 } ); 15 | (void)mok.value(); 16 | co_return; 17 | }( timer ) ); 18 | 19 | ioc.run(); 20 | } 21 | -------------------------------------------------------------------------------- /benches/timer/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_BENCHES_TIMER_COMMON_HPP 6 | #define FIONA_BENCHES_TIMER_COMMON_HPP 7 | 8 | #include 9 | #include 10 | 11 | void fiona_timer_bench(); 12 | 13 | void asio_timer_bench(); 14 | 15 | #endif // FIONA_BENCHES_TIMER_COMMON_HPP 16 | -------------------------------------------------------------------------------- /include/fiona/params.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_PARAMS_HPP 6 | #define FIONA_PARAMS_HPP 7 | 8 | #include 9 | 10 | namespace fiona { 11 | struct io_context_params 12 | { 13 | std::uint32_t sq_entries = 4096; 14 | std::uint32_t cq_entries = 4096; 15 | std::uint32_t num_files = 1024; 16 | }; 17 | } // namespace fiona 18 | 19 | #endif // FIONA_PARAMS_HPP 20 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AlwaysBreakAfterReturnType: AllDefinitions 2 | PointerAlignment: Left 3 | AlwaysBreakTemplateDeclarations: Yes 4 | SpacesInParentheses: true 5 | LambdaBodyIndentation: OuterScope 6 | ColumnLimit: 80 7 | BinPackArguments: true 8 | BinPackParameters: false 9 | AllowAllArgumentsOnNextLine: true 10 | AlignTrailingComments: 11 | Kind: Always 12 | OverEmptyLines: 2 13 | BreakBeforeBraces: Custom 14 | BraceWrapping: 15 | AfterStruct: true 16 | AfterClass: true 17 | AfterFunction: true 18 | BeforeLambdaBody: true 19 | QualifierAlignment: Right 20 | -------------------------------------------------------------------------------- /benches/post/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_BENCHES_POST_COMMON_HPP 6 | #define FIONA_BENCHES_POST_COMMON_HPP 7 | 8 | #include 9 | 10 | inline constexpr int const num_threads = 8; 11 | inline constexpr int const num_tasks = 250'000; 12 | inline constexpr int const total_runs = num_threads * num_tasks; 13 | 14 | void fiona_post_bench(); 15 | 16 | void asio_post_bench(); 17 | 18 | #endif // FIONA_BENCHES_POST_COMMON_HPP 19 | -------------------------------------------------------------------------------- /cmake/fiona-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | set(FIONA_REQUIRED "") 6 | if("${${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED}") 7 | set(FIONA_REQUIRED REQUIRED) 8 | endif() 9 | set(FIONA_QUIET "") 10 | if("${${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY}") 11 | set(FIONA_QUIET QUIET) 12 | endif() 13 | 14 | find_dependency(PkgConfig) 15 | if(PKG_CONFIG_FOUND) 16 | pkg_check_modules(uring ${FIONA_REQUIRED} ${FIONA_QUIET} IMPORTED_TARGET liburing) 17 | endif() 18 | 19 | find_dependency( 20 | Boost 1.84 21 | ${FIONA_REQUIRED} 22 | ${FIONA_QUIET} 23 | COMPONENTS 24 | assert 25 | config 26 | container_hash 27 | core 28 | smart_ptr 29 | system 30 | unordered 31 | ) 32 | 33 | find_dependency(Botan 3.3.0 CONFIG COMPONENTS tls) 34 | 35 | include(${CMAKE_CURRENT_LIST_DIR}/fiona-targets.cmake) 36 | -------------------------------------------------------------------------------- /include/fiona/detail/time.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_DETAIL_TIME_HPP 6 | #define FIONA_DETAIL_TIME_HPP 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace fiona { 13 | namespace detail { 14 | 15 | template 16 | __kernel_timespec 17 | duration_to_timespec( std::chrono::duration const& d ) 18 | { 19 | auto sec = std::chrono::floor( d ); 20 | auto nsec = std::chrono::duration_cast( d - sec ); 21 | __kernel_timespec ts; 22 | ts.tv_sec = sec.count(); 23 | ts.tv_nsec = nsec.count(); 24 | return ts; 25 | } 26 | 27 | } // namespace detail 28 | } // namespace fiona 29 | 30 | #endif // FIONA_DETAIL_TIME_HPP 31 | -------------------------------------------------------------------------------- /benches/echo/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_BENCHES_ECHO_COMMON_HPP 6 | #define FIONA_BENCHES_ECHO_COMMON_HPP 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | inline constexpr char const* localhost_ipv4 = "127.0.0.1"; 17 | 18 | constexpr int num_clients = 5000; 19 | constexpr int num_msgs = 1000; 20 | 21 | using lock_guard = std::lock_guard; 22 | 23 | inline lock_guard 24 | guard() 25 | { 26 | static std::mutex mtx; 27 | return lock_guard( mtx ); 28 | } 29 | 30 | void fiona_echo_bench(); 31 | 32 | void asio_echo_bench(); 33 | 34 | #endif // FIONA_BENCHES_ECHO_COMMON_HPP 35 | -------------------------------------------------------------------------------- /include/fiona/ip.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_IP_HPP 6 | #define FIONA_IP_HPP 7 | 8 | #include // for uint16_t 9 | #include 10 | 11 | #include // for sockaddr_in, sockaddr_in6 12 | 13 | #include 14 | 15 | namespace fiona { 16 | namespace ip { 17 | 18 | sockaddr_in FIONA_EXPORT make_sockaddr_ipv4( char const* ipv4_addr, 19 | std::uint16_t const port ); 20 | 21 | sockaddr_in6 FIONA_EXPORT make_sockaddr_ipv6( char const* ipv6_addr, 22 | std::uint16_t const port ); 23 | 24 | std::string FIONA_EXPORT to_string( sockaddr_in const* p_ipv4_addr ); 25 | 26 | } // namespace ip 27 | } // namespace fiona 28 | 29 | #endif // FIONA_IP_HPP 30 | -------------------------------------------------------------------------------- /cmake/Findliburing.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | pkg_check_modules(PC_liburing REQUIRED liburing) 3 | 4 | find_path(liburing_INCLUDE_DIR 5 | NAMES liburing.h 6 | PATHS ${PC_liburing_INCLUDE_DIRS} 7 | ) 8 | find_library(liburing_LIBRARY 9 | NAMES uring 10 | PATHS ${PC_liburing_LIBRARY_DIRS} 11 | ) 12 | 13 | set(liburing_VERSION ${PC_liburing_VERSION}) 14 | 15 | include(FindPackageHandleStandardArgs) 16 | find_package_handle_standard_args(liburing 17 | FOUND_VAR liburing_FOUND 18 | REQUIRED_VARS 19 | liburing_LIBRARY 20 | liburing_INCLUDE_DIR 21 | VERSION_VAR liburing_VERSION 22 | ) 23 | 24 | if(liburing_FOUND AND NOT TARGET liburing::liburing) 25 | add_library(liburing::liburing UNKNOWN IMPORTED) 26 | set_target_properties(liburing::liburing PROPERTIES 27 | IMPORTED_LOCATION "${liburing_LIBRARY}" 28 | INTERFACE_INCLUDE_DIRECTORIES "${liburing_INCLUDE_DIR}" 29 | ) 30 | endif() 31 | 32 | mark_as_advanced( 33 | liburing_INCLUDE_DIR 34 | liburing_LIBRARY 35 | ) 36 | -------------------------------------------------------------------------------- /benches/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Christian Mazakas 2 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | # file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | find_package( 6 | Boost 1.84 7 | REQUIRED 8 | COMPONENTS 9 | asio 10 | beast 11 | ) 12 | 13 | find_package(Catch2 3.0 REQUIRED) 14 | 15 | include(Catch) 16 | 17 | function(add_bench_deps tgt) 18 | target_link_libraries( 19 | ${tgt} 20 | PRIVATE 21 | Fiona::core 22 | Boost::asio 23 | Boost::beast 24 | Catch2::Catch2 Catch2::Catch2WithMain) 25 | endfunction() 26 | 27 | add_executable(echo echo/common.hpp echo/echo.cpp echo/asio.cpp echo/fiona.cpp) 28 | add_bench_deps(echo) 29 | 30 | add_executable(timer timer/common.hpp timer/timer.cpp timer/asio.cpp timer/fiona.cpp) 31 | add_bench_deps(timer) 32 | 33 | add_executable(post post/common.hpp post/post.cpp post/asio.cpp post/fiona.cpp) 34 | add_bench_deps(post) 35 | 36 | add_executable(recv recv/common.hpp recv/recv.cpp recv/asio.cpp recv/fiona.cpp) 37 | add_bench_deps(recv) 38 | -------------------------------------------------------------------------------- /include/fiona/detail/get_sqe.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_DETAIL_GET_SQE_HPP 6 | #define FIONA_DETAIL_GET_SQE_HPP 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace fiona { 13 | namespace detail { 14 | 15 | inline void 16 | submit_ring( io_uring* ring ) 17 | { 18 | io_uring_submit_and_get_events( ring ); 19 | } 20 | 21 | [[nodiscard]] inline io_uring_sqe* 22 | get_sqe( io_uring* ring ) 23 | { 24 | auto sqe = io_uring_get_sqe( ring ); 25 | if ( sqe ) { 26 | return sqe; 27 | } 28 | submit_ring( ring ); 29 | sqe = io_uring_get_sqe( ring ); 30 | BOOST_ASSERT( sqe ); 31 | return sqe; 32 | } 33 | 34 | inline void 35 | reserve_sqes( io_uring* ring, unsigned n ) 36 | { 37 | auto r = io_uring_sq_space_left( ring ); 38 | if ( r < n ) { 39 | submit_ring( ring ); 40 | } 41 | } 42 | 43 | } // namespace detail 44 | } // namespace fiona 45 | 46 | #endif // FIONA_DETAIL_GET_SQE_HPP 47 | -------------------------------------------------------------------------------- /benches/timer/fiona.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using namespace std::chrono_literals; 15 | 16 | void 17 | fiona_timer_bench() 18 | { 19 | static std::atomic_int anums = 0; 20 | 21 | fiona::io_context_params params; 22 | params.cq_entries = 16 * 1024; 23 | params.sq_entries = 16 * 1024; 24 | 25 | fiona::io_context ioc( params ); 26 | auto ex = ioc.get_executor(); 27 | 28 | for ( int i = 0; i < 10'000; ++i ) { 29 | ex.spawn( []( fiona::executor ex ) -> fiona::task 30 | { 31 | fiona::timer timer( ex ); 32 | for ( int i = 0; i < 10'000; ++i ) { 33 | auto mokay = co_await timer.async_wait( 1ms ); 34 | CHECK( mokay.has_value() ); 35 | anums.fetch_add( 1, std::memory_order_relaxed ); 36 | } 37 | co_return; 38 | }( ex ) ); 39 | } 40 | ioc.run(); 41 | 42 | CHECK( anums == 10'000 * 10'000 ); 43 | } 44 | -------------------------------------------------------------------------------- /src/detail/awaitable_base.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_DETAIL_AWAITABLE_BASE_HPP 6 | #define FIONA_DETAIL_AWAITABLE_BASE_HPP 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | struct io_uring_cqe; 15 | 16 | namespace fiona { 17 | namespace detail { 18 | 19 | struct FIONA_EXPORT ref_count 20 | { 21 | int count_ = 0; 22 | virtual ~ref_count(); 23 | 24 | int 25 | use_count() const noexcept 26 | { 27 | return count_; 28 | } 29 | }; 30 | 31 | inline void 32 | intrusive_ptr_add_ref( ref_count* prc ) noexcept 33 | { 34 | ++prc->count_; 35 | } 36 | 37 | inline void 38 | intrusive_ptr_release( ref_count* p_rc ) 39 | { 40 | if ( --p_rc->count_ == 0 ) { 41 | delete p_rc; 42 | } 43 | } 44 | 45 | struct FIONA_EXPORT awaitable_base : public virtual ref_count 46 | { 47 | virtual ~awaitable_base() override; 48 | virtual void await_process_cqe( io_uring_cqe* cqe ) = 0; 49 | virtual std::coroutine_handle<> handle() noexcept = 0; 50 | }; 51 | 52 | } // namespace detail 53 | } // namespace fiona 54 | 55 | #endif // FIONA_DETAIL_AWAITABLE_BASE_HPP 56 | -------------------------------------------------------------------------------- /benches/recv/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_BENCHES_ECHO_COMMON_HPP 6 | #define FIONA_BENCHES_ECHO_COMMON_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | inline constexpr char const* localhost_ipv4 = "127.0.0.1"; 19 | 20 | constexpr int num_clients = 500; 21 | constexpr int num_msgs = 1; 22 | constexpr int msg_size = 6 * 1024 * 1024; 23 | 24 | void fiona_recv_bench(); 25 | void asio_recv_bench(); 26 | 27 | inline std::vector 28 | make_random_input( std::size_t n ) 29 | { 30 | std::vector v( n, 0 ); 31 | auto rng = Catch::Generators::random( 0, 255 ); 32 | for ( auto& b : v ) { 33 | b = static_cast( rng.get() ); 34 | } 35 | return v; 36 | } 37 | 38 | using lock_guard = std::lock_guard; 39 | 40 | inline lock_guard 41 | guard() 42 | { 43 | static std::mutex mtx; 44 | return lock_guard( mtx ); 45 | } 46 | 47 | #endif // FIONA_BENCHES_ECHO_COMMON_HPP 48 | -------------------------------------------------------------------------------- /test/tls/botan/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIDczCCAd0CAQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBojANBgkqhkiG9w0B 3 | AQEFAAOCAY8AMIIBigKCAYEAsPZdkwq6yUERQv77jOI7J7Y1YUM/KsDq4+y/ye/4 4 | FFyO3GD5mkXbuTfjjc/wTCYAuRpuQ+rE4649NdOAo4gbEk/yzdT5zOxfS/sfOF4/ 5 | wJt/yZjPKLjJL4P/Lmyli6NXuJTjXybvMYTCfoyYT7gYc3VTjbvZrU57i6arTo1o 6 | TM3yMq9UX4CXRb1h+/hrLEY/czaeObSwpK0I/jgxwHhgEjUelADgmYrE08w4yx/L 7 | l6WV63LVZocwGWOla/9zePm8v/sphd/7MWBeSgLuGHeE5rp/aSzSecv0SOUWmX0/ 8 | LHzMlqdGkUgD+fcH/jC3yX4O7ZIu16IyJ51EPtN4cNEmyob0o6WLOMCoHOjmvVWW 9 | r0WZMIqZU8DM9ZO70h3afORyKFGKa/KFgpAf5Xhohs3ROw498j/7JTNwJpVRD/OS 10 | 1AFPBPpcshVArrUXH6fY81dK/WJFY1JB+t0VjytE5zfdkD1lTxlzJxLVjXRhdGy2 11 | q+Xt18xdxu0bZz51rzS5kJ19AgMBAAGgHDAaBgkqhkiG9w0BCQ4xDTALMAkGA1Ud 12 | EwQCMAAwCwYJKoZIhvcNAQELA4IBgQB0gfyuTsDQ0Ldv1dbRKjDslNJi+DKIY6UB 13 | VZ0+5As7tdZtR3vLwmwMwHOWyOtg4yJmmuII7VoJ+1ahcSuR8J7OwNWZG0BeFuaR 14 | 3f/Njq0gBK9Xa8p3IC+whHyX9/ohvSahnwZHMtuSDIYyspOVfwZXTuwYpQw6hVuS 15 | mQMoVo4yGSNziN0fX2pBJxvzjTcEKydSgvF5inpHPX5ZW7UYv9MhiIkLE2pBXR1Y 16 | BXWUkbcUJToaibNpRCF2/hHdkJYOe9Nixl7zMPHLxjrPLBqKpZm2v8llQ9175VuQ 17 | zbHB5bE52QL6Vx0Om1XXPEmoZSUng5ZJhQGYnvAe7q6UZcLtQ10pGvcZVSahN3NK 18 | qtmRe9Si44A8Dkdb5un7lP6uXcjkyohGTn0Pij8jFLQRxFcusfHvfh55x24u8D6E 19 | NonLEpcqXFf9LK14nbcfXXqhuSjA3TeKnk0dyyJqIUgWTS4e59KxJjUqCQOxokJ+ 20 | erotoX/AfWetYSnK7OIUG9rfrK2MshQ= 21 | -----END CERTIFICATE REQUEST----- 22 | -------------------------------------------------------------------------------- /LICENSE.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 | -------------------------------------------------------------------------------- /benches/post/fiona.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "common.hpp" 16 | 17 | using namespace std::chrono_literals; 18 | 19 | void 20 | fiona_post_bench() 21 | { 22 | fiona::io_context ioc; 23 | std::vector threads; 24 | 25 | static int num_runs = 0; 26 | 27 | auto ex = ioc.get_executor(); 28 | fiona::timer timer( ex ); 29 | 30 | auto task = []( fiona::timer& timer ) -> fiona::task 31 | { 32 | ++num_runs; 33 | if ( num_runs == total_runs ) { 34 | co_await timer.async_cancel(); 35 | } 36 | co_return; 37 | }; 38 | 39 | ex.spawn( []( fiona::timer timer ) -> fiona::task 40 | { 41 | co_await timer.async_wait( 100s ); 42 | co_return; 43 | }( timer ) ); 44 | 45 | for ( int i = 0; i < num_threads; ++i ) { 46 | threads.emplace_back( [ex, task, &timer] 47 | { 48 | for ( int j = 0; j < num_tasks; ++j ) { 49 | ex.post( task( timer ) ); 50 | } 51 | } ); 52 | } 53 | 54 | ioc.run(); 55 | CHECK( num_runs == num_threads * num_tasks ); 56 | } 57 | -------------------------------------------------------------------------------- /include/fiona/error.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_ERROR_HPP 6 | #define FIONA_ERROR_HPP 7 | 8 | #include // for BOOST_ASSERT 9 | #include 10 | #include // for result 11 | 12 | #include // for strerror 13 | #include // for operator<<, basic_ostream:... 14 | #include // for make_error_code, operator<< 15 | 16 | namespace fiona { 17 | 18 | struct error_code : public std::error_code 19 | { 20 | inline friend std::ostream& 21 | operator<<( std::ostream& os, error_code const& ec ) 22 | { 23 | os << static_cast( ec ) << ":" 24 | << std::strerror( ec.value() ); 25 | return os; 26 | } 27 | 28 | static error_code 29 | from_errno( int e ) 30 | { 31 | BOOST_ASSERT( e != 0 ); 32 | return { std::make_error_code( static_cast( e ) ) }; 33 | } 34 | }; 35 | 36 | namespace detail { 37 | 38 | BOOST_NOINLINE BOOST_NORETURN inline void 39 | throw_errno_as_error_code( int e ) 40 | { 41 | throw std::system_error( error_code::from_errno( e ) ); 42 | } 43 | 44 | } // namespace detail 45 | 46 | template 47 | using result = boost::system::result; 48 | 49 | } // namespace fiona 50 | 51 | #endif // FIONA_ERROR_HPP 52 | -------------------------------------------------------------------------------- /test/tls/botan/server.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEBzCCAnGgAwIBAgIRAIw4tgThKNd8BPTlgPzTmdQwCwYJKoZIhvcNAQELMBQx 3 | EjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0yNDAxMjMyMTQ4NTdaFw0yNTAxMjIyMTQ4 4 | NTdaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGP 5 | ADCCAYoCggGBALD2XZMKuslBEUL++4ziOye2NWFDPyrA6uPsv8nv+BRcjtxg+ZpF 6 | 27k3443P8EwmALkabkPqxOOuPTXTgKOIGxJP8s3U+czsX0v7HzheP8Cbf8mYzyi4 7 | yS+D/y5spYujV7iU418m7zGEwn6MmE+4GHN1U4272a1Oe4umq06NaEzN8jKvVF+A 8 | l0W9Yfv4ayxGP3M2njm0sKStCP44McB4YBI1HpQA4JmKxNPMOMsfy5ellety1WaH 9 | MBljpWv/c3j5vL/7KYXf+zFgXkoC7hh3hOa6f2ks0nnL9EjlFpl9Pyx8zJanRpFI 10 | A/n3B/4wt8l+Du2SLteiMiedRD7TeHDRJsqG9KOlizjAqBzo5r1Vlq9FmTCKmVPA 11 | zPWTu9Id2nzkcihRimvyhYKQH+V4aIbN0TsOPfI/+yUzcCaVUQ/zktQBTwT6XLIV 12 | QK61Fx+n2PNXSv1iRWNSQfrdFY8rROc33ZA9ZU8ZcycS1Y10YXRstqvl7dfMXcbt 13 | G2c+da80uZCdfQIDAQABo1gwVjAhBgNVHQ4EGgQY2d01jxMqVb9x0XT6QDNB4vVo 14 | +KZasDJiMAwGA1UdEwEB/wQCMAAwIwYDVR0jBBwwGoAYPLflSxVa9lSdNRoMJUd5 15 | 05uTcT80+rGqMAsGCSqGSIb3DQEBCwOCAYEAdxiT2bWCBOTR69uL0j9hSnZAwpwl 16 | r6/dckP+7E4Y7j1rEv2KWvAwLpvKTsRP/fvNjUyHd7jee604cibeopNaeUJkP4at 17 | V64+JZaqm2tYrVHGhKfRbPailnPn0jsLA+xBk95xphNhc6SX8ucimWvGZx5mzk2W 18 | 5lLvae1nmJV5KEv9t1lQRj1MRf1cAbou2/7yxal9WXZY9fYoSQ305oAeb5hq5UXj 19 | hUrkok35B43muxnUWdUq2MoYK0E6z4Il7Pfwq5MLYdcVZ5WImJHJ9zo7vdNeDso0 20 | jtH7xPzqbHzkj3nxat7lEFFL70azvvY2CXYixjvPYzP0VlzT292ut2mCe9eqMmBN 21 | 5Y39Vet9VEst+dVtIAL7FUaAafR9XcCHLW9QlKM9TnVJt+U7hD+tESLRodTLr3qR 22 | hYjUUdXr+a22lYRW2xIOQmVF5x8GjNdSmWEJF0jU6uasKW0qDSlkMFhSifNATz84 23 | 7KOlp7sawRYmNNEi9QtEUpux8Lrvdf+uK6Wz 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /test/tls/botan/ca.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEHTCCAoegAwIBAgIRAIIZpMpFgcjs0jG5Ls3NQoMwCwYJKoZIhvcNAQELMBQx 3 | EjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0yNDAxMjMyMTQ3NDZaFw0yNTAxMjIyMTQ3 4 | NDZaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGP 5 | ADCCAYoCggGBAJgCZ2rTVMj1ZrfLppFa1XHG5tv++tdQp+JYk357Nd8ry1xLa9aE 6 | UauzZWx++mU0+dL923QEhxAAObmw6ZGcZfJ4vLN1RNTUyGxx0dDENhY1li6HgjUW 7 | IsXX63NhGz+20kJY2zVWlFNFUmocYacfe7k2Yar3MnbMJgliel8sFjMPqypMSO67 8 | AdsCK/zrJPVXXpDZ6sl2s3gQbj/xOxyHp+3NAOhhnF7yOeOx8pUt1tt6KnxQGMhn 9 | yZPzH+bXaEDC6hKdnDFbEXqJuKIkKDF+YpUvfvcbl5H47rG01H0TcXg6ZddHY5Rm 10 | O31K1Cxy1toK+r0boG3qTVAI5LV2f65RwCvgf7bS/LtuPg6H64bLHoGTQO9dxg8m 11 | j9y5NcrwlcOelRbzA6jVTKKHp5drOObM9T0iqRYhZAMSNEyXVr96dq8sevYU1FPg 12 | AVV4KdhTkS160mmDIlpOiGL+hc5Re3qBhbt5xXtIqOWGQHp8yyTN9eAPeXVdt/8B 13 | Z2O9wAqztF7QiQIDAQABo24wbDAhBgNVHQ4EGgQYPLflSxVa9lSdNRoMJUd505uT 14 | cT80+rGqMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMCMGA1Ud 15 | IwQcMBqAGDy35UsVWvZUnTUaDCVHedObk3E/NPqxqjALBgkqhkiG9w0BAQsDggGB 16 | AGRLrv5erc86h4UmN8uBazekKa/nrFubTuBs22v/AYTkrLiiA9y6K27Vbmnn9sSB 17 | mhsYtit4l8FyhGmsfFaj9H2IblQGzIga9RFxEbO9dzJUrxpw/gum8LABar2M2iup 18 | YpXchM5VuRA6KpNwU8jFprPuWIAIv3Y4QzeJBjZPbwikfgyiS5SwCWLLD33eUukV 19 | yrGxrOxyJnHW+sC8N+Z4HSLfanaKbKfIN4uh6hBBPkK0BXJzeLW8PWFER8DwEq8R 20 | YeMG9acz3EWKQH+YCUMfvSi/EhmvquOehH07TwGa20ehCpfM3d5yoV8J4cTAtDOa 21 | 95UZecKXJfEjentmNz3/wM0xNYtyOvlh2vhHOwtwNETibQavtIkFhFSEBoH3tP0l 22 | ZifwpcZrA6IqSskIobqKQW7951PEgxCZ8ArTrb/LYQtI/nuWUu6jigIFW35dIGUG 23 | s8P8wGHm2JO6USuUZuQabSBnEkzp68KExkQUyQ7RgUCZ4fQdgooibJt+4VNNFQHZ 24 | cA== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /benches/timer/asio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | 9 | #if defined( BOOST_GCC ) && defined( __SANITIZE_THREAD__ ) 10 | 11 | #include 12 | 13 | void 14 | asio_timer_bench() 15 | { 16 | std::cout << "Asio does not support building with tsan and gcc!" << std::endl; 17 | CHECK( false ); 18 | } 19 | 20 | #else 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | using namespace std::chrono_literals; 32 | 33 | void 34 | asio_timer_bench() 35 | { 36 | static std::atomic_int anums = 0; 37 | 38 | boost::asio::io_context ioc( 1 ); 39 | 40 | for ( int i = 0; i < 10'000; ++i ) { 41 | boost::asio::co_spawn( 42 | ioc.get_executor(), 43 | []( boost::asio::any_io_executor ex ) -> boost::asio::awaitable 44 | { 45 | boost::asio::steady_timer timer( ex ); 46 | for ( int i = 0; i < 10'000; ++i ) { 47 | timer.expires_after( 1ms ); 48 | co_await timer.async_wait( boost::asio::use_awaitable ); 49 | anums.fetch_add( 1, std::memory_order_relaxed ); 50 | } 51 | co_return; 52 | }( ioc.get_executor() ), boost::asio::detached ); 53 | } 54 | ioc.run(); 55 | 56 | CHECK( anums == 10'000 * 10'000 ); 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /include/fiona/io_context.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_IO_CONTEXT_HPP 6 | #define FIONA_IO_CONTEXT_HPP 7 | 8 | #include // for throw_errno_as_err... 9 | #include // for io_context_params 10 | #include // for task 11 | 12 | #include // for io_context_frame 13 | 14 | #include // for unordered_node_map 15 | 16 | #include // for uint16_t 17 | #include // for size_t 18 | #include // for shared_ptr, __shar... 19 | 20 | #include // for EEXIST 21 | 22 | #include // for FIONA_EXPORT 23 | 24 | namespace fiona { 25 | class executor; 26 | } 27 | 28 | namespace fiona { 29 | 30 | class io_context 31 | { 32 | std::shared_ptr p_frame_; 33 | 34 | public: 35 | io_context( io_context_params const& params = {} ) 36 | : p_frame_( std::make_shared( params ) ) 37 | { 38 | } 39 | 40 | FIONA_EXPORT ~io_context(); 41 | 42 | FIONA_EXPORT executor get_executor() const noexcept; 43 | 44 | io_context_params 45 | params() const noexcept 46 | { 47 | return p_frame_->params_; 48 | } 49 | 50 | FIONA_EXPORT void run(); 51 | }; 52 | 53 | } // namespace fiona 54 | 55 | #endif // FIONA_IO_CONTEXT_HPP 56 | -------------------------------------------------------------------------------- /benches/post/asio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "common.hpp" 20 | 21 | using namespace std::chrono_literals; 22 | 23 | namespace asio = boost::asio; 24 | 25 | void 26 | asio_post_bench() 27 | { 28 | asio::io_context ioc; 29 | std::vector threads; 30 | 31 | static int num_runs = 0; 32 | 33 | auto ex = ioc.get_executor(); 34 | asio::steady_timer timer( ex ); 35 | 36 | auto task = [ptimer = &timer]() -> asio::awaitable 37 | { 38 | ++num_runs; 39 | if ( num_runs == total_runs ) { 40 | ptimer->cancel(); 41 | } 42 | co_return; 43 | }; 44 | 45 | asio::co_spawn( ex, [ptimer = &timer]() mutable -> asio::awaitable 46 | { 47 | ptimer->expires_after( 100s ); 48 | co_await ptimer->async_wait( asio::use_awaitable ); 49 | co_return; 50 | }, asio::detached ); 51 | 52 | for ( int i = 0; i < num_threads; ++i ) { 53 | threads.emplace_back( [ex, task] 54 | { 55 | for ( int j = 0; j < num_tasks; ++j ) { 56 | asio::co_spawn( ex, task, asio::detached ); 57 | } 58 | } ); 59 | } 60 | 61 | ioc.run(); 62 | CHECK( num_runs == num_threads * num_tasks ); 63 | } 64 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 3.0 REQUIRED) 2 | 3 | include(Catch) 4 | 5 | add_test( 6 | NAME "fiona-verify-headers" 7 | COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} 8 | --target "fiona_verify_interface_header_sets" 9 | --config $ 6 | 7 | #include // for throw_errno_as_error_code 8 | 9 | #include // for htons, inet_pton 10 | #include // for errno 11 | #include // for AF_INET, AF_INET6 12 | 13 | namespace fiona { 14 | namespace ip { 15 | 16 | sockaddr_in FIONA_EXPORT 17 | make_sockaddr_ipv4( char const* ipv4_addr, std::uint16_t const port ) 18 | { 19 | int ret = -1; 20 | 21 | sockaddr_in addr = {}; 22 | addr.sin_family = AF_INET; 23 | addr.sin_port = htons( port ); 24 | 25 | ret = inet_pton( AF_INET, ipv4_addr, &addr.sin_addr ); 26 | 27 | if ( ret == 0 ) { 28 | throw "invalid network address was used"; 29 | } 30 | 31 | if ( ret != 1 ) { 32 | fiona::detail::throw_errno_as_error_code( errno ); 33 | } 34 | 35 | return addr; 36 | } 37 | 38 | sockaddr_in6 FIONA_EXPORT 39 | make_sockaddr_ipv6( char const* ipv6_addr, std::uint16_t const port ) 40 | { 41 | int ret = -1; 42 | 43 | sockaddr_in6 addr = {}; 44 | addr.sin6_family = AF_INET6; 45 | addr.sin6_port = htons( port ); 46 | 47 | ret = inet_pton( AF_INET6, ipv6_addr, &addr.sin6_addr ); 48 | 49 | if ( ret == 0 ) { 50 | throw "invalid network address was used"; 51 | } 52 | 53 | if ( ret != 1 ) { 54 | fiona::detail::throw_errno_as_error_code( errno ); 55 | } 56 | 57 | return addr; 58 | } 59 | 60 | std::string FIONA_EXPORT 61 | to_string( sockaddr_in const* p_ipv4_addr ) 62 | { 63 | std::string s( INET_ADDRSTRLEN, '\0' ); 64 | auto p_dst = inet_ntop( AF_INET, &p_ipv4_addr->sin_addr, s.data(), 65 | static_cast( s.size() ) ); 66 | if ( p_dst == nullptr ) { 67 | fiona::detail::throw_errno_as_error_code( errno ); 68 | } 69 | auto idx = s.find_first_of( '\0' ); 70 | s.erase( idx ); 71 | return s; 72 | } 73 | 74 | } // namespace ip 75 | } // namespace fiona 76 | -------------------------------------------------------------------------------- /test/file_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "helpers.hpp" 5 | 6 | static int num_runs = 0; 7 | 8 | TEST_CASE( "creating a new file" ) 9 | { 10 | num_runs = 0; 11 | 12 | fiona::io_context ioc; 13 | auto ex = ioc.get_executor(); 14 | 15 | ex.spawn( []( fiona::executor ex ) -> fiona::task 16 | { 17 | fiona::file file( ex ); 18 | 19 | std::size_t const buf_size = 4096; 20 | 21 | auto m_ok = co_await file.async_open( "/tmp", O_TMPFILE | O_RDWR ); 22 | CHECK( m_ok.has_value() ); 23 | 24 | std::string_view msg = "hello, world!"; 25 | auto m_written = co_await file.async_write( msg ); 26 | CHECK( m_written.value() == msg.size() ); 27 | 28 | char read_buf[buf_size] = {}; 29 | auto m_read = co_await file.async_read( read_buf ); 30 | 31 | CHECK( m_read.value() == m_written.value() ); 32 | 33 | std::string_view sv( read_buf, m_read.value() ); 34 | CHECK( sv == msg ); 35 | 36 | ++num_runs; 37 | co_return; 38 | }( ex ) ); 39 | 40 | ioc.run(); 41 | CHECK( num_runs == 1 ); 42 | } 43 | 44 | TEST_CASE( "creating a new file with fixed I/O" ) 45 | { 46 | num_runs = 0; 47 | 48 | fiona::io_context ioc; 49 | auto ex = ioc.get_executor(); 50 | 51 | ex.spawn( []( fiona::executor ex ) -> fiona::task 52 | { 53 | fiona::file file( ex ); 54 | 55 | std::size_t const num_bufs = 1024; 56 | std::size_t const buf_size = 4096; 57 | auto& fixed_bufs = ex.register_fixed_buffers( num_bufs, buf_size ); 58 | 59 | auto buf = fixed_bufs.get_avail_buf(); 60 | 61 | auto m_ok = co_await file.async_open( "/tmp", O_TMPFILE | O_RDWR ); 62 | CHECK( m_ok.has_value() ); 63 | 64 | std::string_view msg = "hello, world!"; 65 | std::memcpy( buf.as_bytes().data(), msg.data(), msg.size() ); 66 | buf.set_len( msg.size() ); 67 | 68 | auto m_written = co_await file.async_write_fixed( buf ); 69 | CHECK( m_written.value() == msg.size() ); 70 | 71 | auto m_read = co_await file.async_read_fixed( buf ); 72 | 73 | CHECK( m_read.value() == m_written.value() ); 74 | 75 | auto sv = buf.as_str(); 76 | CHECK( sv == msg ); 77 | 78 | ++num_runs; 79 | co_return; 80 | }( ex ) ); 81 | 82 | ioc.run(); 83 | CHECK( num_runs == 1 ); 84 | } 85 | -------------------------------------------------------------------------------- /src/buffer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | #include // for min 8 | #include // for memcpy 9 | #include // for span 10 | #include // for string 11 | 12 | namespace fiona { 13 | namespace detail { 14 | 15 | inline constexpr detail::buf_header_impl const default_buf_header_; 16 | 17 | unsigned char* 18 | default_buf_header() 19 | { 20 | return reinterpret_cast( 21 | const_cast( &default_buf_header_ ) ); 22 | } 23 | 24 | } // namespace detail 25 | 26 | namespace { 27 | 28 | std::size_t 29 | buffer_size( recv_buffer_sequence_view bufs ) 30 | { 31 | std::size_t n = 0; 32 | for ( auto buf_view : bufs ) { 33 | n += buf_view.size(); 34 | } 35 | return n; 36 | } 37 | 38 | std::size_t 39 | buffer_copy( std::span dst, recv_buffer_sequence_view bufs ) 40 | { 41 | std::size_t n = 0; 42 | 43 | auto end = bufs.end(); 44 | for ( auto pos = bufs.begin(); pos != end; ++pos ) { 45 | auto buf_view = *pos; 46 | if ( buf_view.empty() ) { 47 | continue; 48 | } 49 | 50 | auto m = std::min( dst.size(), buf_view.size() ); 51 | std::memcpy( dst.data(), buf_view.data(), m ); 52 | n += m; 53 | 54 | if ( m < buf_view.size() ) { 55 | break; 56 | } 57 | 58 | dst = dst.subspan( m ); 59 | if ( dst.empty() ) { 60 | break; 61 | } 62 | } 63 | return n; 64 | } 65 | 66 | std::size_t 67 | buffer_copy( std::span dst, recv_buffer_sequence_view bufs ) 68 | { 69 | return buffer_copy( 70 | std::span( reinterpret_cast( dst.data() ), dst.size() ), 71 | bufs ); 72 | } 73 | 74 | } // namespace 75 | 76 | std::string 77 | recv_buffer_sequence_view::to_string() const 78 | { 79 | auto n = buffer_size( *this ); 80 | std::string s( n, '\0' ); 81 | buffer_copy( std::span( s.data(), s.size() ), *this ); 82 | return s; 83 | } 84 | 85 | std::vector 86 | recv_buffer_sequence_view::to_bytes() const 87 | { 88 | auto n = buffer_size( *this ); 89 | std::vector s( n, 0x00 ); 90 | buffer_copy( s, *this ); 91 | return s; 92 | } 93 | 94 | } // namespace fiona 95 | -------------------------------------------------------------------------------- /test/ipv6_test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int num_runs = 0; 9 | 10 | TEST_CASE( "ipv6 sanity check" ) 11 | { 12 | 13 | auto server = []( fiona::executor ex, 14 | fiona::tcp::acceptor acceptor ) -> fiona::task 15 | { 16 | (void)ex; 17 | 18 | auto mstream = co_await acceptor.async_accept(); 19 | auto& stream = mstream.value(); 20 | 21 | stream.set_buffer_group( 0 ); 22 | 23 | auto mbufs = co_await stream.async_recv(); 24 | auto bufs = std::move( mbufs ).value(); 25 | 26 | auto octets = bufs.to_bytes(); 27 | auto str = bufs.to_string(); 28 | CHECK( octets.size() > 0 ); 29 | CHECK( str == "hello, world!" ); 30 | 31 | auto n_result = co_await stream.async_send( octets ); 32 | 33 | CHECK( n_result.value() == octets.size() ); 34 | 35 | ++num_runs; 36 | co_return; 37 | }; 38 | 39 | auto client = []( fiona::executor ex, 40 | sockaddr_in6 ipv6_addr ) -> fiona::task 41 | { 42 | fiona::tcp::client client( ex ); 43 | fiona::timer timer( ex ); 44 | 45 | auto mok = co_await client.async_connect( &ipv6_addr ); 46 | 47 | CHECK( mok.has_value() ); 48 | 49 | co_await timer.async_wait( 1s ); 50 | 51 | auto const msg = std::string_view( "hello, world!" ); 52 | auto result = co_await client.async_send( msg ); 53 | CHECK( result.value() == msg.size() ); 54 | 55 | client.set_buffer_group( 0 ); 56 | 57 | auto mbufs = co_await client.async_recv(); 58 | auto bufs = std::move( mbufs ).value(); 59 | 60 | auto octets = bufs.to_bytes(); 61 | auto str = bufs.to_string(); 62 | CHECK( octets.size() > 0 ); 63 | CHECK( str == "hello, world!" ); 64 | 65 | ++num_runs; 66 | co_return; 67 | }; 68 | 69 | num_runs = 0; 70 | 71 | fiona::io_context ioc; 72 | 73 | auto ex = ioc.get_executor(); 74 | ex.register_buf_ring( 1024, 128, 0 ); 75 | 76 | auto server_addr = fiona::ip::make_sockaddr_ipv6( localhost_ipv6, 0 ); 77 | fiona::tcp::acceptor acceptor( ex, &server_addr ); 78 | 79 | auto port = acceptor.port(); 80 | 81 | server_addr = fiona::ip::make_sockaddr_ipv6( localhost_ipv6, port ); 82 | ex.spawn( server( ex, std::move( acceptor ) ) ); 83 | ex.spawn( client( ex, server_addr ) ); 84 | 85 | ioc.run(); 86 | CHECK( num_runs == 2 ); 87 | } 88 | -------------------------------------------------------------------------------- /include/fiona/dns.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_DNS_HPP 6 | #define FIONA_DNS_HPP 7 | 8 | #include // for result 9 | #include // for executor 10 | 11 | #include // for coroutine_handle 12 | #include // for shared_ptr 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace fiona { 19 | struct dns_frame; 20 | } // namespace fiona 21 | struct addrinfo; 22 | 23 | namespace fiona { 24 | 25 | struct dns_entry_list 26 | { 27 | private: 28 | addrinfo* head_ = nullptr; 29 | 30 | dns_entry_list( addrinfo* head ); 31 | 32 | friend struct dns_awaitable; 33 | 34 | public: 35 | dns_entry_list() = default; 36 | 37 | FIONA_EXPORT 38 | dns_entry_list( dns_entry_list&& rhs ) noexcept; 39 | 40 | FIONA_EXPORT 41 | dns_entry_list& operator=( dns_entry_list&& rhs ) noexcept; 42 | 43 | dns_entry_list( dns_entry_list const& ) = delete; 44 | dns_entry_list& operator=( dns_entry_list const& ) = delete; 45 | 46 | FIONA_EXPORT 47 | ~dns_entry_list(); 48 | 49 | FIONA_EXPORT 50 | addrinfo const* data() const noexcept; 51 | }; 52 | 53 | struct dns_awaitable 54 | { 55 | private: 56 | executor ex_; 57 | std::shared_ptr pframe_; 58 | 59 | dns_awaitable( executor ex, std::shared_ptr pframe ); 60 | 61 | friend struct dns_resolver; 62 | 63 | public: 64 | ~dns_awaitable() = default; 65 | 66 | FIONA_EXPORT 67 | bool await_ready() const; 68 | 69 | FIONA_EXPORT 70 | void await_suspend( std::coroutine_handle<> h ); 71 | 72 | FIONA_EXPORT 73 | result await_resume(); 74 | }; 75 | 76 | struct dns_resolver 77 | { 78 | private: 79 | fiona::executor ex_; 80 | std::shared_ptr pframe_ = nullptr; 81 | 82 | public: 83 | FIONA_EXPORT 84 | dns_resolver( fiona::executor ex ); 85 | 86 | dns_resolver( dns_resolver&& ) = default; 87 | dns_resolver& operator=( dns_resolver&& ) = default; 88 | 89 | dns_resolver( dns_resolver const& ) = default; 90 | dns_resolver& operator=( dns_resolver const& ) = default; 91 | 92 | ~dns_resolver() = default; 93 | 94 | FIONA_EXPORT 95 | dns_awaitable async_resolve( char const* node, char const* service ); 96 | }; 97 | } // namespace fiona 98 | 99 | #endif // FIONA_DNS_HPP 100 | -------------------------------------------------------------------------------- /test/tls/botan/ca.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCYAmdq01TI9Wa3 3 | y6aRWtVxxubb/vrXUKfiWJN+ezXfK8tcS2vWhFGrs2VsfvplNPnS/dt0BIcQADm5 4 | sOmRnGXyeLyzdUTU1MhscdHQxDYWNZYuh4I1FiLF1+tzYRs/ttJCWNs1VpRTRVJq 5 | HGGnH3u5NmGq9zJ2zCYJYnpfLBYzD6sqTEjuuwHbAiv86yT1V16Q2erJdrN4EG4/ 6 | 8Tsch6ftzQDoYZxe8jnjsfKVLdbbeip8UBjIZ8mT8x/m12hAwuoSnZwxWxF6ibii 7 | JCgxfmKVL373G5eR+O6xtNR9E3F4OmXXR2OUZjt9StQsctbaCvq9G6Bt6k1QCOS1 8 | dn+uUcAr4H+20vy7bj4Oh+uGyx6Bk0DvXcYPJo/cuTXK8JXDnpUW8wOo1Uyih6eX 9 | azjmzPU9IqkWIWQDEjRMl1a/enavLHr2FNRT4AFVeCnYU5EtetJpgyJaTohi/oXO 10 | UXt6gYW7ecV7SKjlhkB6fMskzfXgD3l1Xbf/AWdjvcAKs7Re0IkCAwEAAQKCAYAG 11 | oTbgz4jHdFSKOEVH2lNW3AWeRg0ArmbewLLmIE+QL+PLCbW8AcIMyrfmDH+1QaM9 12 | EuSrDGDWT3w1ChnrHOF2utICwwmWfWqkYtkeJtXzurCVz5kxNt7wtrhxLCS5Gasq 13 | nkopQGEbD09HNietgfpZG0hqjCTU+OsWcQdvS91IACtHSVPLqGJYqqE/2FhLTwfe 14 | f3KgXh9OYZZoDt20zSyDevPK2X8Cxm5vQjKXU7bRlGQdTzeq00P/wj8mR/InE5kl 15 | Xj9uqq93r0xa7FZFRuoZ1Tw9e5RRqZk88fPidPYoXEinC1YsCKyRTdxNTr1Wz9pS 16 | iuIw93Xl6bLEgD3C3efHN2Zh3slEpNLR5cHiR0xucbuhv9jARpymBHex0EerFyPN 17 | 3FLdaCQFuw9sOj5SioHxLuxlto0XIwoJEhle267JmkTwNlOitcBjSVAfjt/x/Q+W 18 | NYSFCjQTbkViIdnnl0y+T296pWqatlMG88kGqKgAkx1yCm4TjS7tRhhru3WSHqUC 19 | gcEAxT9G+F2gboa0tcWlDWm7dUGzmbfVrjq5bxgcXTg+ie17A2UJJg/WCc3izz5a 20 | /qykufpwEcInIw+XwFKh/XIgg4sT5rrc6z3sIKuhS6ZiC5ERTgj0nKrJcT4/UymB 21 | I9xqpIp1+0CFf/TOlvkZCh3jQQ3CQqMPp0uMsGIuZ1QaLATDqqp6cAHr4aXvTQK5 22 | 3JzXfMG1wfIqa5X/p93qbzzcaPpQbfdKR233HJBAP6AiHajhTYGXUjEEL8vq25OY 23 | KnGfAoHBAMVJmiJCqVlEASCFsYoEK1eEZwUoIFtJZbmBJjCtjncF7Ds17PtpmTDB 24 | X8ehsT8fx4MvkpgEEUYtVdYXRhx/freprQUo8xzgROhnupG7+UwMcvfOVvlkYEq4 25 | R382sTTHMNXuUzAadN5KI41Eqru9clJ2Q5X/lgkwBlPrI6k6qgSoXn6uZw1zG3rG 26 | MMsdTCXRF2GvaqJyTK8KWcAuAL+wWHPE8mcRqwhpg3A9zsYZb++ekbkcodN7WYTg 27 | oluW0DQc1wKBwQCvdPczjU1do/Px0atdgBUhW91gOrgYMOKsoPzyK0ruNFJ9lY2K 28 | zwFtXTiJc4VMD9TEHrM8CMH5nTA3AwpXwgKMINYGI2jK1R1LfoCPI6UJdvS2vp6h 29 | Egb49g8scboPZU1zqjsC0cUvKlvuzgxpBGcXgh6wWg5dmYW/jMaMYbKaP26yTM12 30 | /y6NXaa9kVQ1m0rQDhwZNspnQ1nUQEH+y1uhAjEzcIsheP8c6HBOaKqQSZ6I7GLn 31 | 2PRJdBIpWmQ8NS8CgcEAup2slsvHtRCGoGTWU18uQFKVwGL8eF0tRBPONC13nU6U 32 | 7k1MJWT16xB/sS5ZowREjOrrF7CBRoiLsT/CqiJPkxuE9mSN0rqxc8Tqzf+pCS6d 33 | cZpU2wL/sq4tsz/gz7O7liEhv1bHRZ2Lk41okr3Y7ffAZpqlKXusAKVjzigXuuAo 34 | wlvmTuqbC799WCQWa48lNtnfXFoR/2EjFI5vVuNQR/H7+VLVHdfw5LMSbICTSW4N 35 | GbTjvk/nlbGG4mv99c1xAoHAINzhE7ceWVBYdz1gfiq3mTVRmWMcQ3FmpzxWk2eE 36 | ZCdiToOi4DSn5pm3n7Abtif6Z7txm9lsIT5SJFV2JYih7t63sXgpTtWCxBFhZLX4 37 | pHaWjhdEbaY97EJ9jt4dnzWxy1u/7ccotWdzs0DcVzS95+7gTyD/kM9QM1UnU9rX 38 | DFSmE8Ue/alMwyPatlIaCn6bp5Tw+jr+ZomnHSv9L0KgPvMCTUUqa0wV7MucxwXy 39 | 4Kf9U3cpGzNkmIjnKJrs/teF 40 | -----END PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /test/tls/botan/server.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCw9l2TCrrJQRFC 3 | /vuM4jsntjVhQz8qwOrj7L/J7/gUXI7cYPmaRdu5N+ONz/BMJgC5Gm5D6sTjrj01 4 | 04CjiBsST/LN1PnM7F9L+x84Xj/Am3/JmM8ouMkvg/8ubKWLo1e4lONfJu8xhMJ+ 5 | jJhPuBhzdVONu9mtTnuLpqtOjWhMzfIyr1RfgJdFvWH7+GssRj9zNp45tLCkrQj+ 6 | ODHAeGASNR6UAOCZisTTzDjLH8uXpZXrctVmhzAZY6Vr/3N4+by/+ymF3/sxYF5K 7 | Au4Yd4Tmun9pLNJ5y/RI5RaZfT8sfMyWp0aRSAP59wf+MLfJfg7tki7XojInnUQ+ 8 | 03hw0SbKhvSjpYs4wKgc6Oa9VZavRZkwiplTwMz1k7vSHdp85HIoUYpr8oWCkB/l 9 | eGiGzdE7Dj3yP/slM3AmlVEP85LUAU8E+lyyFUCutRcfp9jzV0r9YkVjUkH63RWP 10 | K0TnN92QPWVPGXMnEtWNdGF0bLar5e3XzF3G7RtnPnWvNLmQnX0CAwEAAQKCAYAI 11 | xXPGuAw65LJZ8CpaLCFJsWrDH89fuFQ5AlRQ5BR90RPbsVehFL/BVhdw9GxkU3Xk 12 | 6heqq7y6mrmGxq1kbavTcPChNy8/WW/+MIi2a78caUvtaCX/tLkNu9/7VkzjHcny 13 | 6J5XtwxTStG0VOLjAKyYyULGjn5Jn5w/RDGb7fJl7C29fJp7NidJWgu84e4d/nvJ 14 | CcOdKwTRnl08Jiwau1y3sLfyFuELFNyQsVl9tMO/CBwYOyB/7OKupeIrKjkR0MvV 15 | hyTc/A+xmhaTjnbITXVaicr40yUwAyyyPi9+ocQe+diBhtUoNGZFB3bWrMkW2a7q 16 | 6xsfHuXlqOW7IYNikO2rJ226X6WA/jaTrQzk0s8a+xW40D68g1ul8KHzx8emBLMV 17 | SyrNpL/JtyIYsQbT2XxdVbnExoULxkf4d0VcepCT5btmPX3r6GNT1ubqVNm4PFuc 18 | qH9tJVqRP7EWrv/GM1qm3+eMiF9ApXoyBl6smc6vTBN6Ahoh5TbiWxpQyl2x59kC 19 | gcEAyNWDqjTL6P8uBqIBPY3PPXLc18i2APOB0FnRPKSGZ7Fp/4xktMwKgR9IkxAm 20 | 6r2m8FmYaYgHKYYNZLiawSrdPUjJKRQZ657sN7UMFrvJZLkXPTmjOIBP6MHGm97g 21 | s1eSVD9Fn9Ub5eccYgi2BXSFqSbHrFAJqKjN1kxMN87ryNfeLeFqvCQS4tb4xUZ9 22 | pwy3nhBl20LzeNN67Z0U61OMUrVBmB90TyNvYdpU+73vI8+kAhuGKzIE84KZORGe 23 | iDIfAoHBAOGSNurymCfNRH9hPsld9nkl6YqCf6W8zvlvnT9+YUPN2m2L5vMG3CMv 24 | A6/Qe+eEZ4AaguY8UmpnpX8H6U/beuEKJmtMEdSDxgXj1bYqR0VlRNXABYZMG5qn 25 | CB/y3FQ5mQcehgytnO05V4FMfgNbfCVEnuYNbnv+4VUrBb8mrmpGRuKQkCDiRACW 26 | gFsnwnuByOnK6mdPCaUybngQX624Tor41Ug1z9tQTFQ43uAanRPND+NvxGkwAkeS 27 | InOPwmZU4wKBwCwgvg1oDGXlq/dJIXyk7CRUWLIRJ+yNlLhzA8aPX/jowqGj93zC 28 | WIyGkyu9IM/zW/rztMl7WheFnOvEpDxNbs9IburXv9pF0SFsZnuYR2gaQ0DwqSyB 29 | BQ3fhgQ5tKWzWDOqs2GrMbYSUeo4f8YzFCtWmgkCjRzB+nhby2N309cNl4P9Q7aT 30 | NpMhKQgvtOw/nxjcAYVr8JNb5GbRXQdYFD0YFpGWV7gL0S1vC71Evobf2Omdop1b 31 | IDthNsmTVYIXgQKBwQDWf6OZZ3co4hjwyBhcg+2M5nBeNhoMEK8AoxJ0/kifoXDE 32 | GvVSxIMxMdPfq/EnBtElEBDJvzLf3A+T7IIAXkVLGcvO3FdYLrZvg2gGAc4kP1hl 33 | qmOWgWj+dzdSqi66KMtL6cqu46kGLDD50zNBHg2/orE3Mi6N2qGLy417Z90TUiC1 34 | 8aIcowznQjZseDKIH7JeH4cnFhGl9X72zRsupsSJgnXOZN1zP7hVnn7RvH0CB03E 35 | iNPM8so4JM599YZ2qOECgcEAle4S7Zx0gfIetvI16M4jQHXdaFKZPmbyUxDnT7RE 36 | 2O3ZqKpF5DRYZLNKGHqPsXEVEsVRyMTCBIcHCZfvBvnZuRj+tlCFVK5CayWA+Xa1 37 | 6znG0X8R9eYYqJOnXcMytb3XwYmco+JnlR/ElAETlin8xma/geejDSO6v6KV7k/3 38 | hjdXrOYT5VFMF/ksJAGGk6ywYeT6dpg8eqxZNmv1wCYI8ofVBdnc5J2DaBOdO7zC 39 | r2lyeOVqNvpFghsrjDQlcpS4 40 | -----END PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /test/helpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FIONA_TEST_HELPERS_HPP 2 | #define FIONA_TEST_HELPERS_HPP 3 | 4 | #include 5 | #if BOOST_CLANG 6 | #pragma clang diagnostic ignored "-Wheader-hygiene" 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | using namespace std::chrono_literals; 26 | 27 | #define CHECK_GE( T, U ) CHECK( T >= U ) 28 | #define CHECK_EQ( T, U ) CHECK( T == U ) 29 | #define CHECK_LT( T, U ) CHECK( T < U ) 30 | 31 | #define FIONA_TASK( ... ) []( __VA_ARGS__ ) -> fiona::task 32 | 33 | inline constexpr char const* localhost_ipv4 = "127.0.0.1"; 34 | inline constexpr char const* localhost_ipv6 = "::1"; 35 | 36 | template 37 | struct duration_guard 38 | { 39 | std::chrono::duration expected; 40 | std::chrono::system_clock::time_point prev; 41 | 42 | duration_guard( std::chrono::duration expected_ ) 43 | : expected( expected_ ), prev( std::chrono::system_clock::now() ) 44 | { 45 | } 46 | 47 | ~duration_guard() 48 | { 49 | auto now = std::chrono::system_clock::now(); 50 | 51 | auto elapsed = 52 | std::chrono::duration_cast>( now - 53 | prev ); 54 | CHECK_GE( elapsed, expected ); 55 | CHECK_LT( elapsed, expected * 1.10 ); 56 | } 57 | }; 58 | 59 | template 60 | duration_guard( std::chrono::duration ) 61 | -> duration_guard; 62 | 63 | CATCH_TRANSLATE_EXCEPTION( fiona::error_code const& ex ) 64 | { 65 | return ex.message(); 66 | } 67 | 68 | inline std::vector 69 | make_random_input( std::size_t n ) 70 | { 71 | std::vector v( n, 0 ); 72 | auto rng = Catch::Generators::random( 0, 255 ); 73 | for ( auto& b : v ) { 74 | b = static_cast( rng.get() ); 75 | } 76 | return v; 77 | } 78 | 79 | #if defined( BOOST_GCC ) 80 | 81 | #if !defined( __SANITIZE_ADDRESS__ ) && !defined( __SANITIZE_THREAD__ ) && \ 82 | defined( __OPTIMIZE__ ) 83 | // don't want to run the symmetric transfer tests under: 84 | // gcc -O0 -fsanitize=address 85 | // 86 | // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897 87 | #define RUN_SYMMETRIC_TRANSFER_TESTS 88 | #endif 89 | 90 | #else 91 | #define RUN_SYMMETRIC_TRANSFER_TESTS 92 | #endif 93 | 94 | #endif // FIONA_TEST_HELPERS_HPP 95 | -------------------------------------------------------------------------------- /include/fiona/time.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_TIME_HPP 6 | #define FIONA_TIME_HPP 7 | 8 | #include // for result 9 | #include // for executor 10 | 11 | #include // for duration_to_timespec 12 | 13 | #include // for intrusive_ptr 14 | 15 | #include // for duration 16 | #include // for coroutine_handle 17 | 18 | #include 19 | 20 | namespace fiona { 21 | namespace detail { 22 | 23 | struct timer_impl; 24 | 25 | void FIONA_EXPORT intrusive_ptr_add_ref( timer_impl* ); 26 | 27 | void FIONA_EXPORT intrusive_ptr_release( timer_impl* ); 28 | 29 | } // namespace detail 30 | } // namespace fiona 31 | 32 | struct __kernel_timespec; 33 | 34 | namespace fiona { 35 | 36 | struct timer_awaitable 37 | { 38 | private: 39 | friend struct timer; 40 | 41 | boost::intrusive_ptr ptimer_ = nullptr; 42 | 43 | FIONA_EXPORT 44 | timer_awaitable( boost::intrusive_ptr ptimer, 45 | __kernel_timespec ts ); 46 | 47 | public: 48 | FIONA_EXPORT 49 | ~timer_awaitable(); 50 | 51 | FIONA_EXPORT 52 | bool await_ready() const; 53 | 54 | FIONA_EXPORT 55 | void await_suspend( std::coroutine_handle<> h ); 56 | 57 | FIONA_EXPORT 58 | result await_resume(); 59 | }; 60 | 61 | struct timer_cancel_awaitable 62 | { 63 | private: 64 | friend struct timer; 65 | 66 | boost::intrusive_ptr ptimer_ = nullptr; 67 | 68 | timer_cancel_awaitable( boost::intrusive_ptr ptimer ); 69 | 70 | public: 71 | FIONA_EXPORT 72 | ~timer_cancel_awaitable(); 73 | 74 | FIONA_EXPORT 75 | bool await_ready() const; 76 | 77 | FIONA_EXPORT 78 | void await_suspend( std::coroutine_handle<> h ); 79 | 80 | FIONA_EXPORT 81 | result await_resume(); 82 | }; 83 | 84 | struct timer 85 | { 86 | private: 87 | boost::intrusive_ptr ptimer_ = nullptr; 88 | 89 | public: 90 | FIONA_EXPORT 91 | timer( executor ex ); 92 | 93 | FIONA_EXPORT 94 | timer( timer const& ) = default; 95 | FIONA_EXPORT 96 | timer& operator=( timer const& ) = default; 97 | 98 | FIONA_EXPORT 99 | timer( timer&& ) = default; 100 | FIONA_EXPORT 101 | timer& operator=( timer&& ) = default; 102 | 103 | FIONA_EXPORT 104 | ~timer() = default; 105 | 106 | template 107 | timer_awaitable 108 | async_wait( std::chrono::duration d ) 109 | { 110 | auto ts = detail::duration_to_timespec( d ); 111 | return { ptimer_, ts }; 112 | } 113 | 114 | FIONA_EXPORT 115 | timer_cancel_awaitable async_cancel(); 116 | 117 | FIONA_EXPORT 118 | executor get_executor() const noexcept; 119 | }; 120 | 121 | } // namespace fiona 122 | 123 | #endif // FIONA_TIME_HPP 124 | -------------------------------------------------------------------------------- /src/dns.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | #include // for error_code, result 8 | #include // for executor, waker 9 | 10 | #include // for exchange 11 | 12 | #include // for lock_guard, mutex 13 | #include // for jthread 14 | 15 | #include // for addrinfo (ptr only), freeaddrinfo, getaddrinfo 16 | 17 | namespace fiona { 18 | 19 | struct dns_frame 20 | { 21 | std::mutex m_; 22 | std::thread t_; 23 | char const* node_ = nullptr; 24 | char const* service_ = nullptr; 25 | addrinfo const* hints_ = nullptr; 26 | addrinfo* addrlist_ = nullptr; 27 | int res_ = -1; 28 | }; 29 | 30 | dns_entry_list::dns_entry_list( addrinfo* head ) : head_{ head } {} 31 | 32 | dns_entry_list::dns_entry_list( dns_entry_list&& rhs ) noexcept 33 | : head_{ boost::exchange( rhs.head_, nullptr ) } 34 | { 35 | } 36 | 37 | dns_entry_list& 38 | dns_entry_list::operator=( dns_entry_list&& rhs ) noexcept 39 | { 40 | if ( this != &rhs ) { 41 | head_ = boost::exchange( rhs.head_, nullptr ); 42 | } 43 | return *this; 44 | } 45 | 46 | dns_entry_list::~dns_entry_list() 47 | { 48 | if ( head_ ) { 49 | freeaddrinfo( head_ ); 50 | } 51 | } 52 | 53 | addrinfo const* 54 | dns_entry_list::data() const noexcept 55 | { 56 | return head_; 57 | } 58 | 59 | dns_awaitable::dns_awaitable( executor ex, std::shared_ptr pframe ) 60 | : ex_{ ex }, pframe_{ pframe } 61 | { 62 | } 63 | 64 | bool 65 | dns_awaitable::await_ready() const 66 | { 67 | return false; 68 | } 69 | 70 | void 71 | dns_awaitable::await_suspend( std::coroutine_handle<> h ) 72 | { 73 | auto waker = ex_.make_waker( h ); 74 | auto& frame = *pframe_; 75 | frame.t_ = std::thread( [pframe = pframe_, waker] 76 | { 77 | int res = -1; 78 | 79 | addrinfo* addrlist = nullptr; 80 | res = getaddrinfo( pframe->node_, pframe->service_, pframe->hints_, 81 | &addrlist ); 82 | 83 | { 84 | std::lock_guard guard{ pframe->m_ }; 85 | pframe->res_ = res; 86 | pframe->addrlist_ = addrlist; 87 | } 88 | 89 | waker.wake(); 90 | } ); 91 | } 92 | 93 | result 94 | dns_awaitable::await_resume() 95 | { 96 | auto& frame = *pframe_; 97 | frame.t_.join(); 98 | std::lock_guard guard( frame.m_ ); 99 | 100 | if ( frame.res_ != 0 ) { 101 | return { fiona::error_code::from_errno( frame.res_ ) }; 102 | } 103 | 104 | return { dns_entry_list( frame.addrlist_ ) }; 105 | } 106 | 107 | dns_resolver::dns_resolver( fiona::executor ex ) 108 | : ex_{ ex }, pframe_{ new dns_frame() } 109 | { 110 | } 111 | 112 | dns_awaitable 113 | dns_resolver::async_resolve( char const* node, char const* service ) 114 | { 115 | pframe_->node_ = node; 116 | pframe_->service_ = service; 117 | return { ex_, pframe_ }; 118 | } 119 | 120 | } // namespace fiona 121 | -------------------------------------------------------------------------------- /include/fiona/tls.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_TLS_HPP 6 | #define FIONA_TLS_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include "fiona_tls_export.h" 18 | 19 | namespace fiona { 20 | namespace tls { 21 | 22 | struct tls_context; 23 | 24 | namespace detail { 25 | 26 | struct client_impl; 27 | struct server_impl; 28 | struct tls_context_frame; 29 | 30 | } // namespace detail 31 | 32 | //------------------------------------------------------------------------------ 33 | 34 | struct tls_context 35 | { 36 | private: 37 | friend struct detail::client_impl; 38 | friend struct detail::server_impl; 39 | 40 | std::shared_ptr p_tls_frame_; 41 | 42 | public: 43 | FIONA_TLS_EXPORT 44 | tls_context(); 45 | 46 | ~tls_context() = default; 47 | 48 | tls_context( tls_context const& ) = default; 49 | tls_context& operator=( tls_context const& ) = default; 50 | 51 | FIONA_TLS_EXPORT 52 | void add_certificate_authority( std::string_view filepath ); 53 | 54 | FIONA_TLS_EXPORT 55 | void add_certificate_key_pair( std::string_view cert_path, 56 | std::string_view key_path ); 57 | }; 58 | 59 | //------------------------------------------------------------------------------ 60 | 61 | class FIONA_TLS_EXPORT client : private tcp::client 62 | { 63 | public: 64 | client() = default; 65 | client( tls_context ctx, executor ex, std::string_view hostname ); 66 | 67 | client( client const& ) = default; 68 | client& operator=( client const& ) = default; 69 | 70 | client( client&& ) = default; 71 | client& operator=( client&& ) = default; 72 | 73 | virtual ~client() override; 74 | 75 | bool operator==( client const& ) const = default; 76 | 77 | using tcp::client::async_connect; 78 | using tcp::stream::async_close; 79 | using tcp::stream::set_buffer_group; 80 | 81 | task> async_handshake(); 82 | task> async_send( std::span buf ); 83 | task> async_send( std::string_view msg ); 84 | task> async_recv(); 85 | task> async_shutdown(); 86 | }; 87 | 88 | class FIONA_TLS_EXPORT server : private tcp::stream 89 | { 90 | public: 91 | server() = default; 92 | server( tls_context ctx, executor ex, int fd ); 93 | 94 | server( server const& ) = default; 95 | server& operator=( server const& ) = default; 96 | 97 | server( server&& ) = default; 98 | server& operator=( server&& ) = default; 99 | 100 | virtual ~server() override; 101 | 102 | bool operator==( server const& ) const = default; 103 | 104 | using tcp::stream::async_close; 105 | using tcp::stream::get_executor; 106 | using tcp::stream::set_buffer_group; 107 | 108 | task> async_handshake(); 109 | task> async_send( std::span buf ); 110 | task> async_send( std::string_view msg ); 111 | task> async_recv(); 112 | task> async_shutdown(); 113 | }; 114 | 115 | } // namespace tls 116 | } // namespace fiona 117 | 118 | #endif // FIONA_TLS_HPP 119 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include // for recv_buffer 6 | #include // for throw_errno_as_error_code 7 | 8 | #include // for buf_ring 9 | 10 | #include // for BOOST_ASSERT 11 | 12 | #include // for size_t 13 | #include // for uint16_t, uint32_t 14 | #include // for vector 15 | 16 | #include // for io_uring_buf_ring_add, io_uring_b... 17 | 18 | namespace fiona { 19 | 20 | fixed_buffers::fixed_buffers( io_uring* ring, 21 | unsigned num_bufs, 22 | std::size_t buf_size ) 23 | : bufs_( num_bufs, std::vector( buf_size, 0 ) ), 24 | iovecs_( num_bufs ), buf_ids_( num_bufs ), ring_( ring ) 25 | { 26 | for ( std::size_t i = 0; i < num_bufs; ++i ) { 27 | auto& buf = bufs_[i]; 28 | iovecs_[i] = iovec{ .iov_base = buf.data(), .iov_len = buf.size() }; 29 | buf_ids_[i] = i; 30 | } 31 | avail_buf_ids_ = buf_ids_.data() + num_bufs; 32 | 33 | io_uring_register_buffers( ring_, iovecs_.data(), num_bufs ); 34 | } 35 | 36 | fixed_buffers::~fixed_buffers() { io_uring_unregister_buffers( ring_ ); } 37 | 38 | //------------------------------------------------------------------------------ 39 | 40 | namespace detail { 41 | 42 | buf_ring::buf_ring( io_uring* ring, std::uint32_t num_bufs, std::uint16_t bgid ) 43 | : bufs_( num_bufs ), buf_ids_( num_bufs ), ring_( ring ), bgid_{ bgid } 44 | { 45 | int ret = 0; 46 | 47 | auto* buf_ring = io_uring_setup_buf_ring( ring_, num_bufs, bgid, 0, &ret ); 48 | if ( !buf_ring ) { 49 | throw_errno_as_error_code( -ret ); 50 | } 51 | 52 | buf_ring_ = buf_ring; 53 | buf_id_pos_ = buf_ids_.data(); 54 | } 55 | 56 | buf_ring::buf_ring( io_uring* ring, 57 | std::uint32_t num_bufs, 58 | std::size_t buf_size, 59 | std::uint16_t bgid ) 60 | : buf_ring( ring, num_bufs, bgid ) 61 | { 62 | buf_size_ = buf_size; 63 | for ( auto& buf : bufs_ ) { 64 | buf = recv_buffer( buf_size_ ); 65 | } 66 | 67 | auto mask = io_uring_buf_ring_mask( static_cast( bufs_.size() ) ); 68 | for ( std::size_t i = 0; i < bufs_.size(); ++i ) { 69 | auto& buf = bufs_[i]; 70 | BOOST_ASSERT( buf.capacity() > 0 ); 71 | io_uring_buf_ring_add( 72 | buf_ring_, buf.data(), static_cast( buf.capacity() ), 73 | static_cast( i ), mask, static_cast( i ) ); 74 | } 75 | io_uring_buf_ring_advance( buf_ring_, static_cast( bufs_.size() ) ); 76 | } 77 | 78 | buf_ring::~buf_ring() 79 | { 80 | if ( buf_ring_ ) { 81 | BOOST_ASSERT( ring_ ); 82 | io_uring_free_buf_ring( ring_, buf_ring_, 83 | static_cast( bufs_.size() ), bgid_ ); 84 | } 85 | } 86 | 87 | void 88 | buf_ring::recycle_buffer( recv_buffer buf ) 89 | { 90 | if ( buf_id_pos_ == buf_ids_.data() ) { 91 | return; 92 | } 93 | 94 | auto buffer_id = *( --buf_id_pos_ ); 95 | auto& b = get_buf( buffer_id ); 96 | BOOST_ASSERT( b.capacity() == 0 ); 97 | 98 | BOOST_ASSERT( buf.capacity() > 0 ); 99 | io_uring_buf_ring_add( buf_ring_, buf.data(), 100 | static_cast( buf.capacity() ), 101 | static_cast( buffer_id ), 102 | io_uring_buf_ring_mask( num_bufs() ), 0 ); 103 | io_uring_buf_ring_advance( buf_ring_, 1 ); 104 | b = std::move( buf ); 105 | } 106 | 107 | } // namespace detail 108 | } // namespace fiona 109 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(fiona LANGUAGES CXX VERSION 0.1.0) 3 | 4 | if (CMAKE_CXX_STANDARD LESS "20") 5 | message(FATAL_ERROR "fiona is a C++20 library; current CMAKE_CXX_STANDARD is: ${CMAKE_CXX_STANDARD}") 6 | endif() 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | include(GenerateExportHeader) 10 | include(GNUInstallDirs) 11 | 12 | find_package(PkgConfig REQUIRED) 13 | 14 | pkg_check_modules(uring REQUIRED IMPORTED_TARGET liburing) 15 | 16 | find_package( 17 | Boost 1.84 18 | REQUIRED 19 | COMPONENTS 20 | assert 21 | config 22 | container_hash 23 | core 24 | smart_ptr 25 | system 26 | unordered 27 | ) 28 | 29 | find_package(Botan 3.3.0 CONFIG COMPONENTS tls) 30 | 31 | file( 32 | GLOB_RECURSE fiona_headers 33 | CONFIGURE_DEPENDS 34 | "include/*.hpp" 35 | ) 36 | 37 | file( 38 | GLOB fiona_sources 39 | CONFIGURE_DEPENDS 40 | "src/*.cpp" 41 | ) 42 | 43 | add_library(fiona ${fiona_sources}) 44 | 45 | target_include_directories( 46 | fiona 47 | PUBLIC 48 | $ 49 | $ 50 | ) 51 | 52 | target_link_libraries( 53 | fiona 54 | PUBLIC 55 | PkgConfig::uring 56 | Boost::assert 57 | Boost::config 58 | Boost::container_hash 59 | Boost::core 60 | Boost::smart_ptr 61 | Boost::system 62 | Boost::unordered 63 | ) 64 | 65 | target_sources( 66 | fiona 67 | PUBLIC FILE_SET HEADERS 68 | BASE_DIRS include 69 | FILES ${fiona_headers} 70 | ) 71 | 72 | generate_export_header(fiona) 73 | 74 | target_sources( 75 | fiona 76 | PUBLIC 77 | FILE_SET HEADERS 78 | FILES "${CMAKE_CURRENT_BINARY_DIR}/fiona_export.h" 79 | BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}" 80 | ) 81 | 82 | add_library(Fiona::core ALIAS fiona) 83 | 84 | install( 85 | TARGETS 86 | fiona 87 | 88 | EXPORT 89 | fiona-targets 90 | 91 | FILE_SET HEADERS 92 | 93 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 94 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 95 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 96 | ) 97 | 98 | if(Botan_FOUND) 99 | file( 100 | GLOB fiona_tls_sources 101 | CONFIGURE_DEPENDS 102 | "src/tls/*.cpp" 103 | ) 104 | 105 | add_library(fiona_tls ${fiona_tls_sources}) 106 | target_link_libraries( 107 | fiona_tls 108 | PUBLIC 109 | Fiona::core 110 | Botan::Botan-static 111 | ) 112 | target_include_directories(fiona_tls PRIVATE src/detail) 113 | 114 | generate_export_header(fiona_tls) 115 | target_sources( 116 | fiona_tls 117 | PUBLIC 118 | FILE_SET HEADERS 119 | FILES "${CMAKE_CURRENT_BINARY_DIR}/fiona_tls_export.h" 120 | BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}" 121 | ) 122 | 123 | add_library(Fiona::tls ALIAS fiona_tls) 124 | install( 125 | TARGETS 126 | fiona_tls 127 | 128 | EXPORT 129 | fiona-targets 130 | 131 | FILE_SET HEADERS 132 | 133 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 134 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 135 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 136 | ) 137 | endif() 138 | 139 | install( 140 | EXPORT 141 | fiona-targets 142 | 143 | NAMESPACE Fiona:: 144 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fiona 145 | ) 146 | 147 | include(CMakePackageConfigHelpers) 148 | write_basic_package_version_file( 149 | ${CMAKE_CURRENT_BINARY_DIR}/fiona-config-version.cmake 150 | VERSION ${PROJECT_VERSION} 151 | COMPATIBILITY AnyNewerVersion 152 | ) 153 | 154 | configure_package_config_file( 155 | ${CMAKE_CURRENT_LIST_DIR}/cmake/fiona-config.cmake.in 156 | ${CMAKE_CURRENT_BINARY_DIR}/fiona-config.cmake 157 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fiona 158 | ) 159 | 160 | install( 161 | FILES 162 | ${CMAKE_CURRENT_BINARY_DIR}/fiona-config.cmake 163 | ${CMAKE_CURRENT_BINARY_DIR}/fiona-config-version.cmake 164 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fiona 165 | ) 166 | 167 | option(FIONA_BUILD_TESTING "" OFF) 168 | if(FIONA_BUILD_TESTING) 169 | set_target_properties(fiona PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON) 170 | include(CTest) 171 | add_subdirectory(test) 172 | add_subdirectory(benches) 173 | endif() 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fiona 2 | 3 | Fiona is a coroutine runtime library built on top of io_uring and C++20's 4 | coroutines. This means that Fiona is a Linux-only library. 5 | 6 | Fiona includes a vcpkg.json for easy dependency management. 7 | 8 | ## Useful Scripts for Developers 9 | 10 | ### Testing 11 | 12 | run.sh 13 | 14 | ```bash 15 | #!/bin/bash 16 | 17 | set -e 18 | 19 | clear 20 | 21 | ulimit -n 25000 22 | 23 | ninja -C __build__ fiona_verify_interface_header_sets 24 | 25 | ctest \ 26 | --test-dir __build__/ \ 27 | -j20 \ 28 | --output-on-failure \ 29 | --schedule-random \ 30 | "$@" 31 | ``` 32 | 33 | toolchain.cmake 34 | ```cmake 35 | include(/home/exbigboss/cpp/vcpkg/scripts/buildsystems/vcpkg.cmake) 36 | list(APPEND CMAKE_PREFIX_PATH /home/exbigboss/cpp/__install__) 37 | set(CMAKE_CXX_STANDARD 20) 38 | set(CMAKE_CXX_FLAGS_INIT "-Wall -Wextra -pedantic -fsanitize=address,undefined") 39 | ``` 40 | 41 | For emulating a CI setup locally, use something like: 42 | ```bash 43 | #!/bin/#!/bin/bash 44 | 45 | export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-18 46 | export UBSAN_OPTIONS=print_stacktrace=1 47 | export ASAN_OPTIONS="detect_invalid_pointer_pairs=2:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1" 48 | 49 | set -e 50 | 51 | clean() { 52 | input=$1 53 | str=${input//[^a-zA-Z0-9]/} 54 | echo "$str" 55 | } 56 | 57 | build_and_test() { 58 | local cc 59 | cc=$(clean "$1") 60 | 61 | local build_type 62 | build_type=$(clean "$2") 63 | 64 | local build_dir 65 | local cxx_flags_init 66 | 67 | if [ -z "$3" ]; then 68 | build_dir=__ci_build__/${cc}/${build_type}/nosan 69 | cxx_flags_init="-Wall -Wextra -pedantic" 70 | else 71 | local sanitizers 72 | sanitizers=$(clean "$3") 73 | build_dir=__ci_build__/${cc}/${build_type}/${sanitizers} 74 | cxx_flags_init="-Wall -Wextra -pedantic -fsanitize=$3" 75 | fi 76 | 77 | mkdir -p "$build_dir" 78 | mkdir -p "__ci_build__/${cc}/${build_type}/test" 79 | cp -r test/tls "__ci_build__/${cc}/${build_type}/test" 80 | 81 | cmake \ 82 | --no-warn-unused-cli \ 83 | -DVCPKG_INSTALL_OPTIONS="--no-print-usage --only-binarycaching" \ 84 | -DBUILD_SHARED_LIBS=ON \ 85 | -DBUILD_TESTING=ON \ 86 | -DFIONA_BUILD_TESTING=ON \ 87 | -DCMAKE_PREFIX_PATH=/home/exbigboss/cpp/__install__ \ 88 | -DCMAKE_BUILD_TYPE="$2" \ 89 | -DCMAKE_EXPORT_COMPILE_COMMANDS=TRUE \ 90 | -DCMAKE_CXX_COMPILER="$1" \ 91 | -DCMAKE_CXX_STANDARD=20 \ 92 | -DCMAKE_CXX_EXTENSIONS=OFF \ 93 | -DCMAKE_CXX_FLAGS_INIT="$cxx_flags_init" \ 94 | -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ 95 | -DCMAKE_TOOLCHAIN_FILE=/home/exbigboss/cpp/vcpkg/scripts/buildsystems/vcpkg.cmake \ 96 | -S/home/exbigboss/cpp/fiona \ 97 | -B/home/exbigboss/cpp/fiona/"$build_dir" \ 98 | -G Ninja 99 | 100 | cmake --build /home/exbigboss/cpp/fiona/"$build_dir" --config "$2" --target all 101 | 102 | ulimit -n 25000 103 | 104 | ninja -C "$build_dir" fiona_verify_interface_header_sets 105 | 106 | ctest \ 107 | --test-dir "$build_dir" \ 108 | -j20 \ 109 | --output-on-failure \ 110 | --schedule-random \ 111 | "$@" 112 | } 113 | 114 | clear 115 | 116 | rm -r __ci_build__ 117 | 118 | build_and_test "clang++-18" "release" "address,undefined" 119 | build_and_test "clang++-18" "release" "thread" 120 | build_and_test "clang++-18" "release" 121 | 122 | build_and_test "clang++-17" "release" "address,undefined" 123 | build_and_test "clang++-17" "release" "thread" 124 | build_and_test "clang++-17" "release" 125 | 126 | build_and_test "clang++-16" "release" "address,undefined" 127 | build_and_test "clang++-16" "release" "thread" 128 | build_and_test "clang++-16" "release" 129 | 130 | build_and_test "/home/exbigboss/cpp/__install__/bin/g++" "release" "address,undefined" 131 | build_and_test "/home/exbigboss/cpp/__install__/bin/g++" "release" "thread" 132 | build_and_test "/home/exbigboss/cpp/__install__/bin/g++" "release" 133 | 134 | build_and_test "g++-13" "release" "address,undefined" 135 | build_and_test "g++-13" "release" "thread" 136 | build_and_test "g++-13" "release" 137 | 138 | build_and_test "g++-12" "release" "address,undefined" 139 | build_and_test "g++-12" "release" "thread" 140 | build_and_test "g++-12" "release" 141 | 142 | ``` 143 | -------------------------------------------------------------------------------- /test/waker_test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if BOOST_CLANG 13 | #pragma clang diagnostic ignored "-Wunreachable-code" 14 | #endif 15 | 16 | using lock_guard = std::lock_guard; 17 | 18 | static std::atomic_int num_runs = 0; 19 | static std::mutex mtx; 20 | 21 | struct custom_awaitable 22 | { 23 | std::shared_ptr> nums; 24 | std::thread t; 25 | fiona::executor ex; 26 | std::shared_ptr m; 27 | bool should_detach = false; 28 | 29 | custom_awaitable( fiona::executor ex_ ) : ex{ ex_ } 30 | { 31 | nums = std::make_shared>(); 32 | m = std::make_shared(); 33 | } 34 | 35 | ~custom_awaitable() 36 | { 37 | if ( should_detach ) { 38 | t.detach(); 39 | } else { 40 | t.join(); 41 | } 42 | } 43 | 44 | bool 45 | await_ready() const noexcept 46 | { 47 | return false; 48 | } 49 | 50 | void 51 | await_suspend( std::coroutine_handle<> h ) 52 | { 53 | auto waker = ex.make_waker( h ); 54 | 55 | t = std::thread( [nums = this->nums, m = this->m, 56 | should_detach = this->should_detach, waker]() mutable 57 | { 58 | std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) ); 59 | { 60 | std::lock_guard lg{ *m }; 61 | *nums = std::vector{ 1, 2, 3, 4 }; 62 | } 63 | ++num_runs; 64 | 65 | try { 66 | waker.wake(); 67 | } catch ( std::system_error const& ec ) { 68 | { 69 | lock_guard g( mtx ); 70 | CHECK( should_detach ); 71 | CHECK( ec.code() == std::errc::invalid_argument ); 72 | } 73 | return; 74 | } 75 | 76 | { 77 | lock_guard g( mtx ); 78 | CHECK( !should_detach ); 79 | } 80 | } ); 81 | } 82 | 83 | std::vector 84 | await_resume() 85 | { 86 | std::lock_guard lg{ *m }; 87 | return std::move( *nums ); 88 | } 89 | }; 90 | 91 | TEST_CASE( "waiting a simple future" ) 92 | { 93 | num_runs = 0; 94 | 95 | fiona::io_context ioc; 96 | 97 | auto ex = ioc.get_executor(); 98 | ex.spawn( []( fiona::executor ex ) -> fiona::task 99 | { 100 | duration_guard dg( std::chrono::milliseconds( 500 ) ); 101 | auto nums = co_await custom_awaitable( ex ); 102 | { 103 | lock_guard g( mtx ); 104 | CHECK( nums == std::vector{ 1, 2, 3, 4 } ); 105 | } 106 | ++num_runs; 107 | co_return; 108 | }( ex ) ); 109 | ioc.run(); 110 | 111 | { 112 | lock_guard g( mtx ); 113 | CHECK( num_runs == 2 ); 114 | } 115 | } 116 | 117 | TEST_CASE( "waker outlives the io_context" ) 118 | { 119 | num_runs = 0; 120 | 121 | { 122 | fiona::io_context ioc; 123 | auto ex = ioc.get_executor(); 124 | 125 | ex.spawn( []( fiona::executor ex ) -> fiona::task 126 | { 127 | ++num_runs; 128 | auto a = custom_awaitable( ex ); 129 | a.should_detach = true; 130 | co_await a; 131 | // we should never hit this 132 | CHECK( false ); 133 | }( ex ) ); 134 | 135 | ex.spawn( []( fiona::executor ex ) -> fiona::task 136 | { 137 | (void)ex; 138 | ++num_runs; 139 | throw "a random error occurred!!!!!"; 140 | co_return; 141 | }( ex ) ); 142 | 143 | CHECK_THROWS( ioc.run() ); 144 | } 145 | 146 | std::this_thread::sleep_for( std::chrono::milliseconds( 750 ) ); 147 | CHECK( num_runs == 3 ); 148 | } 149 | 150 | TEST_CASE( "awaiting multiple foreign futures" ) 151 | { 152 | num_runs = 0; 153 | 154 | constexpr int const num_futures = 100; 155 | 156 | fiona::io_context ioc; 157 | 158 | auto ex = ioc.get_executor(); 159 | for ( int i = 0; i < num_futures; ++i ) { 160 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 161 | duration_guard dg( std::chrono::milliseconds( 500 ) ); 162 | auto nums = co_await custom_awaitable( ex ); 163 | { 164 | lock_guard g( mtx ); 165 | CHECK( nums == std::vector{ 1, 2, 3, 4 } ); 166 | } 167 | ++num_runs; 168 | co_return; 169 | }( ex ) ); 170 | } 171 | ioc.run(); 172 | 173 | { 174 | lock_guard g( mtx ); 175 | CHECK( num_runs == 2 * num_futures ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /include/fiona/file.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_FILE_HPP 6 | #define FIONA_FILE_HPP 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace fiona { 18 | namespace detail { 19 | 20 | struct file_impl; 21 | 22 | void FIONA_EXPORT intrusive_ptr_add_ref( file_impl* p_file ) noexcept; 23 | void FIONA_EXPORT intrusive_ptr_release( file_impl* p_file ) noexcept; 24 | 25 | } // namespace detail 26 | 27 | class open_awaitable; 28 | class write_awaitable; 29 | class read_awaitable; 30 | 31 | //------------------------------------------------------------------------------ 32 | 33 | class file 34 | { 35 | boost::intrusive_ptr p_file_; 36 | 37 | public: 38 | file() = default; 39 | 40 | FIONA_EXPORT file( executor ex ); 41 | 42 | file( file const& ) = default; 43 | file& operator=( file const& ) = default; 44 | 45 | file( file&& ) = default; 46 | file& operator=( file&& ) = default; 47 | 48 | ~file() = default; 49 | 50 | bool operator==( file const& ) const = default; 51 | 52 | FIONA_EXPORT open_awaitable async_open( std::string pathname, int flags ); 53 | 54 | FIONA_EXPORT 55 | write_awaitable async_write( std::span msg ); 56 | FIONA_EXPORT write_awaitable async_write( std::string_view msg ); 57 | 58 | FIONA_EXPORT write_awaitable async_write_fixed( fixed_buffer const& buf ); 59 | 60 | FIONA_EXPORT read_awaitable async_read( std::span buf ); 61 | FIONA_EXPORT read_awaitable async_read( std::span buf ); 62 | FIONA_EXPORT read_awaitable async_read_fixed( fixed_buffer& buf ); 63 | }; 64 | 65 | //------------------------------------------------------------------------------ 66 | 67 | class open_awaitable 68 | { 69 | friend class file; 70 | boost::intrusive_ptr p_file_; 71 | 72 | open_awaitable( boost::intrusive_ptr p_file ) 73 | : p_file_( p_file ) 74 | { 75 | } 76 | 77 | public: 78 | open_awaitable() = delete; 79 | 80 | open_awaitable( open_awaitable const& ) = delete; 81 | open_awaitable& operator=( open_awaitable const& ) = delete; 82 | 83 | // TODO: must implement cancel-on-drop here 84 | FIONA_EXPORT ~open_awaitable(); 85 | 86 | bool 87 | await_ready() const noexcept 88 | { 89 | return false; 90 | } 91 | 92 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 93 | FIONA_EXPORT result await_resume(); 94 | }; 95 | 96 | //------------------------------------------------------------------------------ 97 | 98 | class write_awaitable 99 | { 100 | friend class file; 101 | boost::intrusive_ptr p_file_; 102 | 103 | write_awaitable( boost::intrusive_ptr p_file ) 104 | : p_file_( p_file ) 105 | { 106 | } 107 | 108 | public: 109 | write_awaitable() = delete; 110 | 111 | write_awaitable( write_awaitable const& ) = delete; 112 | write_awaitable& operator=( write_awaitable const& ) = delete; 113 | 114 | // TODO: must implement cancel-on-drop here 115 | FIONA_EXPORT ~write_awaitable(); 116 | 117 | bool 118 | await_ready() const noexcept 119 | { 120 | return false; 121 | } 122 | 123 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 124 | FIONA_EXPORT result await_resume(); 125 | }; 126 | 127 | //------------------------------------------------------------------------------ 128 | 129 | class read_awaitable 130 | { 131 | friend class file; 132 | boost::intrusive_ptr p_file_; 133 | 134 | read_awaitable( boost::intrusive_ptr p_file ) 135 | : p_file_( p_file ) 136 | { 137 | } 138 | 139 | public: 140 | read_awaitable() = delete; 141 | 142 | read_awaitable( read_awaitable const& ) = delete; 143 | read_awaitable& operator=( read_awaitable const& ) = delete; 144 | 145 | // TODO: must implement cancel-on-drop here 146 | FIONA_EXPORT ~read_awaitable(); 147 | 148 | bool 149 | await_ready() const noexcept 150 | { 151 | return false; 152 | } 153 | 154 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 155 | FIONA_EXPORT result await_resume(); 156 | }; 157 | 158 | } // namespace fiona 159 | 160 | #endif // FIONA_FILE_HPP 161 | -------------------------------------------------------------------------------- /test/dns_test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" // for FIONA_TASK 2 | 3 | #include // for recv_buffer_sequence 4 | #include // for dns_resolver, dns_awaitable 5 | #include // for error_code 6 | #include // for executor 7 | #include // for io_context 8 | #include // for to_string 9 | #include // for client, connect_awaitable 10 | 11 | #include // for operator""s 12 | #include // for coroutine_handle 13 | #include // for size_t 14 | #include // for addrinfo, EAI_NONAME 15 | #include // for operator== 16 | #include // for string_view 17 | #include // for vector 18 | 19 | #include // for AF_INET, AF_INET6 20 | 21 | struct sockaddr_in; 22 | 23 | static int num_runs = 0; 24 | 25 | TEST_CASE( "fetching the list of remote IP addresses" ) 26 | { 27 | num_runs = 0; 28 | 29 | fiona::io_context ioc; 30 | auto ex = ioc.get_executor(); 31 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 32 | fiona::dns_resolver resolver( ex ); 33 | 34 | auto m_entrylist = 35 | co_await resolver.async_resolve( "www.bing.com", "https" ); 36 | 37 | CHECK( m_entrylist.has_value() ); 38 | 39 | auto& entrylist = m_entrylist.value(); 40 | 41 | int resolved_addrs = 0; 42 | 43 | for ( auto const* p_ai = entrylist.data(); p_ai; p_ai = p_ai->ai_next ) { 44 | auto const af = p_ai->ai_family; 45 | if ( af == AF_INET || af == AF_INET6 ) { 46 | ++resolved_addrs; 47 | } 48 | } 49 | 50 | CHECK( resolved_addrs > 0 ); 51 | ++num_runs; 52 | co_return; 53 | }( ex ) ); 54 | 55 | ioc.run(); 56 | CHECK( num_runs == 1 ); 57 | } 58 | 59 | TEST_CASE( "fetching a non-existent entry" ) 60 | { 61 | num_runs = 0; 62 | 63 | fiona::io_context ioc; 64 | auto ex = ioc.get_executor(); 65 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 66 | fiona::dns_resolver resolver( ex ); 67 | 68 | auto m_entrylist = 69 | co_await resolver.async_resolve( "www.lmaobro.rawr", "https" ); 70 | 71 | if ( m_entrylist.has_value() ) { 72 | auto& entrylist = m_entrylist.value(); 73 | for ( auto p = entrylist.data(); p; p = p->ai_next ) { 74 | REQUIRE( p->ai_family == AF_INET ); 75 | auto p_ipv4 = reinterpret_cast( p->ai_addr ); 76 | // this is the AT&T DNS assist IP that gets returned 77 | // not everyone will have this but I do when using my laptop... 78 | CHECK( fiona::ip::to_string( p_ipv4 ) == "143.244.220.150" ); 79 | } 80 | } else { 81 | CHECK( m_entrylist.has_error() ); 82 | CHECK( m_entrylist.error().value() == EAI_NONAME ); 83 | } 84 | 85 | ++num_runs; 86 | co_return; 87 | }( ex ) ); 88 | 89 | ioc.run(); 90 | CHECK( num_runs == 1 ); 91 | } 92 | 93 | TEST_CASE( "connecting a client" ) 94 | { 95 | num_runs = 0; 96 | 97 | fiona::io_context ioc; 98 | auto ex = ioc.get_executor(); 99 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 100 | fiona::dns_resolver resolver( ex ); 101 | 102 | auto m_entrylist = 103 | co_await resolver.async_resolve( "www.google.com", "http" ); 104 | 105 | CHECK( m_entrylist.has_value() ); 106 | 107 | auto& entrylist = m_entrylist.value(); 108 | 109 | sockaddr const* remote_addr = nullptr; 110 | for ( auto const* p = entrylist.data(); p; p = p->ai_next ) { 111 | if ( p->ai_family == AF_INET || p->ai_family == AF_INET6 ) { 112 | remote_addr = p->ai_addr; 113 | break; 114 | } 115 | } 116 | 117 | fiona::tcp::client client( ex ); 118 | client.timeout( 3s ); 119 | 120 | auto m_ok = co_await client.async_connect( remote_addr ); 121 | CHECK( m_ok.has_value() ); 122 | 123 | auto req = std::string_view( "GET / HTTP/1.1\r\n" 124 | "Host: www.google.com\r\n" 125 | "Connection: close\r\n" 126 | "\r\n" ); 127 | 128 | co_await client.async_send( req ); 129 | 130 | ex.register_buf_ring( 1024, 128, 0 ); 131 | client.set_buffer_group( 0 ); 132 | 133 | std::size_t num_read = 0; 134 | while ( true ) { 135 | auto m_bufs = co_await client.async_recv(); 136 | if ( m_bufs.has_error() ) { 137 | break; 138 | } 139 | 140 | auto& bufs = m_bufs.value(); 141 | auto octets = bufs.to_bytes(); 142 | if ( octets.empty() ) { 143 | break; 144 | } 145 | 146 | num_read += octets.size(); 147 | } 148 | 149 | co_await client.async_close(); 150 | 151 | CHECK( num_read > 0 ); 152 | 153 | ++num_runs; 154 | co_return; 155 | }( ex ) ); 156 | 157 | ioc.run(); 158 | CHECK( num_runs == 1 ); 159 | } 160 | -------------------------------------------------------------------------------- /benches/echo/fiona.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | CATCH_TRANSLATE_EXCEPTION( fiona::error_code const& ex ) 16 | { 17 | return ex.message(); 18 | } 19 | 20 | inline constexpr std::size_t const buf_size = 4096; 21 | 22 | void 23 | fiona_echo_bench() 24 | { 25 | static std::atomic_uint64_t anum_runs = 0; 26 | constexpr std::uint16_t bgid = 0; 27 | 28 | constexpr std::string_view msg = "hello, world!"; 29 | 30 | fiona::io_context_params params; 31 | params.num_files = 16 * 1024; 32 | params.sq_entries = 256; 33 | params.cq_entries = 16 * 1024; 34 | // params.sq_entries = 2 * 4096; 35 | // params.cq_entries = 2 * 4096; 36 | 37 | fiona::io_context ioc( params ); 38 | auto ex = ioc.get_executor(); 39 | ex.register_buf_ring( 4 * 4096, buf_size, bgid ); 40 | 41 | auto addr = fiona::ip::make_sockaddr_ipv4( localhost_ipv4, 0 ); 42 | fiona::tcp::acceptor acceptor( ex, &addr ); 43 | auto const port = acceptor.port(); 44 | 45 | auto handle_request = []( fiona::executor ex, fiona::tcp::stream stream, 46 | std::string_view msg ) -> fiona::task 47 | { 48 | // stream.timeout( 5s ); 49 | 50 | std::size_t num_bytes = 0; 51 | 52 | stream.set_buffer_group( bgid ); 53 | 54 | while ( num_bytes < num_msgs * msg.size() ) { 55 | auto mbufs = co_await stream.async_recv(); 56 | auto bufs = std::move( mbufs ).value(); 57 | 58 | auto view = ( *bufs.begin() ); 59 | 60 | auto octets = view.readable_bytes(); 61 | auto m = view.as_str(); 62 | { 63 | auto g = guard(); 64 | REQUIRE( m == msg ); 65 | } 66 | 67 | auto num_written = co_await stream.async_send( octets ); 68 | 69 | { 70 | auto g = guard(); 71 | REQUIRE( num_written.value() == octets.size() ); 72 | } 73 | num_bytes += octets.size(); 74 | 75 | (void)ex; 76 | // ex.recycle_buffer( bufs.pop_front(), bgid ); 77 | 78 | // if ( num_bytes >= ( num_msgs * msg.size() ) / 2 ) { 79 | // throw "lmao"; 80 | // } 81 | } 82 | 83 | co_await stream.async_close(); 84 | 85 | ++anum_runs; 86 | }; 87 | 88 | auto server = [handle_request]( fiona::executor ex, 89 | fiona::tcp::acceptor acceptor, 90 | std::string_view msg ) -> fiona::task 91 | { 92 | for ( int i = 0; i < num_clients; ++i ) { 93 | auto stream = co_await acceptor.async_accept(); 94 | ex.spawn( handle_request( ex, std::move( stream.value() ), msg ) ); 95 | } 96 | 97 | ++anum_runs; 98 | co_return; 99 | }; 100 | 101 | auto client = []( fiona::executor ex, std::uint16_t port, 102 | std::string_view msg ) -> fiona::task 103 | { 104 | fiona::tcp::client client( ex ); 105 | // client.timeout( 5s ); 106 | 107 | auto addr = fiona::ip::make_sockaddr_ipv4( localhost_ipv4, port ); 108 | auto mok = co_await client.async_connect( &addr ); 109 | (void)mok; 110 | 111 | std::size_t num_bytes = 0; 112 | 113 | client.set_buffer_group( bgid ); 114 | 115 | while ( num_bytes < num_msgs * msg.size() ) { 116 | auto result = co_await client.async_send( msg ); 117 | { 118 | auto g = guard(); 119 | REQUIRE( result.value() == std::size( msg ) ); 120 | } 121 | 122 | auto mbufs = co_await client.async_recv(); 123 | auto bufs = std::move( mbufs ).value(); 124 | 125 | auto view = ( *bufs.begin() ); 126 | 127 | auto octets = view.readable_bytes(); 128 | auto m = view.as_str(); 129 | 130 | { 131 | auto g = guard(); 132 | REQUIRE( octets.size() == result.value() ); 133 | } 134 | { 135 | auto g = guard(); 136 | REQUIRE( m == msg ); 137 | } 138 | 139 | num_bytes += octets.size(); 140 | 141 | // ex.recycle_buffer( bufs.pop_front(), bgid ); 142 | } 143 | 144 | co_await client.async_close(); 145 | ++anum_runs; 146 | }; 147 | 148 | std::thread t1( [¶ms, &client, port, msg] 149 | { 150 | try { 151 | fiona::io_context ioc( params ); 152 | auto ex = ioc.get_executor(); 153 | ex.register_buf_ring( 4 * 4096, buf_size, bgid ); 154 | 155 | for ( int i = 0; i < num_clients; ++i ) { 156 | ex.spawn( client( ex, port, msg ) ); 157 | } 158 | ioc.run(); 159 | 160 | } catch ( std::exception const& ex ) { 161 | std::cout << "exception caught in client thread:\n" 162 | << ex.what() << std::endl; 163 | } catch ( ... ) { 164 | std::cout << "unidentified exception caught" << std::endl; 165 | } 166 | } ); 167 | 168 | ex.spawn( server( ex, std::move( acceptor ), msg ) ); 169 | try { 170 | ioc.run(); 171 | } catch ( ... ) { 172 | t1.join(); 173 | throw; 174 | } 175 | 176 | t1.join(); 177 | 178 | { 179 | auto g = guard(); 180 | REQUIRE( anum_runs == 1 + ( 2 * num_clients ) ); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /include/fiona/task.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_TASK_HPP 6 | #define FIONA_TASK_HPP 7 | 8 | #include 9 | #include 10 | #include // for coroutine_handle, suspend_always 11 | #include // for exception_ptr, rethrow_exception, curren... 12 | #include // for addressof, move, forward 13 | 14 | #include // for BOOST_ASSERT 15 | 16 | #if BOOST_CLANG 17 | #pragma clang diagnostic push 18 | #pragma clang diagnostic ignored "-Wswitch-default" 19 | #endif 20 | 21 | namespace fiona { 22 | template 23 | struct promise; 24 | } // namespace fiona 25 | 26 | namespace fiona { 27 | 28 | template 29 | struct task 30 | { 31 | private: 32 | struct awaitable 33 | { 34 | std::coroutine_handle> h_; 35 | 36 | awaitable( std::coroutine_handle> h ) : h_( h ) {} 37 | 38 | bool 39 | await_ready() const noexcept 40 | { 41 | return !h_ || h_.done(); 42 | } 43 | 44 | std::coroutine_handle<> 45 | await_suspend( std::coroutine_handle<> awaiting_coro ) noexcept; 46 | 47 | decltype( auto ) 48 | await_resume() 49 | { 50 | BOOST_ASSERT( h_ ); 51 | return std::move( h_.promise() ).result(); 52 | } 53 | }; 54 | 55 | std::coroutine_handle> h_ = nullptr; 56 | 57 | public: 58 | using promise_type = ::fiona::promise; 59 | 60 | task() = default; 61 | task( std::coroutine_handle> h ) : h_( h ) {} 62 | 63 | ~task() 64 | { 65 | if ( h_ ) { 66 | h_.destroy(); 67 | } 68 | } 69 | 70 | task( task const& ) = delete; 71 | task& operator=( task const& ) = delete; 72 | 73 | task( task&& rhs ) noexcept : h_( rhs.h_ ) { rhs.h_ = nullptr; } 74 | task& 75 | operator=( task&& rhs ) noexcept 76 | { 77 | if ( this != &rhs ) { 78 | auto h = h_; 79 | h_ = rhs.h_; 80 | rhs.h_ = h; 81 | } 82 | return *this; 83 | } 84 | 85 | awaitable 86 | operator co_await() noexcept 87 | { 88 | return awaitable{ h_ }; 89 | } 90 | 91 | void* 92 | into_address() 93 | { 94 | BOOST_ASSERT( h_ ); 95 | auto p = h_.address(); 96 | h_ = nullptr; 97 | return p; 98 | } 99 | 100 | static task 101 | from_address( void* p ) 102 | { 103 | return { std::coroutine_handle>::from_address( p ) }; 104 | } 105 | }; 106 | 107 | struct promise_base 108 | { 109 | private: 110 | struct final_awaitable 111 | { 112 | bool 113 | await_ready() const noexcept 114 | { 115 | return false; 116 | } 117 | 118 | template 119 | std::coroutine_handle<> 120 | await_suspend( std::coroutine_handle coro ) noexcept 121 | { 122 | return coro.promise().continuation_; 123 | } 124 | 125 | BOOST_NORETURN 126 | void 127 | await_resume() noexcept 128 | { 129 | BOOST_ASSERT( false ); 130 | __builtin_unreachable(); 131 | } 132 | }; 133 | 134 | std::coroutine_handle<> continuation_ = nullptr; 135 | 136 | public: 137 | promise_base() = default; 138 | 139 | std::suspend_always 140 | initial_suspend() 141 | { 142 | return {}; 143 | } 144 | final_awaitable 145 | final_suspend() noexcept 146 | { 147 | return {}; 148 | } 149 | 150 | void 151 | set_continuation( std::coroutine_handle<> continuation ) 152 | { 153 | BOOST_ASSERT( !continuation_ ); 154 | continuation_ = continuation; 155 | } 156 | }; 157 | 158 | template 159 | struct promise final : public promise_base 160 | { 161 | private: 162 | enum class result_type { uninit, ok, err }; 163 | 164 | result_type rt = result_type::uninit; 165 | union { 166 | T value_; 167 | std::exception_ptr exception_; 168 | }; 169 | 170 | public: 171 | promise() noexcept {} 172 | ~promise() 173 | { 174 | switch ( rt ) { 175 | case result_type::ok: 176 | value_.~T(); 177 | break; 178 | 179 | case result_type::err: 180 | exception_.~exception_ptr(); 181 | break; 182 | 183 | case result_type::uninit: 184 | break; 185 | } 186 | } 187 | 188 | task 189 | get_return_object() 190 | { 191 | return { std::coroutine_handle::from_promise( *this ) }; 192 | } 193 | 194 | template 195 | void 196 | return_value( Expr&& expr ) 197 | { 198 | new ( std::addressof( value_ ) ) T( std::forward( expr ) ); 199 | rt = result_type::ok; 200 | } 201 | 202 | void 203 | unhandled_exception() 204 | { 205 | new ( std::addressof( exception_ ) ) 206 | std::exception_ptr( std::current_exception() ); 207 | rt = result_type::err; 208 | } 209 | 210 | T& 211 | result() & 212 | { 213 | if ( rt == result_type::err ) { 214 | std::rethrow_exception( exception_ ); 215 | } 216 | return value_; 217 | } 218 | 219 | T&& 220 | result() && 221 | { 222 | if ( rt == result_type::err ) { 223 | std::rethrow_exception( exception_ ); 224 | } 225 | return std::move( value_ ); 226 | } 227 | }; 228 | 229 | template <> 230 | struct promise final : public promise_base 231 | { 232 | private: 233 | enum class result_type { uninit, ok, err }; 234 | 235 | std::exception_ptr exception_; 236 | 237 | public: 238 | task 239 | get_return_object() 240 | { 241 | return { std::coroutine_handle::from_promise( *this ) }; 242 | } 243 | 244 | void 245 | return_void() 246 | { 247 | } 248 | 249 | void 250 | unhandled_exception() 251 | { 252 | exception_ = std::exception_ptr( std::current_exception() ); 253 | } 254 | 255 | void 256 | result() 257 | { 258 | if ( exception_ ) { 259 | std::rethrow_exception( exception_ ); 260 | } 261 | } 262 | }; 263 | 264 | template 265 | std::coroutine_handle<> 266 | task::awaitable::await_suspend( 267 | std::coroutine_handle<> awaiting_coro ) noexcept 268 | { 269 | /* 270 | * because this awaitable is created using the coroutine_handle of a 271 | * child coroutine, awaiting_coro is the parent 272 | * 273 | * store a handle to the parent in the Promise object so that we can 274 | * access it later in Promise::final_suspend. 275 | */ 276 | h_.promise().set_continuation( awaiting_coro ); 277 | return h_; 278 | } 279 | } // namespace fiona 280 | 281 | #if BOOST_CLANG 282 | #pragma clang diagnostic pop 283 | #endif 284 | 285 | #endif // FIONA_TASK_HPP 286 | -------------------------------------------------------------------------------- /benches/echo/asio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | 9 | #if defined( BOOST_GCC ) && defined( __SANITIZE_THREAD__ ) 10 | 11 | #include 12 | 13 | void 14 | asio_echo_bench() 15 | { 16 | std::cout << "Asio does not support building with tsan and gcc!" << std::endl; 17 | CHECK( false ); 18 | } 19 | 20 | #else 21 | 22 | // #define USE_TIMEOUTS 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | using namespace std::chrono_literals; 43 | using namespace boost::asio::experimental::awaitable_operators; 44 | 45 | #if defined( USE_TIMEOUTS ) 46 | using stream_type = boost::beast::tcp_stream; 47 | #else 48 | using stream_type = boost::asio::ip::tcp::socket; 49 | #endif 50 | 51 | void 52 | asio_echo_bench() 53 | { 54 | static std::atomic_uint64_t anum_runs = 0; 55 | 56 | constexpr std::string_view msg = "hello, world!"; 57 | 58 | boost::asio::io_context ioc( 1 ); 59 | auto ex = ioc.get_executor(); 60 | 61 | boost::asio::ip::tcp::acceptor acceptor( ioc ); 62 | static boost::asio::ip::tcp::endpoint endpoint( 63 | boost::asio::ip::address_v4( { 127, 0, 0, 1 } ), 3301 ); 64 | acceptor.open( endpoint.protocol() ); 65 | acceptor.set_option( boost::asio::ip::tcp::acceptor::reuse_address( true ) ); 66 | acceptor.bind( endpoint ); 67 | acceptor.listen(); 68 | 69 | auto handle_request = 70 | []( stream_type stream, 71 | std::string_view msg ) -> boost::asio::awaitable 72 | { 73 | std::size_t num_bytes = 0; 74 | 75 | char buf[128] = {}; 76 | 77 | while ( num_bytes < num_msgs * msg.size() ) { 78 | 79 | #if defined( USE_TIMEOUTS ) 80 | stream.expires_after( 5s ); 81 | #endif 82 | 83 | auto num_read = co_await stream.async_read_some( 84 | boost::asio::buffer( buf ), boost::asio::deferred ); 85 | 86 | auto octets = std::span( buf, num_read ); 87 | auto m = std::string_view( reinterpret_cast( octets.data() ), 88 | octets.size() ); 89 | { 90 | auto g = guard(); 91 | REQUIRE( m == msg ); 92 | } 93 | 94 | #if defined( USE_TIMEOUTS ) 95 | stream.expires_after( 5s ); 96 | #endif 97 | 98 | auto num_written = co_await stream.async_write_some( 99 | boost::asio::buffer( m ), boost::asio::deferred ); 100 | 101 | { 102 | auto g = guard(); 103 | REQUIRE( num_written == octets.size() ); 104 | } 105 | num_bytes += octets.size(); 106 | 107 | // if ( num_bytes >= ( num_msgs * msg.size() ) / 2 ) { 108 | // throw "lmao"; 109 | // } 110 | } 111 | 112 | ++anum_runs; 113 | }; 114 | 115 | auto server = 116 | [handle_request]( boost::asio::any_io_executor ex, 117 | boost::asio::ip::tcp::acceptor acceptor, 118 | std::string_view msg ) -> boost::asio::awaitable 119 | { 120 | for ( int i = 0; i < num_clients; ++i ) { 121 | auto stream = co_await acceptor.async_accept( boost::asio::deferred ); 122 | 123 | boost::asio::co_spawn( 124 | ex, handle_request( stream_type( std::move( stream ) ), msg ), 125 | boost::asio::detached ); 126 | } 127 | 128 | ++anum_runs; 129 | co_return; 130 | }; 131 | 132 | auto client = []( boost::asio::any_io_executor ex, 133 | std::string_view msg ) -> boost::asio::awaitable 134 | { 135 | stream_type client( ex ); 136 | 137 | #if defined( USE_TIMEOUTS ) 138 | client.expires_after( 5s ); 139 | #endif 140 | 141 | co_await client.async_connect( endpoint, boost::asio::deferred ); 142 | 143 | std::size_t num_bytes = 0; 144 | 145 | char buf[128] = {}; 146 | while ( num_bytes < num_msgs * msg.size() ) { 147 | #if defined( USE_TIMEOUTS ) 148 | client.expires_after( 5s ); 149 | #endif 150 | 151 | auto num_written = co_await client.async_write_some( 152 | boost::asio::buffer( msg ), boost::asio::deferred ); 153 | 154 | { 155 | auto g = guard(); 156 | REQUIRE( num_written == std::size( msg ) ); 157 | } 158 | 159 | #if defined( USE_TIMEOUTS ) 160 | client.expires_after( 5s ); 161 | #endif 162 | 163 | auto num_read = co_await client.async_read_some( 164 | boost::asio::buffer( buf ), boost::asio::deferred ); 165 | 166 | { 167 | auto g = guard(); 168 | REQUIRE( num_written == num_read ); 169 | } 170 | 171 | auto octets = std::span( buf, num_read ); 172 | auto m = std::string_view( reinterpret_cast( octets.data() ), 173 | octets.size() ); 174 | { 175 | auto g = guard(); 176 | REQUIRE( m == msg ); 177 | } 178 | 179 | num_bytes += octets.size(); 180 | } 181 | 182 | ++anum_runs; 183 | co_return; 184 | }; 185 | 186 | std::thread t1( [&client, msg] 187 | { 188 | try { 189 | boost::asio::io_context ioc( 1 ); 190 | 191 | auto ex = ioc.get_executor(); 192 | for ( int i = 0; i < num_clients; ++i ) { 193 | boost::asio::co_spawn( ex, client( ex, msg ), boost::asio::detached ); 194 | } 195 | 196 | auto num_run = ioc.run(); 197 | { 198 | auto g = guard(); 199 | REQUIRE( num_run > 0 ); 200 | } 201 | 202 | } catch ( std::exception const& ex ) { 203 | std::cout << "exception caught in client thread:\n" 204 | << ex.what() << std::endl; 205 | } 206 | } ); 207 | 208 | boost::asio::co_spawn( ex, server( ex, std::move( acceptor ), msg ), 209 | boost::asio::detached ); 210 | 211 | try { 212 | auto num_run = ioc.run(); 213 | { 214 | auto g = guard(); 215 | REQUIRE( num_run > 0 ); 216 | } 217 | } catch ( ... ) { 218 | t1.join(); 219 | throw; 220 | } 221 | 222 | t1.join(); 223 | 224 | { 225 | auto g = guard(); 226 | REQUIRE( anum_runs == 1 + ( 2 * num_clients ) ); 227 | } 228 | } 229 | #endif 230 | -------------------------------------------------------------------------------- /benches/recv/fiona.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | using namespace std::chrono_literals; 17 | 18 | CATCH_TRANSLATE_EXCEPTION( fiona::error_code const& ex ) 19 | { 20 | return ex.message(); 21 | } 22 | 23 | inline constexpr std::size_t const buf_size = 4096; 24 | inline constexpr std::size_t const num_bufs = 1024; 25 | 26 | void 27 | fiona_recv_bench() 28 | { 29 | static std::atomic_uint64_t anum_runs = 0; 30 | constexpr std::uint16_t server_bgid = 0; 31 | constexpr std::uint16_t client_bgid = 1; 32 | 33 | auto msg = make_random_input( msg_size ); 34 | 35 | fiona::io_context_params params; 36 | params.num_files = 16 * 1024; 37 | params.sq_entries = 256; 38 | params.cq_entries = 16 * 1024; 39 | // params.sq_entries = 2 * 4096; 40 | // params.cq_entries = 2 * 4096; 41 | 42 | fiona::io_context ioc( params ); 43 | auto ex = ioc.get_executor(); 44 | ex.register_buf_ring( num_bufs, buf_size, server_bgid ); 45 | 46 | auto addr = fiona::ip::make_sockaddr_ipv4( localhost_ipv4, 0 ); 47 | fiona::tcp::acceptor acceptor( ex, &addr ); 48 | auto const port = acceptor.port(); 49 | 50 | auto handle_request = 51 | []( fiona::executor, fiona::tcp::stream stream, 52 | std::span msg ) -> fiona::task 53 | { 54 | stream.timeout( 5s ); 55 | 56 | std::size_t num_bytes = 0; 57 | 58 | stream.set_buffer_group( server_bgid ); 59 | 60 | std::vector body( msg_size ); 61 | unsigned char* p = body.data(); 62 | 63 | while ( num_bytes < num_msgs * msg.size() ) { 64 | auto m_bufs = co_await stream.async_recv(); 65 | if ( m_bufs.has_error() && 66 | m_bufs.error() == fiona::error_code::from_errno( ENOBUFS ) ) { 67 | continue; 68 | } 69 | 70 | auto bufs = std::move( m_bufs ).value(); 71 | for ( auto view : bufs ) { 72 | auto octets = view.readable_bytes(); 73 | { 74 | auto g = guard(); 75 | REQUIRE( octets.size() > 0 ); 76 | } 77 | 78 | for ( std::size_t i = 0; i < octets.size(); ++i ) { 79 | *p++ = octets[i]; 80 | } 81 | 82 | num_bytes += octets.size(); 83 | } 84 | } 85 | { 86 | auto g = guard(); 87 | REQUIRE( std::ranges::equal( body, msg ) ); 88 | } 89 | 90 | auto send_buf = msg; 91 | while ( !send_buf.empty() ) { 92 | auto m_sent = co_await stream.async_send( send_buf ); 93 | if ( m_sent.has_error() ) { 94 | { 95 | auto g = guard(); 96 | CHECK( m_sent.error() == fiona::error_code::from_errno( ETIMEDOUT ) ); 97 | } 98 | } 99 | { 100 | auto g = guard(); 101 | REQUIRE( m_sent.has_value() ); 102 | } 103 | auto sent = *m_sent; 104 | send_buf = send_buf.subspan( sent ); 105 | } 106 | 107 | co_await stream.async_shutdown( SHUT_WR ); 108 | co_await stream.async_recv(); 109 | co_await stream.async_close(); 110 | ++anum_runs; 111 | }; 112 | 113 | auto server = [handle_request]( 114 | fiona::executor ex, fiona::tcp::acceptor acceptor, 115 | std::span msg ) -> fiona::task 116 | { 117 | for ( int i = 0; i < num_clients; ++i ) { 118 | auto stream = co_await acceptor.async_accept(); 119 | ex.spawn( handle_request( ex, std::move( stream.value() ), msg ) ); 120 | } 121 | 122 | ++anum_runs; 123 | co_return; 124 | }; 125 | 126 | auto client = []( fiona::executor ex, std::uint16_t port, 127 | std::span msg ) -> fiona::task 128 | { 129 | fiona::tcp::client client( ex ); 130 | client.timeout( 5s ); 131 | 132 | auto addr = fiona::ip::make_sockaddr_ipv4( localhost_ipv4, port ); 133 | auto mok = co_await client.async_connect( &addr ); 134 | (void)mok; 135 | 136 | std::size_t num_bytes = 0; 137 | 138 | client.set_buffer_group( client_bgid ); 139 | 140 | std::vector body( msg_size ); 141 | unsigned char* p = body.data(); 142 | 143 | auto send_buf = msg; 144 | while ( !send_buf.empty() ) { 145 | auto m_sent = co_await client.async_send( send_buf ); 146 | { 147 | auto g = guard(); 148 | REQUIRE( m_sent.has_value() ); 149 | } 150 | auto sent = *m_sent; 151 | send_buf = send_buf.subspan( sent ); 152 | } 153 | 154 | while ( num_bytes < num_msgs * msg.size() ) { 155 | auto mbufs = co_await client.async_recv(); 156 | if ( mbufs.has_error() && 157 | mbufs.error() == fiona::error_code::from_errno( ENOBUFS ) ) { 158 | continue; 159 | } 160 | 161 | auto bufs = std::move( mbufs ).value(); 162 | for ( auto view : bufs ) { 163 | auto octets = view.readable_bytes(); 164 | if ( octets.size() == 0 ) { 165 | { 166 | auto g = guard(); 167 | REQUIRE( num_bytes == num_msgs * msg.size() ); 168 | } 169 | } 170 | 171 | for ( std::size_t i = 0; i < octets.size(); ++i ) { 172 | *p++ = octets[i]; 173 | } 174 | 175 | num_bytes += octets.size(); 176 | } 177 | } 178 | { 179 | auto g = guard(); 180 | REQUIRE( std::ranges::equal( body, msg ) ); 181 | } 182 | 183 | co_await client.async_shutdown( SHUT_WR ); 184 | co_await client.async_recv(); 185 | co_await client.async_close(); 186 | ++anum_runs; 187 | }; 188 | 189 | std::thread t1( [¶ms, &client, port, msg] 190 | { 191 | try { 192 | fiona::io_context ioc( params ); 193 | auto ex = ioc.get_executor(); 194 | ex.register_buf_ring( num_bufs, buf_size, client_bgid ); 195 | 196 | for ( int i = 0; i < num_clients; ++i ) { 197 | ex.spawn( client( ex, port, msg ) ); 198 | } 199 | ioc.run(); 200 | 201 | } catch ( std::exception const& ex ) { 202 | std::cout << "exception caught in client thread:\n" 203 | << ex.what() << std::endl; 204 | } catch ( ... ) { 205 | std::cout << "unidentified exception caught" << std::endl; 206 | } 207 | } ); 208 | 209 | ex.spawn( server( ex, std::move( acceptor ), msg ) ); 210 | try { 211 | ioc.run(); 212 | } catch ( ... ) { 213 | t1.join(); 214 | throw; 215 | } 216 | 217 | t1.join(); 218 | 219 | { 220 | auto g = guard(); 221 | REQUIRE( anum_runs == 1 + ( 2 * num_clients ) ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /benches/recv/asio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "common.hpp" 6 | 7 | #include 8 | 9 | #if defined( BOOST_GCC ) && defined( __SANITIZE_THREAD__ ) 10 | 11 | #include 12 | 13 | void 14 | asio_recv_bench() 15 | { 16 | std::cout << "Asio does not support building with tsan and gcc!" << std::endl; 17 | CHECK( false ); 18 | } 19 | 20 | #else 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | using namespace std::chrono_literals; 40 | using namespace boost::asio::experimental::awaitable_operators; 41 | 42 | void 43 | asio_recv_bench() 44 | { 45 | static std::atomic_uint64_t anum_runs = 0; 46 | 47 | auto msg = make_random_input( msg_size ); 48 | 49 | boost::asio::io_context ioc( 1 ); 50 | auto ex = ioc.get_executor(); 51 | 52 | boost::asio::ip::tcp::acceptor acceptor( ioc ); 53 | static boost::asio::ip::tcp::endpoint endpoint( 54 | boost::asio::ip::address_v4( { 127, 0, 0, 1 } ), 3301 ); 55 | acceptor.open( endpoint.protocol() ); 56 | acceptor.set_option( boost::asio::ip::tcp::acceptor::reuse_address( true ) ); 57 | acceptor.bind( endpoint ); 58 | acceptor.listen(); 59 | 60 | auto handle_request = 61 | []( boost::beast::tcp_stream stream, 62 | std::span msg ) -> boost::asio::awaitable 63 | { 64 | std::size_t num_bytes = 0; 65 | 66 | std::vector body( msg_size ); 67 | unsigned char* p = body.data(); 68 | 69 | unsigned char buf[4096] = {}; 70 | 71 | while ( num_bytes < num_msgs * msg.size() ) { 72 | stream.expires_after( 5s ); 73 | auto num_read = co_await stream.async_read_some( 74 | boost::asio::buffer( buf ), boost::asio::use_awaitable ); 75 | 76 | auto octets = std::span( buf, num_read ); 77 | { 78 | auto g = guard(); 79 | REQUIRE( octets.size() > 0 ); 80 | } 81 | 82 | for ( std::size_t i = 0; i < octets.size(); ++i ) { 83 | *p++ = octets[i]; 84 | } 85 | num_bytes += octets.size(); 86 | } 87 | { 88 | auto g = guard(); 89 | REQUIRE( std::ranges::equal( body, msg ) ); 90 | } 91 | 92 | auto send_buf = msg; 93 | while ( !send_buf.empty() ) { 94 | stream.expires_after( 5s ); 95 | auto num_written = co_await stream.async_write_some( 96 | boost::asio::buffer( send_buf ), boost::asio::use_awaitable ); 97 | 98 | send_buf = send_buf.subspan( num_written ); 99 | } 100 | 101 | stream.socket().shutdown( boost::asio::ip::tcp::socket::shutdown_send ); 102 | try { 103 | co_await stream.async_read_some( boost::asio::buffer( buf ), 104 | boost::asio::use_awaitable ); 105 | } catch ( ... ) { 106 | } 107 | stream.socket().close(); 108 | ++anum_runs; 109 | }; 110 | 111 | auto server = [handle_request]( boost::asio::any_io_executor ex, 112 | boost::asio::ip::tcp::acceptor acceptor, 113 | std::span msg ) 114 | -> boost::asio::awaitable 115 | { 116 | for ( int i = 0; i < num_clients; ++i ) { 117 | auto stream = 118 | co_await acceptor.async_accept( boost::asio::use_awaitable ); 119 | 120 | boost::asio::co_spawn( 121 | ex, 122 | handle_request( boost::beast::tcp_stream( std::move( stream ) ), 123 | msg ), 124 | boost::asio::detached ); 125 | } 126 | 127 | ++anum_runs; 128 | co_return; 129 | }; 130 | 131 | auto client = 132 | []( boost::asio::any_io_executor ex, 133 | std::span msg ) -> boost::asio::awaitable 134 | { 135 | boost::beast::tcp_stream client( ex ); 136 | 137 | client.expires_after( 5s ); 138 | co_await client.async_connect( endpoint, boost::asio::use_awaitable ); 139 | 140 | std::size_t num_bytes = 0; 141 | 142 | std::vector body( msg_size ); 143 | unsigned char* p = body.data(); 144 | 145 | unsigned char buf[4096] = {}; 146 | 147 | auto send_buf = msg; 148 | while ( !send_buf.empty() ) { 149 | client.expires_after( 5s ); 150 | auto num_written = co_await client.async_write_some( 151 | boost::asio::buffer( send_buf ), boost::asio::use_awaitable ); 152 | 153 | send_buf = send_buf.subspan( num_written ); 154 | } 155 | 156 | while ( num_bytes < num_msgs * msg.size() ) { 157 | client.expires_after( 5s ); 158 | auto num_read = co_await client.async_read_some( 159 | boost::asio::buffer( buf ), boost::asio::use_awaitable ); 160 | 161 | auto octets = std::span( buf, num_read ); 162 | { 163 | auto g = guard(); 164 | REQUIRE( octets.size() > 0 ); 165 | } 166 | 167 | for ( std::size_t i = 0; i < octets.size(); ++i ) { 168 | *p++ = octets[i]; 169 | } 170 | num_bytes += octets.size(); 171 | } 172 | { 173 | auto g = guard(); 174 | REQUIRE( std::ranges::equal( body, msg ) ); 175 | } 176 | 177 | client.socket().shutdown( boost::asio::ip::tcp::socket::shutdown_send ); 178 | try { 179 | co_await client.async_read_some( boost::asio::buffer( buf ), 180 | boost::asio::use_awaitable ); 181 | } catch ( ... ) { 182 | } 183 | client.socket().close(); 184 | 185 | ++anum_runs; 186 | co_return; 187 | }; 188 | 189 | std::thread t1( [&client, msg] 190 | { 191 | try { 192 | boost::asio::io_context ioc( 1 ); 193 | 194 | auto ex = ioc.get_executor(); 195 | for ( int i = 0; i < num_clients; ++i ) { 196 | boost::asio::co_spawn( ex, client( ex, msg ), boost::asio::detached ); 197 | } 198 | 199 | auto num_run = ioc.run(); 200 | 201 | { 202 | auto g = guard(); 203 | REQUIRE( num_run > 0 ); 204 | } 205 | 206 | } catch ( std::exception const& ex ) { 207 | std::cout << "exception caught in client thread:\n" 208 | << ex.what() << std::endl; 209 | } 210 | } ); 211 | 212 | boost::asio::co_spawn( ex, server( ex, std::move( acceptor ), msg ), 213 | boost::asio::detached ); 214 | 215 | try { 216 | auto num_run = ioc.run(); 217 | { 218 | auto g = guard(); 219 | REQUIRE( num_run > 0 ); 220 | } 221 | } catch ( ... ) { 222 | t1.join(); 223 | throw; 224 | } 225 | 226 | t1.join(); 227 | 228 | { 229 | auto g = guard(); 230 | REQUIRE( anum_runs == 1 + ( 2 * num_clients ) ); 231 | } 232 | } 233 | #endif 234 | -------------------------------------------------------------------------------- /src/time.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | #include // for error_code, result, thr... 8 | #include // for executor, executor_acce... 9 | 10 | #include // for get_sqe, submit_ring 11 | 12 | #include // for BOOST_ASSERT 13 | #include // for BOOST_NOINLINE, BOOST_N... 14 | #include 15 | #include // for intrusive_ptr 16 | 17 | #include // for coroutine_handle 18 | #include // for make_error_code, errc 19 | #include // for move 20 | 21 | #include // for EBUSY, ETIME 22 | #include // for io_uring_sqe_set_data 23 | #include // for io_uring_cqe 24 | #include // for __kernel_timespec 25 | 26 | #include "detail/awaitable_base.hpp" // for awaitable_base, intrusi... 27 | 28 | namespace fiona { 29 | namespace { 30 | 31 | BOOST_NOINLINE BOOST_NORETURN inline void 32 | throw_busy() 33 | { 34 | detail::throw_errno_as_error_code( EBUSY ); 35 | } 36 | 37 | } // namespace 38 | 39 | namespace detail { 40 | namespace { 41 | 42 | struct timeout_frame : public detail::awaitable_base 43 | { 44 | timer_impl* ptimer_ = nullptr; 45 | error_code ec_; 46 | __kernel_timespec ts_ = { .tv_sec = 0, .tv_nsec = 0 }; 47 | std::coroutine_handle<> h_; 48 | bool initiated_ = false; 49 | bool done_ = false; 50 | 51 | timeout_frame( timer_impl* ptimer ) : ptimer_( ptimer ) {} 52 | 53 | virtual ~timeout_frame() override; 54 | 55 | void 56 | reset() 57 | { 58 | ts_ = { .tv_sec = 0, .tv_nsec = 0 }; 59 | h_ = nullptr; 60 | initiated_ = false; 61 | done_ = false; 62 | ec_ = {}; 63 | } 64 | 65 | void 66 | await_process_cqe( io_uring_cqe* cqe ) override 67 | { 68 | done_ = true; 69 | auto e = -cqe->res; 70 | if ( e != 0 && e != ETIME ) { 71 | ec_ = error_code::from_errno( e ); 72 | } 73 | } 74 | 75 | std::coroutine_handle<> 76 | handle() noexcept override 77 | { 78 | return boost::exchange( h_, nullptr ); 79 | } 80 | }; 81 | 82 | struct cancel_frame : public detail::awaitable_base 83 | { 84 | timer_impl* ptimer_ = nullptr; 85 | error_code ec_; 86 | std::coroutine_handle<> h_ = nullptr; 87 | bool initiated_ = false; 88 | bool done_ = false; 89 | 90 | cancel_frame( timer_impl* ptimer ) : ptimer_( ptimer ) {} 91 | virtual ~cancel_frame() override; 92 | 93 | void 94 | reset() 95 | { 96 | initiated_ = false; 97 | done_ = false; 98 | ec_ = {}; 99 | } 100 | 101 | void 102 | await_process_cqe( io_uring_cqe* cqe ) override 103 | { 104 | done_ = true; 105 | if ( cqe->res < 0 ) { 106 | ec_ = error_code::from_errno( -cqe->res ); 107 | } 108 | } 109 | 110 | std::coroutine_handle<> 111 | handle() noexcept override 112 | { 113 | return boost::exchange( h_, nullptr ); 114 | } 115 | }; 116 | 117 | } // namespace 118 | 119 | struct timer_impl : public virtual ref_count, 120 | public timeout_frame, 121 | public cancel_frame 122 | { 123 | private: 124 | friend struct fiona::timer_awaitable; 125 | friend struct fiona::timer_cancel_awaitable; 126 | friend struct fiona::timer; 127 | 128 | executor ex_; 129 | 130 | public: 131 | timer_impl( executor ex ) 132 | : timeout_frame( this ), cancel_frame( this ), ex_( ex ) 133 | { 134 | } 135 | 136 | virtual ~timer_impl() override; 137 | }; 138 | 139 | void 140 | intrusive_ptr_add_ref( timer_impl* ptimer ) 141 | { 142 | intrusive_ptr_add_ref( static_cast( ptimer ) ); 143 | } 144 | 145 | void 146 | intrusive_ptr_release( timer_impl* ptimer ) 147 | { 148 | intrusive_ptr_release( static_cast( ptimer ) ); 149 | } 150 | 151 | timeout_frame::~timeout_frame() {} 152 | cancel_frame::~cancel_frame() {} 153 | timer_impl::~timer_impl() {} 154 | 155 | } // namespace detail 156 | 157 | timer_awaitable::timer_awaitable( 158 | boost::intrusive_ptr ptimer, __kernel_timespec ts ) 159 | : ptimer_( ptimer ) 160 | { 161 | ptimer_->timeout_frame::ts_ = ts; 162 | } 163 | 164 | timer_awaitable::~timer_awaitable() 165 | { 166 | auto& tf = static_cast( *ptimer_ ); 167 | if ( tf.initiated_ && !tf.done_ ) { 168 | auto ring = detail::executor_access_policy::ring( ptimer_->ex_ ); 169 | 170 | auto sqe = detail::get_sqe( ring ); 171 | io_uring_prep_cancel( sqe, &tf, 0 ); 172 | io_uring_sqe_set_data( sqe, nullptr ); 173 | io_uring_sqe_set_flags( sqe, IOSQE_CQE_SKIP_SUCCESS ); 174 | fiona::detail::submit_ring( ring ); 175 | } 176 | } 177 | 178 | bool 179 | timer_awaitable::await_ready() const 180 | { 181 | if ( ptimer_->timeout_frame::initiated_ ) { 182 | throw_busy(); 183 | } 184 | return false; 185 | } 186 | 187 | void 188 | timer_awaitable::await_suspend( std::coroutine_handle<> h ) 189 | { 190 | auto& tf = static_cast( *ptimer_ ); 191 | if ( tf.initiated_ ) { 192 | throw_busy(); 193 | } 194 | 195 | tf.h_ = h; 196 | 197 | auto ring = detail::executor_access_policy::ring( ptimer_->ex_ ); 198 | auto sqe = detail::get_sqe( ring ); 199 | 200 | io_uring_prep_timeout( sqe, &tf.ts_, 0, 0 ); 201 | io_uring_sqe_set_data( 202 | sqe, boost::intrusive_ptr( &tf ).detach() ); 203 | 204 | tf.initiated_ = true; 205 | } 206 | 207 | result 208 | timer_awaitable::await_resume() 209 | { 210 | auto& tf = static_cast( *ptimer_ ); 211 | auto ec = std::move( tf.ec_ ); 212 | tf.reset(); 213 | if ( ec ) { 214 | return { ec }; 215 | } 216 | return {}; 217 | } 218 | 219 | timer_cancel_awaitable::timer_cancel_awaitable( 220 | boost::intrusive_ptr ptimer ) 221 | : ptimer_( ptimer ) 222 | { 223 | } 224 | 225 | timer_cancel_awaitable::~timer_cancel_awaitable() {} 226 | 227 | bool 228 | timer_cancel_awaitable::await_ready() const 229 | { 230 | if ( ptimer_->cancel_frame::initiated_ ) { 231 | throw_busy(); 232 | } 233 | return false; 234 | } 235 | 236 | void 237 | timer_cancel_awaitable::await_suspend( std::coroutine_handle<> h ) 238 | { 239 | if ( ptimer_->cancel_frame::initiated_ ) { 240 | throw_busy(); 241 | } 242 | 243 | auto& cf = static_cast( *ptimer_ ); 244 | 245 | auto ring = detail::executor_access_policy::ring( ptimer_->ex_ ); 246 | auto sqe = detail::get_sqe( ring ); 247 | io_uring_prep_cancel( 248 | sqe, static_cast( ptimer_.get() ), 0 ); 249 | io_uring_sqe_set_data( 250 | sqe, boost::intrusive_ptr( &cf ).detach() ); 251 | 252 | cf.h_ = h; 253 | cf.initiated_ = true; 254 | } 255 | 256 | result 257 | timer_cancel_awaitable::await_resume() 258 | { 259 | auto& cf = static_cast( *ptimer_ ); 260 | if ( !cf.initiated_ || !cf.done_ ) { 261 | throw_busy(); 262 | } 263 | 264 | auto ec = std::move( cf.ec_ ); 265 | cf.reset(); 266 | if ( ec ) { 267 | return { ec }; 268 | } 269 | return {}; 270 | } 271 | 272 | timer::timer( executor ex ) : ptimer_{ new detail::timer_impl( ex ) } {} 273 | 274 | timer_cancel_awaitable 275 | timer::async_cancel() 276 | { 277 | return { ptimer_ }; 278 | } 279 | 280 | executor 281 | timer::get_executor() const noexcept 282 | { 283 | return ptimer_->ex_; 284 | } 285 | 286 | } // namespace fiona 287 | -------------------------------------------------------------------------------- /include/fiona/detail/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_DETAIL_COMMON_HPP 6 | #define FIONA_DETAIL_COMMON_HPP 7 | 8 | #include // for recv_buffer 9 | #include // for io_context_params 10 | 11 | #include // for hash 12 | #include // for unordered_flat_map 13 | #include // for unordered_flat_set 14 | #include 15 | 16 | #include // for array 17 | #include // for coroutine_handle 18 | #include // for uint16_t, uint32_t 19 | #include // for size_t 20 | #include // for deque 21 | #include // for exception_ptr 22 | #include // for list 23 | #include // for mutex 24 | #include // for span 25 | #include // for vector 26 | 27 | #include // for io_uring 28 | #include 29 | 30 | struct io_uring_buf_ring; 31 | 32 | namespace fiona { 33 | 34 | class executor; 35 | 36 | class fixed_buffer 37 | { 38 | friend class file; 39 | friend class fixed_buffers; 40 | 41 | std::span buf_; 42 | std::size_t len_ = 0; 43 | std::size_t buf_index_ = 0; 44 | std::size_t* avail_buf_ids_ = nullptr; 45 | 46 | fixed_buffer( std::span buf, 47 | std::size_t buf_index, 48 | std::size_t* avail_buf_ids ) 49 | : buf_( buf ), len_{ buf.size() }, buf_index_{ buf_index }, 50 | avail_buf_ids_( avail_buf_ids ) 51 | { 52 | } 53 | 54 | public: 55 | fixed_buffer() = delete; 56 | 57 | fixed_buffer( fixed_buffer const& ) = delete; 58 | fixed_buffer& operator=( fixed_buffer const& ) = delete; 59 | 60 | ~fixed_buffer() { *avail_buf_ids_++ = buf_index_; } 61 | 62 | std::span 63 | as_bytes() const noexcept 64 | { 65 | return buf_.subspan( 0, len_ ); 66 | } 67 | 68 | void 69 | set_len( std::size_t n ) noexcept 70 | { 71 | len_ = n; 72 | } 73 | 74 | std::string_view 75 | as_str() const noexcept 76 | { 77 | return { reinterpret_cast( buf_.data() ), len_ }; 78 | } 79 | }; 80 | 81 | class fixed_buffers 82 | { 83 | friend class fiona::executor; 84 | 85 | std::vector> bufs_; 86 | std::vector iovecs_; 87 | std::vector buf_ids_; 88 | 89 | io_uring* ring_ = nullptr; 90 | std::size_t* avail_buf_ids_ = nullptr; 91 | 92 | FIONA_EXPORT 93 | fixed_buffers( io_uring* ring, unsigned num_bufs, std::size_t buf_size ); 94 | 95 | public: 96 | fixed_buffers() = delete; 97 | 98 | fixed_buffers( fixed_buffers const& ) = delete; 99 | fixed_buffers& operator=( fixed_buffers const& ) = delete; 100 | 101 | FIONA_EXPORT ~fixed_buffers(); 102 | 103 | fixed_buffer 104 | get_avail_buf() 105 | { 106 | BOOST_ASSERT( avail_buf_ids_ != buf_ids_.data() ); 107 | 108 | auto buf_index = *--avail_buf_ids_; 109 | return fixed_buffer( bufs_[buf_index], buf_index, avail_buf_ids_ ); 110 | } 111 | }; 112 | 113 | //------------------------------------------------------------------------------ 114 | 115 | namespace detail { 116 | 117 | struct buf_ring 118 | { 119 | std::vector bufs_; 120 | std::vector buf_ids_; 121 | std::size_t* buf_id_pos_ = nullptr; 122 | std::size_t buf_size_ = 0; 123 | io_uring* ring_ = nullptr; 124 | io_uring_buf_ring* buf_ring_ = nullptr; 125 | std::uint16_t bgid_ = 0; 126 | 127 | buf_ring() = delete; 128 | 129 | buf_ring( buf_ring const& ) = delete; 130 | buf_ring& operator=( buf_ring const& ) = delete; 131 | 132 | FIONA_EXPORT 133 | buf_ring( io_uring* ring, std::uint32_t num_bufs, std::uint16_t bgid ); 134 | 135 | FIONA_EXPORT 136 | buf_ring( io_uring* ring, 137 | std::uint32_t num_bufs, 138 | std::size_t buf_size, 139 | std::uint16_t bgid ); 140 | 141 | FIONA_EXPORT ~buf_ring(); 142 | 143 | recv_buffer& 144 | get_buf( std::size_t idx ) noexcept 145 | { 146 | auto& buf = bufs_[idx]; 147 | return buf; 148 | } 149 | 150 | io_uring_buf_ring* 151 | as_ptr() const noexcept 152 | { 153 | return buf_ring_; 154 | } 155 | 156 | std::uint32_t 157 | num_bufs() const noexcept 158 | { 159 | return static_cast( bufs_.size() ); 160 | } 161 | 162 | std::uint16_t 163 | bgid() const noexcept 164 | { 165 | return bgid_; 166 | } 167 | 168 | FIONA_EXPORT void recycle_buffer( recv_buffer buf ); 169 | }; 170 | 171 | //------------------------------------------------------------------------------ 172 | 173 | struct hasher 174 | { 175 | using is_transparent = void; 176 | 177 | template 178 | std::size_t 179 | operator()( std::coroutine_handle h ) const noexcept 180 | { 181 | return ( *this )( h.address() ); 182 | } 183 | 184 | std::size_t 185 | operator()( void* p ) const noexcept 186 | { 187 | boost::hash hasher; 188 | return hasher( p ); 189 | } 190 | }; 191 | 192 | struct key_equal 193 | { 194 | using is_transparent = void; 195 | 196 | template 197 | bool 198 | operator()( std::coroutine_handle const h1, 199 | std::coroutine_handle const h2 ) const noexcept 200 | { 201 | return h1.address() == h2.address(); 202 | } 203 | 204 | template 205 | bool 206 | operator()( std::coroutine_handle const h, void* p ) const noexcept 207 | { 208 | return h.address() == p; 209 | } 210 | 211 | template 212 | bool 213 | operator()( void* p, std::coroutine_handle const h ) const noexcept 214 | { 215 | return h.address() == p; 216 | } 217 | }; 218 | 219 | struct task_map 220 | { 221 | using map_type = boost:: 222 | unordered_flat_map, int*, hasher, key_equal>; 223 | using iterator = typename map_type::iterator; 224 | 225 | map_type tasks_; 226 | 227 | task_map() = default; 228 | 229 | task_map( task_map const& ) = delete; 230 | task_map& operator=( task_map const& ) = delete; 231 | 232 | bool 233 | empty() const noexcept 234 | { 235 | return tasks_.empty(); 236 | } 237 | 238 | template 239 | void 240 | add_task( std::coroutine_handle h ) 241 | { 242 | auto* p = &h.promise().count_; 243 | tasks_.emplace( h, p ); 244 | *p += 1; 245 | } 246 | 247 | void 248 | erase_task( std::coroutine_handle<> h ) 249 | { 250 | auto pos = tasks_.find( h ); 251 | BOOST_ASSERT( pos != tasks_.end() ); 252 | 253 | auto p_count = pos->second; 254 | BOOST_ASSERT( *p_count > 0 ); 255 | 256 | if ( --*p_count == 0 ) { 257 | h.destroy(); 258 | } 259 | tasks_.erase( pos ); 260 | } 261 | 262 | void 263 | clear() 264 | { 265 | while ( !tasks_.empty() ) { 266 | auto pos = tasks_.begin(); 267 | auto [h, p_count] = *pos; 268 | if ( --*p_count == 0 ) { 269 | h.destroy(); 270 | } 271 | tasks_.erase( pos ); 272 | } 273 | } 274 | }; 275 | 276 | //------------------------------------------------------------------------------ 277 | 278 | struct io_context_frame 279 | { 280 | io_uring io_ring_; 281 | std::mutex m_; 282 | task_map tasks_; 283 | io_context_params params_; 284 | std::unique_ptr fixed_buffers_; 285 | boost::unordered_node_map buf_rings_; 286 | boost::unordered_flat_set fds_; 287 | std::deque> run_queue_; 288 | std::exception_ptr exception_ptr_; 289 | std::array pipefd_ = { -1, -1 }; 290 | 291 | FIONA_EXPORT 292 | io_context_frame( io_context_params const& io_ctx_params ); 293 | 294 | FIONA_EXPORT 295 | ~io_context_frame(); 296 | }; 297 | 298 | } // namespace detail 299 | } // namespace fiona 300 | 301 | #endif // FIONA_DETAIL_COMMON_HPP 302 | -------------------------------------------------------------------------------- /test/timer_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include "helpers.hpp" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | 18 | #if BOOST_CLANG 19 | #pragma clang diagnostic ignored "-Wglobal-constructors" 20 | #pragma clang diagnostic ignored "-Wunreachable-code" 21 | #endif 22 | 23 | std::chrono::milliseconds get_sleep_duration(); 24 | 25 | static int num_runs = 0; 26 | 27 | struct seeder 28 | { 29 | seeder( unsigned seed ) { std::srand( seed ); } 30 | }; 31 | 32 | static seeder initialize_seed( 4122023 ); 33 | 34 | std::chrono::milliseconds 35 | get_sleep_duration() 36 | { 37 | return std::chrono::milliseconds{ 200 + ( std::rand() % 1000 ) }; 38 | } 39 | 40 | namespace { 41 | 42 | template 43 | fiona::task 44 | sleep_coro( fiona::timer timer, std::chrono::duration d ) 45 | { 46 | { 47 | duration_guard guard( d ); 48 | auto r = co_await timer.async_wait( d ); 49 | CHECK( r.has_value() ); 50 | } 51 | 52 | ++num_runs; 53 | co_return; 54 | } 55 | 56 | fiona::task 57 | nested_sleep_coro( fiona::executor ex ) 58 | { 59 | auto timer = fiona::timer( ex ); 60 | 61 | { 62 | auto d = get_sleep_duration(); 63 | duration_guard guard( d ); 64 | auto r = co_await timer.async_wait( d ); 65 | CHECK( r ); 66 | } 67 | 68 | { 69 | auto d = get_sleep_duration(); 70 | duration_guard guard( d ); 71 | co_await sleep_coro( timer, d ); 72 | } 73 | 74 | ++num_runs; 75 | co_return; 76 | } 77 | 78 | fiona::task 79 | nested_sleep_coro_late_return( fiona::executor ex ) 80 | { 81 | auto timer = fiona::timer( ex ); 82 | { 83 | auto d = get_sleep_duration(); 84 | duration_guard guard( d ); 85 | co_await sleep_coro( timer, d ); 86 | } 87 | 88 | { 89 | auto d = get_sleep_duration(); 90 | duration_guard guard( d ); 91 | auto r = co_await timer.async_wait( d ); 92 | CHECK( r ); 93 | } 94 | 95 | ++num_runs; 96 | } 97 | 98 | fiona::task 99 | empty_coroutine( fiona::executor ) 100 | { 101 | ++num_runs; 102 | co_return; 103 | } 104 | 105 | fiona::task 106 | nested_post_timer( fiona::executor ex ) 107 | { 108 | ex.spawn( nested_sleep_coro( ex ) ); 109 | ex.spawn( nested_sleep_coro_late_return( ex ) ); 110 | 111 | ++num_runs; 112 | co_return; 113 | } 114 | 115 | fiona::task 116 | recursion_test( fiona::executor ex, int n ) 117 | { 118 | if ( n == 0 ) { 119 | ++num_runs; 120 | co_return; 121 | } 122 | 123 | co_await recursion_test( ex, n - 1 ); 124 | } 125 | 126 | fiona::task 127 | return_value_test() 128 | { 129 | auto f = []() -> fiona::task> 130 | { co_return std::vector{ 1, 2, 3, 4 }; }; 131 | 132 | auto vec = co_await f(); 133 | CHECK_EQ( vec.size(), 4u ); 134 | 135 | auto throwing = []() -> fiona::task 136 | { 137 | throw std::logic_error( "rawr" ); 138 | co_return 1337; 139 | }; 140 | 141 | CHECK_THROWS( co_await throwing() ); 142 | 143 | ++num_runs; 144 | } 145 | 146 | } // namespace 147 | 148 | TEST_CASE( "single sleep" ) 149 | { 150 | num_runs = 0; 151 | fiona::io_context ioc; 152 | auto ex = ioc.get_executor(); 153 | auto timer = fiona::timer( ex ); 154 | ex.spawn( sleep_coro( timer, get_sleep_duration() ) ); 155 | ioc.run(); 156 | CHECK_EQ( num_runs, 1 ); 157 | } 158 | 159 | TEST_CASE( "nested coroutine invocation" ) 160 | { 161 | num_runs = 0; 162 | fiona::io_context ioc; 163 | auto ex = ioc.get_executor(); 164 | ex.spawn( nested_sleep_coro( ex ) ); 165 | ioc.run(); 166 | CHECK_EQ( num_runs, 2 ); 167 | } 168 | 169 | TEST_CASE( "nested coroutine invocation (part 2)" ) 170 | { 171 | num_runs = 0; 172 | fiona::io_context ioc; 173 | auto ex = ioc.get_executor(); 174 | ex.spawn( nested_sleep_coro_late_return( ex ) ); 175 | ioc.run(); 176 | CHECK_EQ( num_runs, 2 ); 177 | } 178 | 179 | TEST_CASE( "empty coroutine" ) 180 | { 181 | num_runs = 0; 182 | fiona::io_context ioc; 183 | auto ex = ioc.get_executor(); 184 | ex.spawn( empty_coroutine( ex ) ); 185 | ioc.run(); 186 | CHECK_EQ( num_runs, 1 ); 187 | } 188 | 189 | TEST_CASE( "multiple concurrent tasks" ) 190 | { 191 | num_runs = 0; 192 | fiona::io_context ioc; 193 | auto ex = ioc.get_executor(); 194 | auto timer = fiona::timer( ex ); 195 | ex.spawn( sleep_coro( timer, get_sleep_duration() ) ); 196 | ex.spawn( nested_sleep_coro( ex ) ); 197 | ex.spawn( nested_sleep_coro_late_return( ex ) ); 198 | ex.spawn( empty_coroutine( ex ) ); 199 | ioc.run(); 200 | CHECK_EQ( num_runs, 1 + 2 + 2 + 1 ); 201 | } 202 | 203 | TEST_CASE( "nested post() invocation" ) 204 | { 205 | num_runs = 0; 206 | fiona::io_context ioc; 207 | auto ex = ioc.get_executor(); 208 | ex.spawn( nested_post_timer( ex ) ); 209 | ioc.run(); 210 | CHECK_EQ( num_runs, 1 + 2 + 2 ); 211 | } 212 | 213 | #if defined( RUN_SYMMETRIC_TRANSFER_TESTS ) 214 | 215 | TEST_CASE( "recursion test" ) 216 | { 217 | num_runs = 0; 218 | fiona::io_context ioc; 219 | auto ex = ioc.get_executor(); 220 | for ( int i = 0; i < 10; ++i ) { 221 | ex.spawn( recursion_test( ex, 1'000'000 ) ); 222 | } 223 | ioc.run(); 224 | CHECK_EQ( num_runs, 10 ); 225 | } 226 | 227 | #endif 228 | 229 | TEST_CASE( "mild stress test" ) 230 | { 231 | num_runs = 0; 232 | fiona::io_context_params params; 233 | params.sq_entries = 4096; 234 | fiona::io_context ioc( params ); 235 | auto ex = ioc.get_executor(); 236 | 237 | std::vector timers; 238 | timers.reserve( 1000 ); 239 | 240 | for ( int i = 0; i < 1000; ++i ) { 241 | auto timer = fiona::timer( ex ); 242 | timers.push_back( std::move( timer ) ); 243 | ex.spawn( sleep_coro( timers.back(), get_sleep_duration() ) ); 244 | } 245 | ioc.run(); 246 | CHECK_EQ( num_runs, 1000 ); 247 | } 248 | 249 | TEST_CASE( "coroutine return test" ) 250 | { 251 | num_runs = 0; 252 | fiona::io_context ioc; 253 | auto ex = ioc.get_executor(); 254 | ex.spawn( return_value_test() ); 255 | ioc.run(); 256 | CHECK_EQ( num_runs, 1 ); 257 | } 258 | 259 | TEST_CASE( "reusable timer" ) 260 | { 261 | num_runs = 0; 262 | 263 | fiona::io_context ioc; 264 | auto ex = ioc.get_executor(); 265 | 266 | auto timer_op = []( fiona::executor ex ) -> fiona::task 267 | { 268 | fiona::timer timer( ex ); 269 | for ( int i = 0; i < 10; ++i ) { 270 | auto d = get_sleep_duration() / 2; 271 | duration_guard dg( d ); 272 | auto r = co_await timer.async_wait( d ); 273 | CHECK( r.has_value() ); 274 | } 275 | ++num_runs; 276 | }; 277 | 278 | for ( int i = 0; i < 1000; ++i ) { 279 | ex.spawn( timer_op( ex ) ); 280 | } 281 | 282 | ioc.run(); 283 | CHECK_EQ( num_runs, 1000 ); 284 | } 285 | 286 | TEST_CASE( "async cancellation" ) 287 | { 288 | num_runs = 0; 289 | 290 | fiona::io_context ioc; 291 | 292 | auto ex = ioc.get_executor(); 293 | ex.spawn( []( fiona::executor ex ) -> fiona::task 294 | { 295 | fiona::timer timer( ex ); 296 | 297 | auto h = fiona::spawn( ex, []( fiona::timer timer ) -> fiona::task 298 | { 299 | fiona::timer t2( timer.get_executor() ); 300 | co_await t2.async_wait( std::chrono::milliseconds( 250 ) ); 301 | auto r = co_await timer.async_cancel(); 302 | CHECK( r.has_value() ); 303 | ++num_runs; 304 | }( timer ) ); 305 | 306 | auto r = co_await timer.async_wait( std::chrono::milliseconds( 1000 ) ); 307 | CHECK( r.has_error() ); 308 | CHECK( r.error() == fiona::error_code::from_errno( ECANCELED ) ); 309 | co_await h; 310 | ++num_runs; 311 | }( ex ) ); 312 | 313 | ioc.run(); 314 | CHECK( num_runs == 2 ); 315 | } 316 | 317 | TEST_CASE( "cancellation canceled on drop" ) 318 | { 319 | num_runs = 0; 320 | 321 | fiona::io_context ioc; 322 | auto ex = ioc.get_executor(); 323 | 324 | ex.spawn( []( fiona::executor ex ) -> fiona::task 325 | { 326 | fiona::timer timer( ex ); 327 | ++num_runs; 328 | auto r = co_await timer.async_cancel(); 329 | (void)r; 330 | CHECK( false ); 331 | }( ioc.get_executor() ) ); 332 | 333 | ex.spawn( []() -> fiona::task 334 | { 335 | ++num_runs; 336 | throw 1234; 337 | co_return; 338 | }() ); 339 | 340 | CHECK_THROWS( ioc.run() ); 341 | CHECK( num_runs == 2 ); 342 | } 343 | -------------------------------------------------------------------------------- /test/post_test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #if BOOST_CLANG 16 | #pragma clang diagnostic ignored "-Wunreachable-code" 17 | #endif 18 | 19 | static std::atomic_int num_runs = 0; 20 | 21 | inline constexpr std::chrono::milliseconds sleep_dur( 25 ); 22 | 23 | namespace { 24 | 25 | fiona::task 26 | make_string( fiona::executor ex ) 27 | { 28 | ++num_runs; 29 | fiona::timer timer( ex ); 30 | auto r = co_await timer.async_wait( sleep_dur ); 31 | CHECK( r.has_value() ); 32 | co_return std::string( "hello world! this should hopefully break sbo and " 33 | "force a dynamic allocation" ); 34 | } 35 | 36 | fiona::task> 37 | make_int_pointer( fiona::executor ex ) 38 | { 39 | ++num_runs; 40 | fiona::timer timer( ex ); 41 | auto r = co_await timer.async_wait( sleep_dur ); 42 | CHECK( r.has_value() ); 43 | 44 | auto p = std::make_unique( 1337 ); 45 | co_return std::move( p ); 46 | } 47 | 48 | fiona::task 49 | throw_exception( fiona::executor ex ) 50 | { 51 | ++num_runs; 52 | fiona::timer timer( ex ); 53 | auto r = co_await timer.async_wait( sleep_dur ); 54 | CHECK( r.has_value() ); 55 | 56 | throw "random error"; 57 | 58 | co_return 1337; 59 | } 60 | 61 | } // namespace 62 | 63 | TEST_CASE( "awaiting a sibling coro" ) 64 | { 65 | num_runs = 0; 66 | 67 | fiona::io_context ioc; 68 | 69 | auto ex = ioc.get_executor(); 70 | ex.spawn( []( fiona::executor ex ) -> fiona::task 71 | { 72 | { 73 | duration_guard dg( 2 * sleep_dur ); 74 | 75 | auto str = co_await fiona::spawn( ex, make_string( ex ) ); 76 | 77 | CHECK( str == "hello world! this should hopefully break sbo and " 78 | "force a dynamic allocation" ); 79 | 80 | CHECK_THROWS( co_await fiona::spawn( ex, throw_exception( ex ) ) ); 81 | } 82 | 83 | { 84 | duration_guard dg( sleep_dur ); 85 | auto h1 = fiona::spawn( ex, make_string( ex ) ); 86 | auto h2 = fiona::spawn( ex, throw_exception( ex ) ); 87 | 88 | auto str = co_await h1; 89 | CHECK_THROWS( co_await h2 ); 90 | 91 | CHECK( str == "hello world! this should hopefully break sbo and " 92 | "force a dynamic allocation" ); 93 | } 94 | 95 | ++num_runs; 96 | co_return; 97 | }( ex ) ); 98 | 99 | ioc.run(); 100 | 101 | // 1 for each child, 2x + parent coro 102 | CHECK( num_runs == 2 * 2 + 1 ); 103 | } 104 | 105 | TEST_CASE( "ignoring exceptions" ) 106 | { 107 | // test the following: 108 | // * coroutine destroyed without a post_awaitable object in its frame 109 | // * coroutine destroyed with a post_awaitable in its frame 110 | // * destroy a nested coroutine for each of the variants above 111 | 112 | num_runs = 0; 113 | fiona::io_context ioc; 114 | 115 | auto ex = ioc.get_executor(); 116 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 117 | ++num_runs; 118 | fiona::timer timer( ex ); 119 | co_await timer.async_wait( 500ms ); 120 | CHECK( false ); 121 | co_return; 122 | }( ex ) ); 123 | 124 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 125 | auto h = fiona::spawn( ex, FIONA_TASK( fiona::executor ex ) { 126 | ++num_runs; 127 | fiona::timer timer( ex ); 128 | co_await timer.async_wait( 500ms ); 129 | CHECK( false ); 130 | co_return; 131 | }( ex ) ); 132 | 133 | (void)h; 134 | 135 | ++num_runs; 136 | fiona::timer timer( ex ); 137 | co_await timer.async_wait( 500ms ); 138 | CHECK( false ); 139 | }( ex ) ); 140 | 141 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 142 | auto inner_task = FIONA_TASK( fiona::executor ex ) 143 | { 144 | ++num_runs; 145 | fiona::timer timer( ex ); 146 | co_await timer.async_wait( 500ms ); 147 | CHECK( false ); 148 | co_return; 149 | } 150 | ( ex ); 151 | 152 | co_await inner_task; 153 | CHECK( false ); 154 | co_return; 155 | }( ex ) ); 156 | 157 | ex.spawn( FIONA_TASK( fiona::executor ex ) { 158 | auto inner_task = FIONA_TASK( fiona::executor ex ) 159 | { 160 | auto h = fiona::spawn( ex, FIONA_TASK( fiona::executor ex ) { 161 | ++num_runs; 162 | fiona::timer timer( ex ); 163 | co_await timer.async_wait( 500ms ); 164 | CHECK( false ); 165 | }( ex ) ); 166 | 167 | (void)h; 168 | 169 | fiona::spawn( ex, FIONA_TASK() { 170 | throw "a random error"; 171 | co_return; 172 | }() ); 173 | 174 | ++num_runs; 175 | fiona::timer timer( ex ); 176 | co_await timer.async_wait( 500ms ); 177 | CHECK( false ); 178 | co_return; 179 | } 180 | ( ex ); 181 | 182 | co_await inner_task; 183 | CHECK( false ); 184 | co_return; 185 | }( ex ) ); 186 | 187 | CHECK_THROWS( ioc.run() ); 188 | CHECK( num_runs == 6 ); 189 | } 190 | 191 | TEST_CASE( "posting a move-only type" ) 192 | { 193 | num_runs = 0; 194 | 195 | fiona::io_context ioc; 196 | auto ex = ioc.get_executor(); 197 | fiona::spawn( ex, []( fiona::executor ex ) -> fiona::task 198 | { 199 | duration_guard dg( sleep_dur ); 200 | auto h = fiona::spawn( ex, make_int_pointer( ex ) ); 201 | 202 | { 203 | auto p = co_await make_int_pointer( ex ); 204 | CHECK( *p == 1337 ); 205 | } 206 | 207 | { 208 | auto p = co_await h; 209 | CHECK( *p == 1337 ); 210 | } 211 | 212 | ++num_runs; 213 | co_return; 214 | }( ex ) ); 215 | 216 | ioc.run(); 217 | CHECK( num_runs == 3 ); 218 | } 219 | 220 | TEST_CASE( "void returning function" ) 221 | { 222 | num_runs = 0; 223 | 224 | fiona::io_context ioc; 225 | auto ex = ioc.get_executor(); 226 | 227 | ex.spawn( []( fiona::executor ex ) -> fiona::task 228 | { 229 | co_await fiona::spawn( ex, []( fiona::executor ex ) -> fiona::task 230 | { 231 | fiona::timer timer( ex ); 232 | auto r = co_await timer.async_wait( std::chrono::milliseconds( 500 ) ); 233 | CHECK( r.has_value() ); 234 | 235 | ++num_runs; 236 | }( ex ) ); 237 | 238 | ++num_runs; 239 | co_return; 240 | }( ex ) ); 241 | 242 | ioc.run(); 243 | 244 | CHECK( num_runs == 2 ); 245 | } 246 | 247 | #if defined( RUN_SYMMETRIC_TRANSFER_TESTS ) 248 | 249 | namespace { 250 | 251 | fiona::task 252 | empty_task() 253 | { 254 | co_return; 255 | } 256 | 257 | fiona::task 258 | symmetric_transfer_test() 259 | { 260 | for ( int i = 0; i < 1'000'000; ++i ) { 261 | co_await empty_task(); 262 | } 263 | ++num_runs; 264 | co_return; 265 | } 266 | 267 | } // namespace 268 | 269 | TEST_CASE( "symmetric transfer" ) 270 | { 271 | num_runs = 0; 272 | 273 | fiona::io_context ioc; 274 | auto ex = ioc.get_executor(); 275 | ex.spawn( symmetric_transfer_test() ); 276 | ioc.run(); 277 | CHECK( num_runs == 1 ); 278 | } 279 | 280 | #endif 281 | 282 | TEST_CASE( "destruction on a separate thread" ) 283 | { 284 | num_runs = 0; 285 | 286 | std::thread t; 287 | 288 | { 289 | fiona::io_context ioc; 290 | auto ex = ioc.get_executor(); 291 | 292 | ex.spawn( []( fiona::executor ex ) -> fiona::task 293 | { 294 | fiona::timer timer( ex ); 295 | auto r = co_await timer.async_wait( std::chrono::milliseconds( 250 ) ); 296 | CHECK( r.has_value() ); 297 | 298 | ++num_runs; 299 | co_return; 300 | }( ex ) ); 301 | 302 | ioc.run(); 303 | 304 | t = std::thread( [&ioc] 305 | { 306 | auto ex = ioc.get_executor(); 307 | (void)ex; 308 | std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) ); 309 | ++num_runs; 310 | } ); 311 | 312 | std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); 313 | } 314 | 315 | t.join(); 316 | CHECK( num_runs == 2 ); 317 | } 318 | 319 | TEST_CASE( "inter-thread posting" ) 320 | { 321 | fiona::io_context ioc; 322 | std::vector threads; 323 | 324 | int const num_threads = 8; 325 | int const num_tasks = 25'000; 326 | int const total_runs = num_threads * num_tasks; 327 | static int num_runs = 0; 328 | 329 | auto ex = ioc.get_executor(); 330 | fiona::timer timer( ex ); 331 | 332 | auto task = []( fiona::timer& timer ) -> fiona::task 333 | { 334 | fiona::timer t( timer.get_executor() ); 335 | co_await t.async_wait( 250ms ); 336 | 337 | ++num_runs; 338 | if ( num_runs == total_runs ) { 339 | co_await timer.async_cancel(); 340 | } 341 | 342 | co_return; 343 | }; 344 | 345 | ex.spawn( FIONA_TASK( fiona::timer timer ) { 346 | co_await timer.async_wait( 100s ); 347 | co_return; 348 | }( timer ) ); 349 | 350 | for ( int i = 0; i < num_threads; ++i ) { 351 | threads.emplace_back( [ex, task, &timer] 352 | { 353 | for ( int j = 0; j < num_tasks; ++j ) { 354 | ex.post( task( timer ) ); 355 | } 356 | } ); 357 | } 358 | 359 | ioc.run(); 360 | CHECK( num_runs == num_threads * num_tasks ); 361 | } 362 | -------------------------------------------------------------------------------- /test/buffer_test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE( "recv_buffer" ) 10 | { 11 | { 12 | fiona::recv_buffer buf; 13 | CHECK( buf.data() != nullptr ); 14 | CHECK( buf.size() == 0 ); 15 | CHECK( buf.capacity() == 0 ); 16 | CHECK( buf.empty() ); 17 | CHECK( buf.as_str() == "" ); 18 | CHECK( buf.readable_bytes().empty() ); 19 | CHECK( buf.spare_capacity_mut().empty() ); 20 | } 21 | 22 | { 23 | fiona::recv_buffer buf( 1024 ); 24 | CHECK( buf.data() != nullptr ); 25 | CHECK( buf.size() == 0 ); 26 | CHECK( buf.capacity() == 1024 ); 27 | CHECK( buf.empty() ); 28 | CHECK( buf.as_str() == "" ); 29 | CHECK( buf.readable_bytes().empty() ); 30 | CHECK( buf.spare_capacity_mut().size() == buf.capacity() ); 31 | } 32 | 33 | { 34 | fiona::recv_buffer buf0( 1024 ); 35 | for ( auto& b : buf0.spare_capacity_mut() ) { 36 | b = 0x12; 37 | } 38 | buf0.set_len( buf0.capacity() ); 39 | 40 | fiona::recv_buffer buf( std::move( buf0 ) ); 41 | 42 | CHECK( buf0.data() != nullptr ); 43 | CHECK( buf0.size() == 0 ); 44 | CHECK( buf0.capacity() == 0 ); 45 | CHECK( buf0.empty() ); 46 | CHECK( buf0.as_str() == "" ); 47 | CHECK( buf0.readable_bytes().empty() ); 48 | CHECK( buf0.spare_capacity_mut().empty() ); 49 | 50 | CHECK( buf.data() != nullptr ); 51 | CHECK( buf.size() == buf.capacity() ); 52 | CHECK( buf.capacity() == 1024 ); 53 | CHECK( !buf.empty() ); 54 | CHECK( buf.as_str() != "" ); 55 | CHECK( buf.readable_bytes().size() == buf.capacity() ); 56 | CHECK( buf.spare_capacity_mut().empty() ); 57 | } 58 | 59 | { 60 | fiona::recv_buffer buf0( 1024 ); 61 | fiona::recv_buffer buf; 62 | 63 | buf = std::move( buf0 ); 64 | 65 | CHECK( buf0.data() != nullptr ); 66 | CHECK( buf0.size() == 0 ); 67 | CHECK( buf0.capacity() == 0 ); 68 | CHECK( buf0.empty() ); 69 | CHECK( buf0.as_str() == "" ); 70 | CHECK( buf0.readable_bytes().empty() ); 71 | CHECK( buf0.spare_capacity_mut().empty() ); 72 | 73 | CHECK( buf.data() != nullptr ); 74 | CHECK( buf.size() == 0 ); 75 | CHECK( buf.capacity() == 1024 ); 76 | CHECK( buf.empty() ); 77 | CHECK( buf.as_str() == "" ); 78 | CHECK( buf.readable_bytes().empty() ); 79 | CHECK( buf.spare_capacity_mut().size() == 1024 ); 80 | } 81 | } 82 | 83 | TEST_CASE( "owned buffer sequence" ) 84 | { 85 | fiona::recv_buffer_sequence buf_seq; 86 | CHECK( buf_seq.num_bufs() == 0 ); 87 | CHECK( buf_seq.empty() ); 88 | 89 | auto register_buffer = [&buf_seq]( std::string_view str ) 90 | { 91 | fiona::recv_buffer buf( 1024 ); 92 | std::ranges::copy( str, buf.spare_capacity_mut().begin() ); 93 | buf.set_len( str.size() ); 94 | CHECK( buf.as_str() == str ); 95 | buf_seq.push_back( std::move( buf ) ); 96 | }; 97 | 98 | std::string_view str1 = "hello, world!"; 99 | std::string_view str2 = "I love C++!"; 100 | std::string_view str3 = "io_uring is pretty great too."; 101 | std::string_view str4 = "Asio was the inspiration for Fiona. It was a true " 102 | "pioneer of its time and Fiona can only " 103 | "hope to hold a candle to its immense success."; 104 | 105 | std::array strs = { str1, str2, str3, str4 }; 106 | 107 | for ( auto str : strs ) { 108 | register_buffer( str ); 109 | } 110 | 111 | CHECK( buf_seq.num_bufs() == strs.size() ); 112 | 113 | auto pos = buf_seq.begin(); 114 | 115 | fiona::recv_buffer_view buf_view = *pos; 116 | 117 | { 118 | CHECK( buf_view.as_str() == str1 ); 119 | 120 | ++pos; 121 | CHECK( ( *pos ).as_str() == str2 ); 122 | 123 | ++pos; 124 | CHECK( ( *pos ).as_str() == str3 ); 125 | 126 | ++pos; 127 | CHECK( ( *pos ).as_str() == str4 ); 128 | 129 | ++pos; 130 | } 131 | 132 | REQUIRE( pos == buf_seq.end() ); 133 | 134 | { 135 | --pos; 136 | CHECK( ( *pos ).as_str() == str4 ); 137 | 138 | --pos; 139 | CHECK( ( *pos ).as_str() == str3 ); 140 | 141 | --pos; 142 | CHECK( ( *pos ).as_str() == str2 ); 143 | 144 | --pos; 145 | CHECK( buf_view.as_str() == str1 ); 146 | } 147 | 148 | CHECK( pos == buf_seq.begin() ); 149 | } 150 | 151 | TEST_CASE( "end() stability" ) 152 | { 153 | fiona::recv_buffer_sequence buf_seq; 154 | auto end = buf_seq.end(); 155 | 156 | buf_seq.push_back( fiona::recv_buffer( 128 ) ); 157 | CHECK( end == buf_seq.end() ); 158 | CHECK( ( *--end ).capacity() == 128 ); 159 | CHECK( end == buf_seq.begin() ); 160 | } 161 | 162 | TEST_CASE( "move stability" ) 163 | { 164 | fiona::recv_buffer_sequence buf_seq; 165 | for ( int i = 0; i < 16; ++i ) { 166 | buf_seq.push_back( fiona::recv_buffer( 128 ) ); 167 | } 168 | 169 | CHECK( std::ranges::distance( buf_seq ) == 16 ); 170 | std::ranges::for_each( buf_seq, []( fiona::recv_buffer_view buf ) 171 | { CHECK( buf.capacity() == 128 ); } ); 172 | 173 | std::vector old_addrs( 16 ); 174 | std::ranges::transform( buf_seq, old_addrs.begin(), 175 | []( fiona::recv_buffer_view b ) 176 | { return b.data(); } ); 177 | 178 | auto pos = buf_seq.begin(); 179 | 180 | fiona::recv_buffer_sequence buf_seq2( std::move( buf_seq ) ); 181 | while ( pos != buf_seq2.end() ) { 182 | CHECK( ( *pos ).capacity() == 128 ); 183 | ++pos; 184 | } 185 | 186 | CHECK( std::ranges::equal( old_addrs, buf_seq2, {}, {}, 187 | []( fiona::recv_buffer_view b ) 188 | { return b.data(); } ) ); 189 | 190 | CHECK( buf_seq.end() != buf_seq2.end() ); 191 | 192 | for ( int i = 0; i < 8; ++i ) { 193 | buf_seq.push_back( fiona::recv_buffer( 256 ) ); 194 | } 195 | 196 | CHECK( std::ranges::distance( buf_seq ) == 8 ); 197 | std::ranges::for_each( buf_seq, []( fiona::recv_buffer_view buf ) 198 | { CHECK( buf.capacity() == 256 ); } ); 199 | } 200 | 201 | TEST_CASE( "push_back empty" ) 202 | { 203 | fiona::recv_buffer_sequence bs; 204 | bs.push_back( fiona::recv_buffer( 0 ) ); 205 | CHECK( bs.num_bufs() == 1 ); 206 | } 207 | 208 | TEST_CASE( "move construct empty" ) 209 | { 210 | fiona::recv_buffer_sequence bufs; 211 | fiona::recv_buffer_sequence bufs2( std::move( bufs ) ); 212 | 213 | CHECK( bufs.empty() ); 214 | CHECK( bufs.begin() == bufs.end() ); 215 | } 216 | 217 | TEST_CASE( "move assign empty" ) 218 | { 219 | fiona::recv_buffer_sequence bufs; 220 | bufs.push_back( fiona::recv_buffer( 1024 ) ); 221 | 222 | fiona::recv_buffer_sequence bufs2; 223 | bufs = std::move( bufs2 ); 224 | CHECK( bufs.empty() ); 225 | CHECK( bufs.begin() == bufs.end() ); 226 | 227 | bufs2.push_back( fiona::recv_buffer( 512 ) ); 228 | CHECK( bufs2.num_bufs() == 1 ); 229 | 230 | bufs = std::move( bufs2 ); 231 | CHECK( bufs2.empty() ); 232 | CHECK( bufs2.begin() == bufs2.end() ); 233 | CHECK( bufs.num_bufs() == 1 ); 234 | } 235 | 236 | TEST_CASE( "concat" ) 237 | { 238 | { 239 | fiona::recv_buffer_sequence bs1, bs2; 240 | bs1.concat( std::move( bs2 ) ); 241 | CHECK( bs1.empty() ); 242 | CHECK( bs2.empty() ); 243 | } 244 | 245 | { 246 | fiona::recv_buffer_sequence bs1, bs2; 247 | bs1.push_back( fiona::recv_buffer( 1 ) ); 248 | bs1.push_back( fiona::recv_buffer( 2 ) ); 249 | 250 | bs1.concat( std::move( bs2 ) ); 251 | { 252 | CHECK( bs1.num_bufs() == 2 ); 253 | CHECK( bs2.num_bufs() == 0 ); 254 | 255 | unsigned cap = 0; 256 | for ( auto bv : bs1 ) { 257 | CHECK( bv.capacity() == ++cap ); 258 | } 259 | } 260 | } 261 | 262 | { 263 | fiona::recv_buffer_sequence bs1, bs2; 264 | bs2.push_back( fiona::recv_buffer( 1 ) ); 265 | bs2.push_back( fiona::recv_buffer( 2 ) ); 266 | 267 | bs1.concat( std::move( bs2 ) ); 268 | { 269 | CHECK( bs1.num_bufs() == 2 ); 270 | CHECK( bs2.num_bufs() == 0 ); 271 | 272 | unsigned cap = 0; 273 | for ( auto bv : bs1 ) { 274 | CHECK( bv.capacity() == ++cap ); 275 | } 276 | } 277 | } 278 | 279 | { 280 | fiona::recv_buffer_sequence bs1, bs2; 281 | bs1.push_back( fiona::recv_buffer( 1 ) ); 282 | bs1.push_back( fiona::recv_buffer( 2 ) ); 283 | bs2.push_back( fiona::recv_buffer( 3 ) ); 284 | bs2.push_back( fiona::recv_buffer( 4 ) ); 285 | 286 | auto p1 = bs1.begin(); 287 | auto p2 = bs2.begin(); 288 | 289 | auto end1 = bs1.end(); 290 | 291 | bs1.concat( std::move( bs2 ) ); 292 | 293 | { 294 | CHECK( bs1.num_bufs() == 4 ); 295 | CHECK( bs2.num_bufs() == 0 ); 296 | 297 | unsigned cap = 0; 298 | for ( auto bv : bs1 ) { 299 | CHECK( bv.capacity() == ++cap ); 300 | } 301 | } 302 | 303 | { 304 | CHECK( ( *--end1 ).capacity() == 4 ); 305 | 306 | CHECK( ( *p2 ).capacity() == 3 ); 307 | --p2; 308 | --p2; 309 | CHECK( p1 == p2 ); 310 | 311 | CHECK( ( *p1 ).capacity() == 1 ); 312 | ++p1; 313 | CHECK( ( *++p1 ).capacity() == 3 ); 314 | CHECK( ( *++p1 ).capacity() == 4 ); 315 | CHECK( ++p1 == bs1.end() ); 316 | } 317 | } 318 | } 319 | 320 | TEST_CASE( "pop_front" ) 321 | { 322 | auto string_to_buf = []( std::string_view str ) 323 | { 324 | fiona::recv_buffer buf( str.size() ); 325 | auto out = buf.spare_capacity_mut().begin(); 326 | auto r = std::ranges::copy( str, out ); 327 | buf.set_len( static_cast( r.out - out ) ); 328 | return buf; 329 | }; 330 | 331 | auto verify_range = []( fiona::recv_buffer_sequence& buf_seq ) 332 | { 333 | for ( auto view : buf_seq ) { 334 | CHECK( view.size() > 0 ); 335 | } 336 | }; 337 | 338 | fiona::recv_buffer_sequence bufs; 339 | bufs.push_back( string_to_buf( "rocky" ) ); 340 | bufs.push_back( string_to_buf( "chase" ) ); 341 | bufs.push_back( string_to_buf( "rubble" ) ); 342 | bufs.push_back( string_to_buf( "zouma" ) ); 343 | bufs.push_back( string_to_buf( "skye" ) ); 344 | 345 | CHECK( bufs.num_bufs() == 5 ); 346 | 347 | CHECK( bufs.pop_front().as_str() == "rocky" ); 348 | CHECK( ( *bufs.begin() ).as_str() == "chase" ); 349 | verify_range( bufs ); 350 | CHECK( bufs.num_bufs() == 4 ); 351 | 352 | CHECK( bufs.pop_front().as_str() == "chase" ); 353 | CHECK( ( *bufs.begin() ).as_str() == "rubble" ); 354 | verify_range( bufs ); 355 | CHECK( bufs.num_bufs() == 3 ); 356 | 357 | CHECK( bufs.pop_front().as_str() == "rubble" ); 358 | CHECK( ( *bufs.begin() ).as_str() == "zouma" ); 359 | verify_range( bufs ); 360 | CHECK( bufs.num_bufs() == 2 ); 361 | 362 | CHECK( bufs.pop_front().as_str() == "zouma" ); 363 | CHECK( ( *bufs.begin() ).as_str() == "skye" ); 364 | verify_range( bufs ); 365 | CHECK( bufs.num_bufs() == 1 ); 366 | 367 | CHECK( bufs.pop_front().as_str() == "skye" ); 368 | CHECK( bufs.begin() == bufs.end() ); 369 | verify_range( bufs ); 370 | CHECK( bufs.num_bufs() == 0 ); 371 | } 372 | 373 | TEST_CASE( "google failure" ) 374 | { 375 | fiona::recv_buffer_sequence bufs; 376 | 377 | for ( int i = 0; i < 2; ++i ) { 378 | for ( int j = 0; j < 5; ++j ) { 379 | bufs.push_back( fiona::recv_buffer( 256 ) ); 380 | } 381 | 382 | for ( int j = 0; j < 5; ++j ) { 383 | auto buf = bufs.pop_front(); 384 | CHECK( buf.capacity() > 0 ); 385 | } 386 | } 387 | 388 | CHECK( bufs.empty() ); 389 | } 390 | -------------------------------------------------------------------------------- /src/io_context.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | #include // for io_context_frame 8 | #include // for reserve_sqes, subm... 9 | #include // for throw_errno_as_err... 10 | #include // for executor_access_po... 11 | #include // for io_context_params 12 | #include // for task 13 | 14 | #include // for BOOST_ASSERT 15 | #include // for exchange 16 | #include // for intrusive_ptr 17 | #include // for unordered_flat_set 18 | 19 | #include // for copy 20 | #include // for array 21 | #include 22 | #include // for coroutine_handle 23 | #include // for memcpy, size_t 24 | #include // for deque 25 | #include // for exception_ptr, ret... 26 | #include // for shared_ptr, __shar... 27 | #include // for move, pair 28 | #include // for vector 29 | 30 | #include // for errno 31 | #include // for io_uring_cqe_get_data 32 | #include // for io_uring_cqe, io_u... 33 | #include // for close, pipe 34 | 35 | #include "detail/awaitable_base.hpp" // for awaitable_base 36 | 37 | namespace { 38 | 39 | struct frame : public fiona::detail::awaitable_base 40 | { 41 | fiona::executor ex_; 42 | std::coroutine_handle<> h_ = nullptr; 43 | alignas( std::coroutine_handle<> ) unsigned char buffer_[sizeof( 44 | std::coroutine_handle<> )] = {}; 45 | int fd_ = -1; 46 | 47 | frame( fiona::executor ex, int fd ) : ex_{ ex }, fd_{ fd } {} 48 | 49 | void 50 | schedule_recv() 51 | { 52 | auto ring = fiona::detail::executor_access_policy::ring( ex_ ); 53 | fiona::detail::reserve_sqes( ring, 1 ); 54 | auto sqe = io_uring_get_sqe( ring ); 55 | io_uring_prep_read( sqe, fd_, buffer_, sizeof( buffer_ ), 0 ); 56 | io_uring_sqe_set_data( sqe, this ); 57 | intrusive_ptr_add_ref( this ); 58 | } 59 | 60 | void 61 | await_process_cqe( io_uring_cqe* cqe ) override 62 | { 63 | if ( cqe->res != sizeof( void* ) ) { 64 | BOOST_ASSERT( cqe->res < 0 ); 65 | fiona::detail::throw_errno_as_error_code( -cqe->res ); 66 | } 67 | 68 | std::uintptr_t data = 0; 69 | std::memcpy( &data, buffer_, sizeof( data ) ); 70 | 71 | if ( data & fiona::detail::wake_mask ) { 72 | data &= fiona::detail::ptr_mask; 73 | h_ = std::coroutine_handle<>::from_address( 74 | reinterpret_cast( data ) ); 75 | 76 | } else if ( data & fiona::detail::post_mask ) { 77 | // TODO: determine if tsan is just giving us false positives here and if 78 | // we should remove lock/unlocking the mutex here 79 | { 80 | auto guard = fiona::detail::executor_access_policy::lock_guard( ex_ ); 81 | } 82 | 83 | data &= fiona::detail::ptr_mask; 84 | 85 | auto& tasks = fiona::detail::executor_access_policy::tasks( ex_ ); 86 | auto& run_queue = fiona::detail::executor_access_policy::run_queue( ex_ ); 87 | 88 | auto task = 89 | fiona::task::from_address( reinterpret_cast( data ) ); 90 | auto internal_task = fiona::detail::scheduler( tasks, std::move( task ) ); 91 | tasks.add_task( internal_task.h_ ); 92 | run_queue.push_back( internal_task.h_ ); 93 | } 94 | 95 | schedule_recv(); 96 | } 97 | 98 | std::coroutine_handle<> 99 | handle() noexcept override 100 | { 101 | return boost::exchange( h_, nullptr ); 102 | } 103 | }; 104 | 105 | struct pipe_awaitable 106 | { 107 | boost::intrusive_ptr p_; 108 | 109 | pipe_awaitable( fiona::executor ex, int fd ) : p_( new frame( ex, fd ) ) 110 | { 111 | p_->schedule_recv(); 112 | } 113 | 114 | ~pipe_awaitable() { cancel(); } 115 | 116 | void 117 | cancel() 118 | { 119 | auto& self = *p_; 120 | auto ring = fiona::detail::executor_access_policy::ring( self.ex_ ); 121 | fiona::detail::reserve_sqes( ring, 1 ); 122 | auto sqe = io_uring_get_sqe( ring ); 123 | io_uring_sqe_set_data( sqe, nullptr ); 124 | io_uring_prep_cancel( sqe, p_.get(), 0 ); 125 | fiona::detail::submit_ring( ring ); 126 | } 127 | }; 128 | 129 | struct cqe_guard 130 | { 131 | io_uring* ring; 132 | io_uring_cqe* cqe; 133 | 134 | cqe_guard( io_uring* ring_, io_uring_cqe* cqe_ ) : ring{ ring_ }, cqe{ cqe_ } 135 | { 136 | } 137 | ~cqe_guard() { io_uring_cqe_seen( ring, cqe ); } 138 | }; 139 | 140 | struct guard 141 | { 142 | fiona::detail::task_map& tasks; 143 | io_uring* ring; 144 | 145 | ~guard() 146 | { 147 | io_uring_submit( ring ); 148 | 149 | tasks.clear(); 150 | 151 | boost::unordered_flat_set blacklist; 152 | 153 | io_uring_cqe* cqe = nullptr; 154 | while ( 0 == io_uring_peek_cqe( ring, &cqe ) ) { 155 | auto guard = cqe_guard( ring, cqe ); 156 | auto p = io_uring_cqe_get_data( cqe ); 157 | 158 | if ( p == nullptr ) { 159 | continue; 160 | } 161 | 162 | if ( blacklist.contains( p ) ) { 163 | // when destructing, we can have N CQEs associated with an awaitable 164 | // because of multishot ops 165 | // our normal I/O loop handles this case fine, but during cleanup 166 | // like this we need to be aware that multiple CQEs can share the 167 | // same user_data 168 | continue; 169 | } 170 | 171 | blacklist.insert( p ); 172 | 173 | intrusive_ptr_release( static_cast( p ) ); 174 | } 175 | } 176 | }; 177 | } // namespace 178 | 179 | namespace fiona { 180 | 181 | io_context::~io_context() 182 | { 183 | { 184 | auto ex = get_executor(); 185 | auto ring = detail::executor_access_policy::ring( ex ); 186 | auto& tasks = p_frame_->tasks_; 187 | 188 | guard g{ tasks, ring }; 189 | } 190 | p_frame_ = nullptr; 191 | } 192 | 193 | executor 194 | io_context::get_executor() const noexcept 195 | { 196 | return executor{ p_frame_ }; 197 | } 198 | 199 | void 200 | io_context::run() 201 | { 202 | struct advance_guard 203 | { 204 | io_uring* ring = nullptr; 205 | unsigned count = 0; 206 | ~advance_guard() 207 | { 208 | if ( ring ) { 209 | io_uring_cq_advance( ring, count ); 210 | } 211 | } 212 | }; 213 | 214 | auto ex = get_executor(); 215 | auto ring = detail::executor_access_policy::ring( ex ); 216 | auto cqes = std::vector( p_frame_->params_.cq_entries ); 217 | auto& tasks = p_frame_->tasks_; 218 | 219 | guard g{ tasks, ring }; 220 | { 221 | pipe_awaitable pipe_awaiter( 222 | ex, detail::executor_access_policy::get_pipefd( ex ) ); 223 | 224 | std::vector> handles( 225 | p_frame_->params_.cq_entries ); 226 | 227 | while ( !tasks.empty() ) { 228 | if ( BOOST_UNLIKELY( static_cast( p_frame_->exception_ptr_ ) ) ) { 229 | auto p = p_frame_->exception_ptr_; 230 | std::rethrow_exception( p_frame_->exception_ptr_ ); 231 | } 232 | 233 | auto& run_queue = p_frame_->run_queue_; 234 | while ( !run_queue.empty() ) { 235 | auto h = run_queue.front(); 236 | h.resume(); 237 | run_queue.pop_front(); 238 | 239 | if ( BOOST_UNLIKELY( static_cast( p_frame_->exception_ptr_ ) ) ) { 240 | auto p = p_frame_->exception_ptr_; 241 | std::rethrow_exception( p_frame_->exception_ptr_ ); 242 | } 243 | } 244 | 245 | if ( tasks.empty() ) { 246 | // if tasks are empty and we don't break here, we get stuck waiting 247 | // forever for I/O that'll never come 248 | break; 249 | } 250 | 251 | io_uring_submit_and_wait( ring, 1 ); 252 | 253 | auto num_ready = io_uring_cq_ready( ring ); 254 | io_uring_peek_batch_cqe( ring, cqes.data(), num_ready ); 255 | 256 | auto phandles = handles.begin(); 257 | 258 | { 259 | advance_guard cqe_guard{ ring, 0 }; 260 | for ( ; cqe_guard.count < num_ready; ++cqe_guard.count ) { 261 | auto cqe = cqes[cqe_guard.count]; 262 | auto p = io_uring_cqe_get_data( cqe ); 263 | if ( !p ) { 264 | continue; 265 | } 266 | 267 | boost::intrusive_ptr q( 268 | static_cast( p ), false ); 269 | BOOST_ASSERT( q->use_count() >= 1 ); 270 | 271 | q->await_process_cqe( cqe ); 272 | if ( auto h = q->handle(); h ) { 273 | *phandles++ = h; 274 | } 275 | 276 | if ( BOOST_UNLIKELY( 277 | static_cast( p_frame_->exception_ptr_ ) ) ) { 278 | auto p = p_frame_->exception_ptr_; 279 | std::rethrow_exception( p_frame_->exception_ptr_ ); 280 | } 281 | } 282 | } 283 | 284 | for ( auto pos = handles.begin(); pos < phandles; ++pos ) { 285 | pos->resume(); 286 | } 287 | } 288 | 289 | if ( BOOST_UNLIKELY( static_cast( p_frame_->exception_ptr_ ) ) ) { 290 | auto p = p_frame_->exception_ptr_; 291 | std::rethrow_exception( p_frame_->exception_ptr_ ); 292 | } 293 | } 294 | } 295 | 296 | namespace detail { 297 | 298 | io_context_frame::io_context_frame( io_context_params const& io_ctx_params ) 299 | : params_( io_ctx_params ) 300 | { 301 | 302 | int ret = -1; 303 | auto ring = &io_ring_; 304 | 305 | ret = pipe( pipefd_.data() ); 306 | if ( ret == -1 ) { 307 | detail::throw_errno_as_error_code( errno ); 308 | } 309 | 310 | io_uring_params params = {}; 311 | params.cq_entries = io_ctx_params.cq_entries; 312 | 313 | { 314 | auto& flags = params.flags; 315 | flags |= IORING_SETUP_CQSIZE; 316 | flags |= IORING_SETUP_SINGLE_ISSUER; 317 | // flags |= IORING_SETUP_COOP_TASKRUN; 318 | // flags |= IORING_SETUP_TASKRUN_FLAG; 319 | flags |= IORING_SETUP_DEFER_TASKRUN; 320 | } 321 | 322 | ret = io_uring_queue_init_params( params_.sq_entries, ring, ¶ms ); 323 | if ( ret != 0 ) { 324 | fiona::detail::throw_errno_as_error_code( -ret ); 325 | } 326 | 327 | ret = io_uring_register_ring_fd( ring ); 328 | if ( ret != 1 ) { 329 | fiona::detail::throw_errno_as_error_code( -ret ); 330 | } 331 | 332 | auto const num_files = params_.num_files; 333 | ret = io_uring_register_files_sparse( ring, num_files ); 334 | if ( ret != 0 ) { 335 | fiona::detail::throw_errno_as_error_code( -ret ); 336 | } 337 | 338 | fds_.reserve( num_files ); 339 | for ( int i = 0; i < static_cast( num_files ); ++i ) { 340 | fds_.insert( i ); 341 | } 342 | } 343 | 344 | io_context_frame::~io_context_frame() 345 | { 346 | close( pipefd_[0] ); 347 | close( pipefd_[1] ); 348 | 349 | auto ring = &io_ring_; 350 | io_uring_queue_exit( ring ); 351 | } 352 | 353 | } // namespace detail 354 | 355 | } // namespace fiona 356 | -------------------------------------------------------------------------------- /src/file.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "detail/awaitable_base.hpp" 10 | 11 | namespace fiona { 12 | namespace detail { 13 | 14 | struct file_impl; 15 | 16 | struct open_frame : awaitable_base 17 | { 18 | std::coroutine_handle<> h_ = nullptr; 19 | 20 | int temp_fd_ = -1; 21 | std::string pathname_; 22 | int flags_ = -1; 23 | 24 | int res_ = 0; 25 | bool initiated_ = false; 26 | bool done_ = false; 27 | 28 | open_frame() = default; 29 | 30 | open_frame( open_frame const& ) = delete; 31 | open_frame& operator=( open_frame const& ) = delete; 32 | 33 | ~open_frame() override; 34 | 35 | void 36 | reset() 37 | { 38 | h_ = nullptr; 39 | temp_fd_ = -1; 40 | pathname_.clear(); 41 | flags_ = -1; 42 | res_ = 0; 43 | initiated_ = false; 44 | done_ = false; 45 | } 46 | 47 | void await_process_cqe( io_uring_cqe* cqe ) override; 48 | 49 | std::coroutine_handle<> 50 | handle() noexcept override 51 | { 52 | return boost::exchange( h_, nullptr ); 53 | } 54 | }; 55 | 56 | open_frame::~open_frame() = default; 57 | 58 | //------------------------------------------------------------------------------ 59 | 60 | struct write_frame : awaitable_base 61 | { 62 | std::span buf_; 63 | std::coroutine_handle<> h_ = nullptr; 64 | int buf_index_ = -1; 65 | int res_ = 0; 66 | bool initiated_ = false; 67 | bool done_ = false; 68 | 69 | write_frame() = default; 70 | 71 | write_frame( write_frame const& ) = delete; 72 | write_frame& operator=( write_frame const& ) = delete; 73 | 74 | ~write_frame() override; 75 | 76 | void 77 | reset() 78 | { 79 | buf_ = {}; 80 | h_ = nullptr; 81 | buf_index_ = -1; 82 | res_ = 0; 83 | initiated_ = false; 84 | done_ = false; 85 | } 86 | 87 | void await_process_cqe( io_uring_cqe* cqe ) override; 88 | 89 | std::coroutine_handle<> 90 | handle() noexcept override 91 | { 92 | return boost::exchange( h_, nullptr ); 93 | } 94 | }; 95 | 96 | write_frame::~write_frame() = default; 97 | 98 | //------------------------------------------------------------------------------ 99 | 100 | struct read_frame : awaitable_base 101 | { 102 | std::coroutine_handle<> h_ = nullptr; 103 | std::span buf_; 104 | int buf_index_ = -1; 105 | int res_ = 0; 106 | bool initiated_ = false; 107 | bool done_ = false; 108 | 109 | read_frame() = default; 110 | 111 | read_frame( read_frame const& ) = delete; 112 | read_frame& operator=( read_frame const& ) = delete; 113 | 114 | ~read_frame() override; 115 | 116 | void 117 | reset() 118 | { 119 | h_ = nullptr; 120 | buf_ = {}; 121 | buf_index_ = -1; 122 | res_ = 0; 123 | initiated_ = false; 124 | done_ = false; 125 | } 126 | 127 | void await_process_cqe( io_uring_cqe* cqe ) override; 128 | 129 | std::coroutine_handle<> 130 | handle() noexcept override 131 | { 132 | return boost::exchange( h_, nullptr ); 133 | } 134 | }; 135 | 136 | read_frame::~read_frame() = default; 137 | 138 | //------------------------------------------------------------------------------ 139 | 140 | struct file_impl final : virtual ref_count, open_frame, write_frame, read_frame 141 | { 142 | executor ex_; 143 | int fd_ = -1; 144 | 145 | file_impl( executor ex ) : ex_( ex ) {} 146 | ~file_impl() override; 147 | }; 148 | 149 | file_impl::~file_impl() 150 | { 151 | if ( fd_ >= 0 ) { 152 | auto ring = executor_access_policy::ring( ex_ ); 153 | auto sqe = get_sqe( ring ); 154 | 155 | io_uring_prep_close_direct( sqe, static_cast( fd_ ) ); 156 | io_uring_sqe_set_flags( sqe, IOSQE_CQE_SKIP_SUCCESS ); 157 | io_uring_sqe_set_data( sqe, nullptr ); 158 | submit_ring( ring ); 159 | executor_access_policy::release_fd( ex_, fd_ ); 160 | fd_ = -1; 161 | } 162 | } 163 | 164 | void FIONA_EXPORT 165 | intrusive_ptr_add_ref( file_impl* p_file ) noexcept 166 | { 167 | intrusive_ptr_add_ref( static_cast( p_file ) ); 168 | } 169 | 170 | void FIONA_EXPORT 171 | intrusive_ptr_release( file_impl* p_file ) noexcept 172 | { 173 | intrusive_ptr_release( static_cast( p_file ) ); 174 | } 175 | 176 | } // namespace detail 177 | 178 | file::file( executor ex ) : p_file_( new detail::file_impl( ex ) ) {} 179 | 180 | open_awaitable 181 | file::async_open( std::string pathname, int flags ) 182 | { 183 | auto& of = static_cast( *p_file_ ); 184 | of.pathname_ = std::move( pathname ); 185 | of.flags_ = flags; 186 | 187 | return { p_file_ }; 188 | } 189 | 190 | write_awaitable 191 | file::async_write( std::string_view msg ) 192 | { 193 | return async_write( std::span( 194 | reinterpret_cast( msg.data() ), msg.size() ) ); 195 | } 196 | 197 | write_awaitable 198 | file::async_write( std::span msg ) 199 | { 200 | auto& wf = static_cast( *p_file_ ); 201 | wf.buf_ = msg; 202 | 203 | return { p_file_ }; 204 | } 205 | 206 | write_awaitable 207 | file::async_write_fixed( fixed_buffer const& buf ) 208 | { 209 | auto& wf = static_cast( *p_file_ ); 210 | wf.buf_ = buf.as_bytes(); 211 | wf.buf_index_ = static_cast( buf.buf_index_ ); 212 | return { p_file_ }; 213 | } 214 | 215 | read_awaitable 216 | file::async_read( std::span buf ) 217 | { 218 | return async_read( 219 | std::span( reinterpret_cast( buf.data() ), buf.size() ) ); 220 | } 221 | 222 | read_awaitable 223 | file::async_read( std::span buf ) 224 | { 225 | auto& rf = static_cast( *p_file_ ); 226 | rf.buf_ = buf; 227 | 228 | return { p_file_ }; 229 | } 230 | 231 | read_awaitable 232 | file::async_read_fixed( fixed_buffer& buf ) 233 | { 234 | auto& rf = static_cast( *p_file_ ); 235 | rf.buf_ = buf.buf_; 236 | rf.buf_index_ = static_cast( buf.buf_index_ ); 237 | return { p_file_ }; 238 | } 239 | 240 | //------------------------------------------------------------------------------ 241 | 242 | void 243 | detail::open_frame::await_process_cqe( io_uring_cqe* cqe ) 244 | { 245 | auto& file_ = static_cast( *this ); 246 | 247 | done_ = true; 248 | res_ = cqe->res; 249 | 250 | if ( res_ >= 0 ) { 251 | file_.fd_ = temp_fd_; 252 | } else { 253 | BOOST_ASSERT( temp_fd_ >= 0 ); 254 | 255 | auto ex = file_.ex_; 256 | fiona::detail::executor_access_policy::release_fd( ex, temp_fd_ ); 257 | } 258 | 259 | temp_fd_ = -1; 260 | } 261 | 262 | //------------------------------------------------------------------------------ 263 | 264 | open_awaitable::~open_awaitable() 265 | { 266 | if ( p_file_->detail::open_frame::initiated_ && 267 | !p_file_->detail::open_frame::done_ ) { 268 | auto ring = fiona::detail::executor_access_policy::ring( p_file_->ex_ ); 269 | fiona::detail::reserve_sqes( ring, 1 ); 270 | auto sqe = io_uring_get_sqe( ring ); 271 | io_uring_prep_cancel( 272 | sqe, static_cast( p_file_.get() ), 0 ); 273 | io_uring_sqe_set_data( sqe, nullptr ); 274 | io_uring_sqe_set_flags( sqe, IOSQE_CQE_SKIP_SUCCESS ); 275 | fiona::detail::submit_ring( ring ); 276 | } 277 | } 278 | 279 | void 280 | open_awaitable::await_suspend( std::coroutine_handle<> h ) 281 | { 282 | auto& of = static_cast( *p_file_ ); 283 | auto ex = p_file_->ex_; 284 | auto ring = detail::executor_access_policy::ring( ex ); 285 | 286 | detail::reserve_sqes( ring, 1 ); 287 | 288 | auto file_index = detail::executor_access_policy::get_available_fd( ex ); 289 | 290 | auto sqe = io_uring_get_sqe( ring ); 291 | io_uring_prep_openat_direct( sqe, AT_FDCWD, of.pathname_.c_str(), of.flags_, 292 | 0, static_cast( file_index ) ); 293 | io_uring_sqe_set_data( sqe, 294 | static_cast( p_file_.get() ) ); 295 | 296 | intrusive_ptr_add_ref( &of ); 297 | 298 | of.initiated_ = true; 299 | of.h_ = h; 300 | of.temp_fd_ = file_index; 301 | } 302 | 303 | result 304 | open_awaitable::await_resume() 305 | { 306 | auto& of = static_cast( *p_file_ ); 307 | auto res = of.res_; 308 | of.reset(); 309 | 310 | if ( res == 0 ) { 311 | return {}; 312 | } 313 | 314 | return { error_code::from_errno( -res ) }; 315 | } 316 | 317 | //------------------------------------------------------------------------------ 318 | 319 | void 320 | detail::write_frame::await_process_cqe( io_uring_cqe* cqe ) 321 | { 322 | res_ = cqe->res; 323 | done_ = true; 324 | } 325 | 326 | //------------------------------------------------------------------------------ 327 | 328 | void 329 | detail::read_frame::await_process_cqe( io_uring_cqe* cqe ) 330 | { 331 | res_ = cqe->res; 332 | done_ = true; 333 | } 334 | 335 | //------------------------------------------------------------------------------ 336 | 337 | write_awaitable::~write_awaitable() 338 | { 339 | if ( p_file_->detail::write_frame::initiated_ && 340 | !p_file_->detail::write_frame::done_ ) { 341 | auto ring = fiona::detail::executor_access_policy::ring( p_file_->ex_ ); 342 | fiona::detail::reserve_sqes( ring, 1 ); 343 | auto sqe = io_uring_get_sqe( ring ); 344 | io_uring_prep_cancel( 345 | sqe, static_cast( p_file_.get() ), 0 ); 346 | io_uring_sqe_set_data( sqe, nullptr ); 347 | io_uring_sqe_set_flags( sqe, IOSQE_CQE_SKIP_SUCCESS ); 348 | fiona::detail::submit_ring( ring ); 349 | } 350 | } 351 | 352 | void 353 | write_awaitable::await_suspend( std::coroutine_handle<> h ) 354 | { 355 | auto& wf = static_cast( *p_file_ ); 356 | if ( wf.initiated_ ) { 357 | fiona::detail::throw_errno_as_error_code( EBUSY ); 358 | } 359 | 360 | auto ex = p_file_->ex_; 361 | auto fd = p_file_->fd_; 362 | 363 | auto ring = detail::executor_access_policy::ring( ex ); 364 | detail::reserve_sqes( ring, 1 ); 365 | 366 | { 367 | auto sqe = detail::get_sqe( ring ); 368 | 369 | unsigned offset = 0; 370 | 371 | if ( wf.buf_index_ >= 0 ) { 372 | io_uring_prep_write_fixed( sqe, fd, wf.buf_.data(), 373 | static_cast( wf.buf_.size() ), 374 | offset, wf.buf_index_ ); 375 | } else { 376 | io_uring_prep_write( sqe, fd, wf.buf_.data(), 377 | static_cast( wf.buf_.size() ), offset ); 378 | } 379 | 380 | io_uring_sqe_set_data( sqe, &wf ); 381 | io_uring_sqe_set_flags( sqe, IOSQE_FIXED_FILE ); 382 | } 383 | 384 | detail::intrusive_ptr_add_ref( &wf ); 385 | 386 | wf.initiated_ = true; 387 | wf.h_ = h; 388 | } 389 | 390 | result 391 | write_awaitable::await_resume() 392 | { 393 | auto& wf = static_cast( *p_file_ ); 394 | auto res = wf.res_; 395 | wf.reset(); 396 | 397 | if ( res < 0 ) { 398 | return { error_code::from_errno( -res ) }; 399 | } 400 | return { static_cast( res ) }; 401 | } 402 | 403 | //------------------------------------------------------------------------------ 404 | 405 | read_awaitable::~read_awaitable() 406 | { 407 | if ( p_file_->detail::read_frame::initiated_ && 408 | !p_file_->detail::read_frame::done_ ) { 409 | 410 | auto ring = fiona::detail::executor_access_policy::ring( p_file_->ex_ ); 411 | fiona::detail::reserve_sqes( ring, 1 ); 412 | auto sqe = io_uring_get_sqe( ring ); 413 | io_uring_prep_cancel( 414 | sqe, static_cast( p_file_.get() ), 0 ); 415 | io_uring_sqe_set_data( sqe, nullptr ); 416 | io_uring_sqe_set_flags( sqe, IOSQE_CQE_SKIP_SUCCESS ); 417 | fiona::detail::submit_ring( ring ); 418 | } 419 | } 420 | 421 | void 422 | read_awaitable::await_suspend( std::coroutine_handle<> h ) 423 | { 424 | auto& rf = static_cast( *p_file_ ); 425 | if ( rf.initiated_ ) { 426 | fiona::detail::throw_errno_as_error_code( EBUSY ); 427 | } 428 | 429 | auto ex = p_file_->ex_; 430 | auto fd = p_file_->fd_; 431 | 432 | auto ring = detail::executor_access_policy::ring( ex ); 433 | detail::reserve_sqes( ring, 1 ); 434 | 435 | { 436 | auto sqe = detail::get_sqe( ring ); 437 | 438 | unsigned offset = 0; 439 | 440 | if ( rf.buf_index_ >= 0 ) { 441 | io_uring_prep_read_fixed( sqe, fd, rf.buf_.data(), 442 | static_cast( rf.buf_.size() ), offset, 443 | rf.buf_index_ ); 444 | } else { 445 | io_uring_prep_read( sqe, fd, rf.buf_.data(), 446 | static_cast( rf.buf_.size() ), offset ); 447 | } 448 | 449 | io_uring_sqe_set_data( sqe, &rf ); 450 | io_uring_sqe_set_flags( sqe, IOSQE_FIXED_FILE ); 451 | } 452 | 453 | detail::intrusive_ptr_add_ref( &rf ); 454 | 455 | rf.initiated_ = true; 456 | rf.h_ = h; 457 | } 458 | 459 | result 460 | read_awaitable::await_resume() 461 | { 462 | auto& rf = static_cast( *p_file_ ); 463 | auto res = rf.res_; 464 | rf.reset(); 465 | 466 | if ( res < 0 ) { 467 | return { error_code::from_errno( -res ) }; 468 | } 469 | return { static_cast( res ) }; 470 | } 471 | 472 | } // namespace fiona 473 | -------------------------------------------------------------------------------- /include/fiona/tcp.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_TCP_HPP 6 | #define FIONA_TCP_HPP 7 | 8 | #include // for recv_buffer_sequence 9 | #include // for result 10 | #include // for executor 11 | 12 | #include // for duration_to_timespec 13 | 14 | #include // for intrusive_ptr 15 | 16 | #include // for duration 17 | #include // for coroutine_handle 18 | #include // for size_t 19 | #include // for uint16_t 20 | #include // for span 21 | #include // for string_view 22 | 23 | #include 24 | 25 | namespace fiona { 26 | namespace tls { 27 | 28 | class client; 29 | class server; 30 | 31 | } // namespace tls 32 | } // namespace fiona 33 | 34 | namespace fiona { 35 | namespace tcp { 36 | namespace detail { 37 | 38 | struct acceptor_impl; 39 | struct client_impl; 40 | struct stream_impl; 41 | 42 | } // namespace detail 43 | 44 | class accept_awaitable; 45 | class accept_raw_awaitable; 46 | class accept_cancel_awaitable; 47 | class accept_close_awaitable; 48 | class connect_awaitable; 49 | class stream_cancel_awaitable; 50 | class stream_close_awaitable; 51 | class send_awaitable; 52 | class recv_awaitable; 53 | class shutdown_awaitable; 54 | 55 | } // namespace tcp 56 | } // namespace fiona 57 | 58 | struct __kernel_timespec; 59 | struct sockaddr; 60 | struct sockaddr_in; 61 | struct sockaddr_in6; 62 | 63 | namespace fiona { 64 | namespace tcp { 65 | namespace detail { 66 | 67 | void FIONA_EXPORT intrusive_ptr_add_ref( acceptor_impl* p_acceptor ) noexcept; 68 | void FIONA_EXPORT intrusive_ptr_release( acceptor_impl* p_acceptor ) noexcept; 69 | 70 | void FIONA_EXPORT intrusive_ptr_add_ref( stream_impl* p_stream ) noexcept; 71 | void FIONA_EXPORT intrusive_ptr_release( stream_impl* p_stream ) noexcept; 72 | 73 | } // namespace detail 74 | } // namespace tcp 75 | } // namespace fiona 76 | 77 | namespace fiona { 78 | namespace tcp { 79 | 80 | class acceptor 81 | { 82 | boost::intrusive_ptr p_acceptor_; 83 | 84 | public: 85 | acceptor() = delete; 86 | 87 | acceptor( acceptor const& ) = default; 88 | acceptor& operator=( acceptor const& ) = default; 89 | 90 | acceptor( acceptor&& ) = default; 91 | acceptor& operator=( acceptor&& ) = default; 92 | 93 | FIONA_EXPORT 94 | acceptor( executor ex, sockaddr const* addr ); 95 | 96 | acceptor( executor ex, sockaddr_in const* addr ) 97 | : acceptor( ex, reinterpret_cast( addr ) ) 98 | { 99 | } 100 | 101 | acceptor( executor ex, sockaddr_in6 const* addr ) 102 | : acceptor( ex, reinterpret_cast( addr ) ) 103 | { 104 | } 105 | 106 | bool operator==( acceptor const& ) const = default; 107 | 108 | FIONA_EXPORT std::uint16_t port() const noexcept; 109 | FIONA_EXPORT executor get_executor() const noexcept; 110 | FIONA_EXPORT accept_awaitable async_accept(); 111 | FIONA_EXPORT accept_raw_awaitable async_accept_raw(); 112 | FIONA_EXPORT accept_cancel_awaitable async_cancel(); 113 | FIONA_EXPORT accept_close_awaitable async_close(); 114 | }; 115 | 116 | //------------------------------------------------------------------------------ 117 | 118 | class FIONA_EXPORT stream 119 | { 120 | friend class client; 121 | friend class tls::client; 122 | friend class tls::server; 123 | friend class accept_awaitable; 124 | 125 | boost::intrusive_ptr p_stream_; 126 | 127 | void timeout( __kernel_timespec ts ); 128 | void cancel_timer(); 129 | void cancel_recv(); 130 | 131 | public: 132 | stream() = default; 133 | stream( executor ex, int fd ); 134 | 135 | stream( stream const& ) = default; 136 | stream& operator=( stream const& ) = default; 137 | 138 | stream( stream&& ) = default; 139 | stream& operator=( stream&& ) = default; 140 | 141 | virtual ~stream(); 142 | 143 | bool operator==( stream const& ) const = default; 144 | 145 | executor get_executor() const noexcept; 146 | 147 | template 148 | void 149 | timeout( std::chrono::duration const d ) 150 | { 151 | auto ts = fiona::detail::duration_to_timespec( d ); 152 | timeout( ts ); 153 | } 154 | 155 | void set_buffer_group( std::uint16_t bgid ); 156 | 157 | stream_close_awaitable async_close(); 158 | stream_cancel_awaitable async_cancel(); 159 | send_awaitable async_send( std::string_view msg ); 160 | send_awaitable async_send( std::span buf ); 161 | recv_awaitable async_recv(); 162 | shutdown_awaitable async_shutdown( int how ); 163 | }; 164 | 165 | //------------------------------------------------------------------------------ 166 | 167 | class FIONA_EXPORT client : public stream 168 | { 169 | public: 170 | client() {} 171 | client( executor ex ); 172 | 173 | client( client const& ) = default; 174 | client& operator=( client const& ) = default; 175 | 176 | client( client&& ) = default; 177 | client& operator=( client&& ) = default; 178 | 179 | virtual ~client() override; 180 | 181 | bool operator==( client const& ) const = default; 182 | 183 | connect_awaitable async_connect( sockaddr const* addr ); 184 | connect_awaitable async_connect( sockaddr_in const* addr ); 185 | connect_awaitable async_connect( sockaddr_in6 const* addr ); 186 | }; 187 | 188 | //------------------------------------------------------------------------------ 189 | 190 | class stream_close_awaitable 191 | { 192 | friend class stream; 193 | friend class client; 194 | 195 | boost::intrusive_ptr p_stream_; 196 | 197 | stream_close_awaitable( boost::intrusive_ptr p_stream ); 198 | 199 | public: 200 | stream_close_awaitable() = delete; 201 | 202 | stream_close_awaitable( stream_close_awaitable const& ) = delete; 203 | stream_close_awaitable& operator=( stream_close_awaitable const& ) = delete; 204 | 205 | FIONA_EXPORT ~stream_close_awaitable(); 206 | 207 | bool 208 | await_ready() const noexcept 209 | { 210 | return false; 211 | } 212 | 213 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 214 | FIONA_EXPORT result await_resume(); 215 | }; 216 | 217 | //------------------------------------------------------------------------------ 218 | 219 | class stream_cancel_awaitable 220 | { 221 | friend class stream; 222 | friend class client; 223 | 224 | boost::intrusive_ptr p_stream_; 225 | 226 | stream_cancel_awaitable( boost::intrusive_ptr p_stream ); 227 | 228 | public: 229 | stream_cancel_awaitable() = delete; 230 | 231 | stream_cancel_awaitable( stream_cancel_awaitable const& ) = delete; 232 | stream_cancel_awaitable& operator=( stream_cancel_awaitable const& ) = delete; 233 | 234 | bool 235 | await_ready() const noexcept 236 | { 237 | return false; 238 | } 239 | 240 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 241 | FIONA_EXPORT result await_resume(); 242 | }; 243 | 244 | //------------------------------------------------------------------------------ 245 | 246 | class send_awaitable 247 | { 248 | friend class stream; 249 | friend class client; 250 | 251 | std::span buf_; 252 | boost::intrusive_ptr p_stream_ = nullptr; 253 | 254 | send_awaitable( std::span buf, 255 | boost::intrusive_ptr p_stream ); 256 | 257 | public: 258 | send_awaitable() = delete; 259 | 260 | send_awaitable( send_awaitable const& ) = delete; 261 | send_awaitable& operator=( send_awaitable const& ) = delete; 262 | 263 | FIONA_EXPORT ~send_awaitable(); 264 | 265 | bool 266 | await_ready() const noexcept 267 | { 268 | return false; 269 | } 270 | 271 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 272 | FIONA_EXPORT result await_resume(); 273 | }; 274 | 275 | //------------------------------------------------------------------------------ 276 | 277 | class recv_awaitable 278 | { 279 | friend class stream; 280 | 281 | boost::intrusive_ptr p_stream_ = nullptr; 282 | 283 | recv_awaitable( boost::intrusive_ptr p_stream ); 284 | 285 | public: 286 | recv_awaitable() = delete; 287 | 288 | recv_awaitable( recv_awaitable const& ) = delete; 289 | recv_awaitable& operator=( recv_awaitable const& ) = delete; 290 | 291 | FIONA_EXPORT 292 | ~recv_awaitable(); 293 | 294 | FIONA_EXPORT 295 | bool await_ready() const; 296 | 297 | FIONA_EXPORT 298 | bool await_suspend( std::coroutine_handle<> h ); 299 | 300 | FIONA_EXPORT 301 | result await_resume(); 302 | }; 303 | 304 | //------------------------------------------------------------------------------ 305 | 306 | class shutdown_awaitable 307 | { 308 | friend class stream; 309 | 310 | boost::intrusive_ptr p_stream_ = nullptr; 311 | 312 | shutdown_awaitable( boost::intrusive_ptr p_stream ); 313 | 314 | public: 315 | shutdown_awaitable() = delete; 316 | 317 | shutdown_awaitable( shutdown_awaitable const& ) = delete; 318 | shutdown_awaitable& operator=( shutdown_awaitable const& ) = delete; 319 | 320 | FIONA_EXPORT 321 | ~shutdown_awaitable(); 322 | 323 | bool 324 | await_ready() const 325 | { 326 | return false; 327 | } 328 | 329 | FIONA_EXPORT 330 | void await_suspend( std::coroutine_handle<> h ); 331 | 332 | FIONA_EXPORT 333 | result await_resume(); 334 | }; 335 | 336 | //------------------------------------------------------------------------------ 337 | 338 | class accept_awaitable 339 | { 340 | private: 341 | friend class acceptor; 342 | friend class accept_raw_awaitable; 343 | 344 | boost::intrusive_ptr p_acceptor_ = nullptr; 345 | 346 | accept_awaitable( boost::intrusive_ptr p_acceptor ); 347 | 348 | public: 349 | accept_awaitable() = delete; 350 | 351 | accept_awaitable( accept_awaitable const& ) = delete; 352 | accept_awaitable& operator=( accept_awaitable const& ) = delete; 353 | 354 | FIONA_EXPORT 355 | ~accept_awaitable(); 356 | 357 | bool 358 | await_ready() const 359 | { 360 | return false; 361 | } 362 | 363 | FIONA_EXPORT 364 | void await_suspend( std::coroutine_handle<> h ); 365 | 366 | FIONA_EXPORT 367 | result await_resume(); 368 | }; 369 | 370 | //------------------------------------------------------------------------------ 371 | 372 | class accept_raw_awaitable : public accept_awaitable 373 | { 374 | friend class acceptor; 375 | 376 | accept_raw_awaitable( 377 | boost::intrusive_ptr p_acceptor ); 378 | 379 | public: 380 | accept_raw_awaitable() = delete; 381 | 382 | accept_raw_awaitable( accept_awaitable const& ) = delete; 383 | accept_raw_awaitable& operator=( accept_raw_awaitable const& ) = delete; 384 | 385 | FIONA_EXPORT 386 | result await_resume(); 387 | }; 388 | 389 | //------------------------------------------------------------------------------ 390 | 391 | class accept_cancel_awaitable 392 | { 393 | friend class acceptor; 394 | 395 | boost::intrusive_ptr p_acceptor_; 396 | 397 | accept_cancel_awaitable( 398 | boost::intrusive_ptr p_acceptor ); 399 | 400 | public: 401 | accept_cancel_awaitable() = delete; 402 | 403 | accept_cancel_awaitable( accept_cancel_awaitable const& ) = delete; 404 | accept_cancel_awaitable& operator=( accept_cancel_awaitable const& ) = delete; 405 | 406 | bool 407 | await_ready() const noexcept 408 | { 409 | return false; 410 | } 411 | 412 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 413 | FIONA_EXPORT result await_resume(); 414 | }; 415 | 416 | //------------------------------------------------------------------------------ 417 | 418 | class accept_close_awaitable 419 | { 420 | friend class acceptor; 421 | 422 | boost::intrusive_ptr p_acceptor_; 423 | 424 | accept_close_awaitable( 425 | boost::intrusive_ptr p_acceptor ) 426 | : p_acceptor_( p_acceptor ) 427 | { 428 | } 429 | 430 | public: 431 | accept_close_awaitable() = delete; 432 | 433 | accept_close_awaitable( accept_close_awaitable const& ) = delete; 434 | accept_close_awaitable& operator=( accept_close_awaitable const& ) = delete; 435 | 436 | bool 437 | await_ready() const noexcept 438 | { 439 | return false; 440 | } 441 | 442 | FIONA_EXPORT void await_suspend( std::coroutine_handle<> h ); 443 | FIONA_EXPORT result await_resume(); 444 | }; 445 | 446 | //------------------------------------------------------------------------------ 447 | 448 | class connect_awaitable 449 | { 450 | friend class client; 451 | 452 | boost::intrusive_ptr pstream_ = nullptr; 453 | 454 | connect_awaitable( boost::intrusive_ptr pstream ); 455 | 456 | public: 457 | connect_awaitable() = delete; 458 | 459 | connect_awaitable( connect_awaitable const& ) = delete; 460 | connect_awaitable& operator=( connect_awaitable const& ) = delete; 461 | 462 | FIONA_EXPORT 463 | ~connect_awaitable(); 464 | 465 | FIONA_EXPORT 466 | bool await_ready() const; 467 | 468 | FIONA_EXPORT 469 | bool await_suspend( std::coroutine_handle<> h ); 470 | 471 | FIONA_EXPORT 472 | result await_resume(); 473 | }; 474 | 475 | } // namespace tcp 476 | } // namespace fiona 477 | 478 | #endif // FIONA_TCP_HPP 479 | -------------------------------------------------------------------------------- /include/fiona/buffer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Christian Mazakas 2 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 3 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | #ifndef FIONA_BUFFER_HPP 6 | #define FIONA_BUFFER_HPP 7 | 8 | #include // for BOOST_ASSERT 9 | #include 10 | 11 | #include // for size_t, ptrdiff_t 12 | #include // for bidirectional_iterator_tag 13 | #include // for align_val_t, operator new 14 | #include // for span 15 | #include // for string 16 | #include // for string_view 17 | #include // for move 18 | #include // for vector 19 | 20 | #include // for FIONA_EXPORT 21 | 22 | namespace fiona { 23 | 24 | class recv_buffer; 25 | 26 | namespace detail { 27 | 28 | struct buf_header_impl 29 | { 30 | std::size_t size_ = 0; 31 | std::size_t capacity_ = 0; 32 | unsigned char* prev_ = nullptr; 33 | unsigned char* next_ = nullptr; 34 | }; 35 | 36 | FIONA_EXPORT unsigned char* default_buf_header(); 37 | 38 | inline void 39 | set_next( unsigned char* buf, unsigned char* next ) noexcept 40 | { 41 | reinterpret_cast( buf )->next_ = next; 42 | } 43 | 44 | inline void 45 | set_next( unsigned char* buf, detail::buf_header_impl* header ) noexcept 46 | { 47 | reinterpret_cast( buf )->next_ = 48 | reinterpret_cast( header ); 49 | } 50 | 51 | inline void 52 | set_prev( unsigned char* buf, unsigned char* prev ) noexcept 53 | { 54 | reinterpret_cast( buf )->prev_ = prev; 55 | } 56 | 57 | inline void 58 | set_prev( detail::buf_header_impl& header, unsigned char* prev ) noexcept 59 | { 60 | header.prev_ = prev; 61 | } 62 | 63 | inline unsigned char* 64 | get_next( unsigned char* buf ) noexcept 65 | { 66 | return reinterpret_cast( buf )->next_; 67 | } 68 | 69 | inline unsigned char* 70 | get_prev( unsigned char* buf ) noexcept 71 | { 72 | return reinterpret_cast( buf )->prev_; 73 | } 74 | 75 | } // namespace detail 76 | 77 | class recv_buffer_view 78 | { 79 | friend class recv_buffer; 80 | friend class recv_buffer_sequence; 81 | friend class recv_buffer_sequence_view; 82 | 83 | constexpr static std::size_t const S = sizeof( detail::buf_header_impl ); 84 | 85 | unsigned char* p_ = detail::default_buf_header(); 86 | 87 | detail::buf_header_impl& 88 | header() noexcept 89 | { 90 | return *reinterpret_cast( p_ ); 91 | } 92 | 93 | detail::buf_header_impl const& 94 | header() const noexcept 95 | { 96 | return *reinterpret_cast( p_ ); 97 | } 98 | 99 | bool 100 | is_defaulted() const noexcept 101 | { 102 | return p_ == detail::default_buf_header(); 103 | } 104 | 105 | recv_buffer_view( unsigned char* p ) noexcept : p_( p ) {} 106 | 107 | public: 108 | recv_buffer_view() = default; 109 | 110 | recv_buffer_view( recv_buffer_view const& ) = default; 111 | recv_buffer_view& operator=( recv_buffer_view const& ) = default; 112 | 113 | recv_buffer_view( recv_buffer_view&& ) = default; 114 | recv_buffer_view& operator=( recv_buffer_view&& ) = default; 115 | 116 | ~recv_buffer_view() = default; 117 | 118 | unsigned char* 119 | data() noexcept 120 | { 121 | return p_ + S; 122 | } 123 | 124 | unsigned char const* 125 | data() const noexcept 126 | { 127 | return p_ + S; 128 | } 129 | 130 | std::size_t 131 | size() const noexcept 132 | { 133 | return header().size_; 134 | } 135 | 136 | std::size_t 137 | capacity() const noexcept 138 | { 139 | return header().capacity_; 140 | } 141 | 142 | bool 143 | empty() const noexcept 144 | { 145 | return size() == 0; 146 | } 147 | 148 | void 149 | set_len( std::size_t size ) noexcept 150 | { 151 | BOOST_ASSERT( !is_defaulted() ); 152 | header().size_ = size; 153 | } 154 | 155 | // todo: rename this 156 | std::span 157 | readable_bytes() noexcept 158 | { 159 | return { data(), size() }; 160 | } 161 | 162 | std::string_view 163 | as_str() const noexcept 164 | { 165 | return { reinterpret_cast( data() ), size() }; 166 | } 167 | 168 | std::span 169 | spare_capacity_mut() noexcept 170 | { 171 | return { data() + size(), capacity() - size() }; 172 | } 173 | }; 174 | 175 | class recv_buffer : public recv_buffer_view 176 | { 177 | friend class recv_buffer_sequence; 178 | friend class recv_buffer_sequence_view; 179 | 180 | constexpr static std::align_val_t const A{ 181 | alignof( detail::buf_header_impl ) }; 182 | 183 | static void* 184 | aligned_alloc( std::size_t n ) 185 | { 186 | return ::operator new( n, A ); 187 | } 188 | 189 | static void 190 | aligned_delete( void* p ) 191 | { 192 | ::operator delete( p, A ); 193 | } 194 | 195 | unsigned char* 196 | to_raw_parts() noexcept 197 | { 198 | auto p = p_; 199 | p_ = detail::default_buf_header(); 200 | return p; 201 | } 202 | 203 | public: 204 | recv_buffer() = default; 205 | recv_buffer( std::size_t capacity ) 206 | : recv_buffer_view( 207 | static_cast( aligned_alloc( S + capacity ) ) ) 208 | { 209 | new ( p_ ) detail::buf_header_impl(); 210 | header().capacity_ = capacity; 211 | } 212 | 213 | recv_buffer( recv_buffer const& ) = delete; 214 | recv_buffer& operator=( recv_buffer const& ) = delete; 215 | 216 | recv_buffer( recv_buffer&& rhs ) noexcept : recv_buffer_view( rhs.p_ ) 217 | { 218 | rhs.p_ = detail::default_buf_header(); 219 | } 220 | 221 | recv_buffer& 222 | operator=( recv_buffer&& rhs ) noexcept 223 | { 224 | if ( this != &rhs ) { 225 | if ( !is_defaulted() ) { 226 | header().~buf_header_impl(); 227 | aligned_delete( p_ ); 228 | } 229 | p_ = rhs.p_; 230 | rhs.p_ = detail::default_buf_header(); 231 | } 232 | return *this; 233 | } 234 | 235 | ~recv_buffer() 236 | { 237 | if ( !is_defaulted() ) { 238 | aligned_delete( p_ ); 239 | } 240 | } 241 | }; 242 | 243 | class recv_buffer_sequence_view 244 | { 245 | friend class recv_buffer_sequence; 246 | 247 | detail::buf_header_impl* p_sentry_; 248 | unsigned char* head_ = nullptr; 249 | unsigned char* tail_ = nullptr; 250 | std::size_t num_bufs_ = 0; 251 | 252 | recv_buffer_sequence_view( detail::buf_header_impl* p_sentry ) 253 | : p_sentry_( p_sentry ) 254 | { 255 | } 256 | 257 | public: 258 | recv_buffer_sequence_view() = delete; 259 | 260 | recv_buffer_sequence_view( recv_buffer_sequence_view const& ) = default; 261 | recv_buffer_sequence_view& 262 | operator=( recv_buffer_sequence_view const& ) = default; 263 | 264 | recv_buffer_sequence_view( recv_buffer_sequence_view&& ) = default; 265 | recv_buffer_sequence_view& operator=( recv_buffer_sequence_view&& ) = default; 266 | 267 | class iterator 268 | { 269 | friend class recv_buffer_sequence_view; 270 | 271 | unsigned char* p_ = nullptr; 272 | 273 | iterator( unsigned char* p ) : p_( p ) { BOOST_ASSERT( p_ ); } 274 | 275 | public: 276 | using value_type = recv_buffer_view; 277 | using difference_type = std::ptrdiff_t; 278 | using reference = value_type; 279 | using pointer = value_type*; 280 | using iterator_category = std::bidirectional_iterator_tag; 281 | 282 | iterator() = default; 283 | ~iterator() = default; 284 | iterator( iterator const& ) = default; 285 | iterator& operator=( iterator const& ) = default; 286 | 287 | iterator( iterator&& ) = default; 288 | iterator& operator=( iterator&& ) = default; 289 | 290 | recv_buffer_view 291 | operator*() noexcept 292 | { 293 | return { p_ }; 294 | } 295 | 296 | recv_buffer_view 297 | operator*() const noexcept 298 | { 299 | return { p_ }; 300 | } 301 | 302 | iterator& 303 | operator++() noexcept 304 | { 305 | BOOST_ASSERT( p_ ); 306 | p_ = detail::get_next( p_ ); 307 | return *this; 308 | } 309 | 310 | iterator 311 | operator++( int ) noexcept 312 | { 313 | BOOST_ASSERT( p_ ); 314 | auto const old = p_; 315 | p_ = detail::get_next( p_ ); 316 | return old; 317 | } 318 | 319 | iterator& 320 | operator--() noexcept 321 | { 322 | p_ = detail::get_prev( p_ ); 323 | return *this; 324 | } 325 | 326 | iterator 327 | operator--( int ) noexcept 328 | { 329 | auto const old = p_; 330 | p_ = detail::get_prev( p_ ); 331 | return old; 332 | } 333 | 334 | bool 335 | operator==( iterator const& rhs ) const 336 | { 337 | return p_ == rhs.p_; 338 | } 339 | 340 | bool operator!=( iterator const& ) const = default; 341 | }; 342 | 343 | using const_iterator = iterator; 344 | 345 | iterator 346 | begin() const 347 | { 348 | if ( empty() ) { 349 | return end(); 350 | } 351 | return { head_ }; 352 | } 353 | 354 | iterator 355 | end() const 356 | { 357 | return { reinterpret_cast( 358 | const_cast( p_sentry_ ) ) }; 359 | } 360 | 361 | std::size_t 362 | num_bufs() const noexcept 363 | { 364 | return num_bufs_; 365 | } 366 | 367 | bool 368 | empty() const noexcept 369 | { 370 | return num_bufs() == 0; 371 | } 372 | 373 | recv_buffer_view 374 | front() const noexcept 375 | { 376 | BOOST_ASSERT( !empty() ); 377 | return recv_buffer_view( head_ ); 378 | } 379 | 380 | recv_buffer_view 381 | back() const noexcept 382 | { 383 | BOOST_ASSERT( !empty() ); 384 | return recv_buffer_view( tail_ ); 385 | } 386 | 387 | FIONA_EXPORT 388 | std::string to_string() const; 389 | 390 | FIONA_EXPORT 391 | std::vector to_bytes() const; 392 | }; 393 | 394 | class recv_buffer_sequence : public recv_buffer_sequence_view 395 | { 396 | detail::buf_header_impl sentry_; 397 | 398 | void 399 | free_list() 400 | { 401 | if ( !head_ ) { 402 | return; 403 | } 404 | 405 | auto p = head_; 406 | 407 | while ( p != reinterpret_cast( &sentry_ ) ) { 408 | auto old = p; 409 | p = detail::get_next( p ); 410 | recv_buffer::aligned_delete( old ); 411 | } 412 | 413 | head_ = nullptr; 414 | tail_ = nullptr; 415 | num_bufs_ = 0; 416 | sentry_.next_ = nullptr; 417 | sentry_.prev_ = nullptr; 418 | } 419 | 420 | public: 421 | recv_buffer_sequence() : recv_buffer_sequence_view( &sentry_ ) {} 422 | 423 | recv_buffer_sequence( recv_buffer_sequence const& ) = delete; 424 | recv_buffer_sequence& operator=( recv_buffer_sequence const& ) = delete; 425 | 426 | recv_buffer_sequence( recv_buffer_sequence&& rhs ) noexcept 427 | : recv_buffer_sequence_view( &sentry_ ) 428 | { 429 | head_ = rhs.head_; 430 | tail_ = rhs.tail_; 431 | num_bufs_ = rhs.num_bufs_; 432 | 433 | rhs.head_ = nullptr; 434 | rhs.tail_ = nullptr; 435 | rhs.num_bufs_ = 0; 436 | 437 | set_prev( rhs.sentry_, nullptr ); 438 | set_prev( sentry_, tail_ ); 439 | if ( tail_ ) { 440 | set_next( tail_, &sentry_ ); 441 | } 442 | } 443 | 444 | recv_buffer_sequence& 445 | operator=( recv_buffer_sequence&& rhs ) 446 | { 447 | if ( this != &rhs ) { 448 | if ( !empty() ) { 449 | free_list(); 450 | } 451 | 452 | head_ = rhs.head_; 453 | tail_ = rhs.tail_; 454 | num_bufs_ = rhs.num_bufs_; 455 | 456 | rhs.head_ = nullptr; 457 | rhs.tail_ = nullptr; 458 | rhs.num_bufs_ = 0; 459 | 460 | set_prev( rhs.sentry_, nullptr ); 461 | set_prev( sentry_, tail_ ); 462 | if ( tail_ ) { 463 | set_next( tail_, &sentry_ ); 464 | } 465 | } 466 | return *this; 467 | } 468 | 469 | ~recv_buffer_sequence() { free_list(); } 470 | 471 | void 472 | push_back( recv_buffer rbuf ) noexcept 473 | { 474 | BOOST_ASSERT( rbuf.p_ != detail::default_buf_header() ); 475 | 476 | auto p = rbuf.to_raw_parts(); 477 | 478 | if ( BOOST_LIKELY( tail_ != nullptr ) ) { 479 | detail::set_next( tail_, p ); 480 | detail::set_prev( p, tail_ ); 481 | } 482 | 483 | set_next( p, &sentry_ ); 484 | set_prev( sentry_, p ); 485 | tail_ = p; 486 | if ( BOOST_UNLIKELY( head_ == nullptr ) ) { 487 | head_ = p; 488 | } 489 | 490 | ++num_bufs_; 491 | } 492 | 493 | recv_buffer 494 | pop_front() 495 | { 496 | BOOST_ASSERT( !empty() ); 497 | 498 | recv_buffer buf; 499 | 500 | auto p = head_; 501 | head_ = detail::get_next( head_ ); 502 | 503 | detail::set_next( p, static_cast( nullptr ) ); 504 | detail::set_prev( p, static_cast( nullptr ) ); 505 | 506 | // this is unconditionally valid, even when `head_ == tail_` 507 | // because `tail_->next_ == &sentry_` 508 | detail::set_prev( head_, nullptr ); 509 | 510 | --num_bufs_; 511 | buf.p_ = p; 512 | 513 | // old head value was the tail, meaning our list is now empty 514 | if ( BOOST_UNLIKELY( p == tail_ ) ) { 515 | BOOST_ASSERT( num_bufs_ == 0 ); 516 | 517 | tail_ = nullptr; 518 | head_ = nullptr; 519 | } 520 | 521 | return buf; 522 | } 523 | 524 | void 525 | concat( recv_buffer_sequence rhs ) noexcept 526 | { 527 | if ( rhs.empty() ) { 528 | return; 529 | } 530 | 531 | if ( empty() ) { 532 | *this = std::move( rhs ); 533 | return; 534 | } 535 | 536 | detail::set_next( tail_, rhs.head_ ); 537 | detail::set_prev( rhs.head_, tail_ ); 538 | 539 | detail::set_next( rhs.tail_, &sentry_ ); 540 | detail::set_prev( sentry_, rhs.tail_ ); 541 | 542 | detail::set_prev( rhs.sentry_, nullptr ); 543 | 544 | tail_ = rhs.tail_; 545 | num_bufs_ += rhs.num_bufs_; 546 | 547 | rhs.head_ = nullptr; 548 | rhs.tail_ = nullptr; 549 | rhs.num_bufs_ = 0; 550 | } 551 | }; 552 | } // namespace fiona 553 | 554 | #endif // FIONA_BUFFER_HPP 555 | --------------------------------------------------------------------------------