├── examples ├── CMakeLists.txt ├── yield │ └── main.cpp ├── timer │ └── main.cpp ├── mutex │ └── main.cpp ├── copy │ └── main.cpp └── echo │ └── main.cpp ├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── CMakeLists.txt ├── src └── coco │ ├── task.cpp │ └── io.cpp ├── .clang-tidy └── include └── coco ├── sync.hpp ├── task.hpp └── io.hpp /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set( 2 | COCO_EXAMPLE_PROJECTS 3 | echo 4 | timer 5 | copy 6 | yield 7 | mutex 8 | ) 9 | 10 | foreach(COCO_PROJECT ${COCO_EXAMPLE_PROJECTS}) 11 | file(GLOB_RECURSE COCO_EXAMPLE_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${COCO_PROJECT}/*.cpp) 12 | add_executable(${COCO_PROJECT} ${COCO_EXAMPLE_SOURCE}) 13 | target_link_libraries(${COCO_PROJECT} PRIVATE coco::coco) 14 | endforeach(COCO_PROJECT ${COCO_EXAMPLE_PROJECTS}) 15 | 16 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | AccessModifierOffset: -4 5 | IncludeBlocks: Preserve 6 | IndentPPDirectives: AfterHash 7 | AllowShortFunctionsOnASingleLine: Empty 8 | AlignConsecutiveAssignments: 9 | Enabled: true 10 | AcrossEmptyLines: false 11 | AcrossComments: false 12 | AlignCompound: true 13 | PadOperators: true 14 | AlignConsecutiveDeclarations: 15 | Enabled: true 16 | AcrossEmptyLines: false 17 | AcrossComments: false 18 | AlignConsecutiveBitFields: 19 | Enabled: true 20 | AcrossEmptyLines: false 21 | AcrossComments: false 22 | ... 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Visual Studio Code 35 | /.vscode 36 | /.cache 37 | /cpm_modules 38 | compile_commands.json 39 | compile_flags.txt 40 | 41 | # Build Directory 42 | /build* 43 | /out 44 | 45 | # macOS System Files 46 | .DS_Store 47 | -------------------------------------------------------------------------------- /examples/yield/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace coco; 4 | 5 | auto loop(int id) noexcept -> task { 6 | for (;;) { 7 | printf("[Info] Yield loop %d.\n", id); 8 | co_await yield(); 9 | } 10 | } 11 | 12 | auto main() -> int { 13 | io_context io_ctx{1}; 14 | 15 | io_ctx.execute(loop(0)); 16 | io_ctx.execute(loop(1)); 17 | io_ctx.execute(loop(2)); 18 | io_ctx.execute(loop(3)); 19 | io_ctx.execute(loop(4)); 20 | io_ctx.execute(loop(5)); 21 | io_ctx.execute(loop(6)); 22 | io_ctx.execute(loop(7)); 23 | io_ctx.execute(loop(8)); 24 | io_ctx.execute(loop(9)); 25 | io_ctx.run(); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /examples/timer/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace coco; 6 | using namespace std::chrono_literals; 7 | 8 | template 9 | auto loop(std::chrono::duration interval) noexcept -> task { 10 | timer t; 11 | for (;;) { 12 | std::error_code error = co_await t.wait(interval); 13 | if (error.value() != 0) { 14 | std::cerr << "[Error] Failed to suspend timer: " << error.message() 15 | << ".\n"; 16 | break; 17 | } 18 | 19 | std::cout << "[Info] Coroutine suspended for " << interval << ".\n"; 20 | } 21 | } 22 | 23 | auto main() -> int { 24 | io_context io_ctx{1}; 25 | io_ctx.execute(loop(500ms)); 26 | io_ctx.execute(loop(10s)); 27 | io_ctx.run(); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Longhao Li 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /examples/mutex/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace coco; 7 | using namespace std::chrono_literals; 8 | 9 | auto loop(io_context &ctx, int id, mutex &mtx, size_t &count) noexcept 10 | -> task { 11 | timer t; 12 | size_t current = 0; 13 | for (;;) { 14 | { 15 | std::lock_guard guard{mtx}; 16 | current = count++; 17 | } 18 | 19 | std::cout << "[" << std::this_thread::get_id() << "] [" << id 20 | << "] Count: " << current << "\n"; 21 | 22 | if (current >= 200) 23 | ctx.stop(); 24 | 25 | co_await t.wait(50ms); 26 | } 27 | } 28 | 29 | auto main() -> int { 30 | io_context io_ctx{4}; 31 | 32 | size_t count = 0; 33 | mutex mtx; 34 | 35 | io_ctx.execute(loop(io_ctx, 0, mtx, count)); 36 | io_ctx.execute(loop(io_ctx, 1, mtx, count)); 37 | io_ctx.execute(loop(io_ctx, 2, mtx, count)); 38 | io_ctx.execute(loop(io_ctx, 3, mtx, count)); 39 | io_ctx.execute(loop(io_ctx, 4, mtx, count)); 40 | io_ctx.execute(loop(io_ctx, 5, mtx, count)); 41 | io_ctx.execute(loop(io_ctx, 6, mtx, count)); 42 | io_ctx.execute(loop(io_ctx, 7, mtx, count)); 43 | io_ctx.execute(loop(io_ctx, 8, mtx, count)); 44 | io_ctx.execute(loop(io_ctx, 9, mtx, count)); 45 | io_ctx.execute(loop(io_ctx, 10, mtx, count)); 46 | io_ctx.execute(loop(io_ctx, 11, mtx, count)); 47 | io_ctx.execute(loop(io_ctx, 12, mtx, count)); 48 | io_ctx.execute(loop(io_ctx, 13, mtx, count)); 49 | io_ctx.execute(loop(io_ctx, 14, mtx, count)); 50 | io_ctx.execute(loop(io_ctx, 15, mtx, count)); 51 | 52 | io_ctx.run(); 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coco 2 | 3 | > This library has a series of known problems and should not be used for productions. See [onion](https://github.com/longhao-li/onion) for a more stable and fully tested async IO library. 4 | 5 | A lightweight and easy to use async IO library implemented with io_uring and C++20 coroutine. 6 | 7 | ## Build 8 | 9 | This project depends on liburing. Please install `liburing-devel`(SUSE/RHEL based distributions) or `liburing-dev`(Debian based distributions) before building this project. 10 | 11 | ### CMake Options 12 | 13 | | Option | Description | Default Value | 14 | | --------------------------- | --------------------------------------------------------------- | ------------- | 15 | | `COCO_BUILD_STATIC_LIBRARY` | Build coco shared library. The shared target is `coco::shared`. | `ON` | 16 | | `COCO_BUILD_SHARED_LIBRARY` | Build coco static library. The static target is `coco::static`. | `ON` | 17 | | `COCO_ENABLE_LTO` | Enable LTO if possible. | `ON` | 18 | | `COCO_WARNINGS_AS_ERRORS` | Treat warnings as errors. | `OFF` | 19 | | `COCO_BUILD_EXAMPLES` | Build coco examples. | `OFF` | 20 | 21 | If both of the static target and shared target are build, the default target `coco::coco` and `coco` will be set to the static library. 22 | 23 | ## Examples 24 | 25 | ### Echo Server 26 | 27 | See `examples/echo` for details. 28 | 29 | ```cpp 30 | auto echo(tcp_connection connection) noexcept -> task { 31 | char buffer[4096]; 32 | size_t bytes; 33 | std::error_code error; 34 | 35 | for (;;) { 36 | error = co_await connection.receive(buffer, sizeof(buffer), bytes); 37 | // Handle errors. 38 | if (bytes == 0) 39 | // Connection closed. 40 | 41 | error = co_await connection.send(buffer, bytes, bytes); 42 | // Handle errors. 43 | } 44 | co_return; 45 | } 46 | 47 | auto acceptor(io_context &io_ctx, tcp_server server) noexcept -> task { 48 | std::error_code error; 49 | tcp_connection connection; 50 | for (;;) { 51 | error = co_await server.accept(connection); 52 | // Handle errors. 53 | io_ctx.execute(echo(std::move(connection))); 54 | } 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /examples/copy/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace coco; 4 | 5 | auto copy(io_context &io_ctx, const char *from, 6 | const char *to) noexcept -> task { 7 | std::error_code error; 8 | size_t size; 9 | uint32_t bytes; 10 | 11 | binary_file src; 12 | binary_file dest; 13 | 14 | char buffer[4194304]; 15 | 16 | error = src.open(from, binary_file::flag::read); 17 | if (error.value() != 0) { 18 | fprintf(stderr, "[Error] Failed to open source file %s: %s\n", from, 19 | error.message().c_str()); 20 | io_ctx.stop(); 21 | co_return; 22 | } 23 | 24 | error = dest.open(to, binary_file::flag::write | binary_file::flag::trunc); 25 | if (error.value() != 0) { 26 | fprintf(stderr, "[Error] Failed to open file %s: %s\n", from, 27 | error.message().c_str()); 28 | io_ctx.stop(); 29 | co_return; 30 | } 31 | 32 | size = src.size(); 33 | while (size >= sizeof(buffer)) { 34 | error = co_await src.read(buffer, sizeof(buffer), bytes); 35 | if (error.value() != 0) { 36 | fprintf(stderr, "[Error] Failed to read data from file %s: %s\n", 37 | from, error.message().c_str()); 38 | break; 39 | } 40 | 41 | error = co_await dest.write(buffer, bytes); 42 | if (error.value() != 0) { 43 | fprintf(stderr, "[Error] Failed to write data to file %s: %s\n", to, 44 | error.message().c_str()); 45 | break; 46 | } 47 | 48 | size -= bytes; 49 | printf("rest size: %zu bytes\n", size); 50 | } 51 | 52 | if (size != 0) { 53 | error = co_await src.read(buffer, size, bytes); 54 | if (error.value() != 0) { 55 | fprintf(stderr, "[Error] Failed to read data from file %s: %s\n", 56 | from, error.message().c_str()); 57 | io_ctx.stop(); 58 | co_return; 59 | } 60 | 61 | error = co_await dest.write(buffer, bytes); 62 | if (error.value() != 0) { 63 | fprintf(stderr, "[Error] Failed to write data to file %s: %s\n", to, 64 | error.message().c_str()); 65 | io_ctx.stop(); 66 | co_return; 67 | } 68 | 69 | size -= sizeof(buffer); 70 | } 71 | 72 | io_ctx.stop(); 73 | } 74 | 75 | auto main(int argc, char **argv) -> int { 76 | if (argc != 3) { 77 | fprintf(stderr, "usage: %s \n", argv[0]); 78 | return -10; 79 | } 80 | 81 | io_context io_ctx{1}; 82 | io_ctx.execute(copy(io_ctx, argv[1], argv[2])); 83 | io_ctx.run(); 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /examples/echo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace coco; 6 | 7 | auto echo(tcp_connection connection) noexcept -> task { 8 | char buffer[4096]; 9 | size_t bytes; 10 | std::error_code error; 11 | 12 | for (;;) { 13 | memset(buffer, 0, sizeof(buffer)); 14 | error = co_await connection.receive(buffer, sizeof(buffer), bytes); 15 | if (error.value() != 0) [[unlikely]] { 16 | fprintf( 17 | stderr, 18 | "[Error] Failed to receive message: %d. Closing Connection.\n", 19 | error.value()); 20 | break; 21 | } 22 | 23 | if (bytes == 0) [[unlikely]] { 24 | printf("[Info] Connection closed with %s:%hu\n", 25 | connection.address().to_string().data(), connection.port()); 26 | break; 27 | } 28 | 29 | printf("[Info] received %zu bytes of data.\n", bytes); 30 | 31 | error = co_await connection.send(buffer, bytes, bytes); 32 | if (error.value() != 0) [[unlikely]] { 33 | printf("[Error] Failed to send message: %s. Closing Connection.\n", 34 | error.message().data()); 35 | break; 36 | } 37 | 38 | printf("[Info] sent %zu bytes of data.\n", bytes); 39 | } 40 | 41 | printf("[Info] Connection with %s:%hu closing.\n", 42 | connection.address().to_string().c_str(), connection.port()); 43 | co_return; 44 | } 45 | 46 | auto acceptor(io_context &io_ctx, tcp_server server) noexcept -> task { 47 | std::error_code error; 48 | tcp_connection connection; 49 | for (;;) { 50 | error = co_await server.accept(connection); 51 | if (error.value() != 0) [[unlikely]] { 52 | fprintf(stderr, 53 | "[Error] TCP server failed to accept new connection.\n"); 54 | io_ctx.stop(); 55 | co_return; 56 | } 57 | 58 | io_ctx.execute(echo(std::move(connection))); 59 | } 60 | } 61 | 62 | auto main(int argc, char **argv) -> int { 63 | if (argc < 2) { 64 | fprintf(stderr, "usage: %s \n", argv[0]); 65 | return -10; 66 | } 67 | 68 | std::error_code error; 69 | 70 | auto address = ip_address::ipv4_loopback(); 71 | auto port = static_cast(atoi(argv[1])); 72 | 73 | tcp_server server; 74 | error = server.listen(address, port); 75 | if (error.value() != 0) { 76 | fprintf(stderr, "Failed to listen to 127.0.0.1:%hu - %s\n", port, 77 | error.message().c_str()); 78 | return EXIT_FAILURE; 79 | } 80 | 81 | io_context io_ctx{1}; 82 | io_ctx.execute(acceptor(io_ctx, std::move(server))); 83 | io_ctx.run(); 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(coco VERSION 1.0.0 LANGUAGES CXX) 4 | 5 | option(COCO_BUILD_STATIC_LIBRARY "Build coco static library" ON) 6 | option(COCO_BUILD_SHARED_LIBRARY "Build coco shared library" ON) 7 | option(COCO_ENABLE_LTO "Enable link time optimization if possible" ON) 8 | option(COCO_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) 9 | option(COCO_BUILD_EXAMPLES "Build coco examples" OFF) 10 | 11 | file(GLOB_RECURSE COCO_HEADER_FILES "include/*.hpp") 12 | file(GLOB_RECURSE COCO_SOURCE_FILES "src/*.cpp") 13 | 14 | # Enable static library target. 15 | if(COCO_BUILD_STATIC_LIBRARY) 16 | add_library(coco-static STATIC ${COCO_SOURCE_FILES} ${COCO_HEADER_FILES}) 17 | 18 | # Set output name for static library. 19 | set_target_properties(coco-static PROPERTIES OUTPUT_NAME coco) 20 | 21 | # Set compile definitions for static library. 22 | target_compile_definitions(coco-static PUBLIC "COCO_API=") 23 | 24 | # Alias target for coco static library. 25 | add_library(coco::static ALIAS coco-static) 26 | add_library(coco::coco ALIAS coco-static) 27 | add_library(coco ALIAS coco-static) 28 | 29 | # Append this static library target to target list. 30 | list(APPEND COCO_ENABLED_TARGETS coco-static) 31 | endif() 32 | 33 | # Enable shared library target. 34 | if(COCO_BUILD_SHARED_LIBRARY) 35 | add_library(coco-shared SHARED ${COCO_SOURCE_FILES} ${COCO_HEADER_FILES}) 36 | 37 | # Set target properties. 38 | set_target_properties( 39 | coco-shared PROPERTIES 40 | OUTPUT_NAME coco 41 | POSITION_INDEPENDENT_CODE TRUE 42 | CXX_VISIBILITY_PRESET hidden 43 | ) 44 | 45 | # Set compile definitions for shared library. 46 | target_compile_definitions(coco-shared PUBLIC "COCO_API=__attribute__((visibility(\"default\")))") 47 | 48 | # Alias target for coco shared library. 49 | if(NOT COCO_BUILD_STATIC_LIBRARY) 50 | add_library(coco::shared ALIAS coco-shared) 51 | add_library(coco::coco ALIAS coco-shared) 52 | add_library(coco ALIAS coco-shared) 53 | endif() 54 | 55 | # Append this static library target to target list. 56 | list(APPEND COCO_ENABLED_TARGETS coco-shared) 57 | endif() 58 | 59 | # Config targets. 60 | foreach(COCO_TARGET ${COCO_ENABLED_TARGETS}) 61 | # Set include directory. 62 | target_include_directories(${COCO_TARGET} PUBLIC $ $) 63 | 64 | # Link system libraries. 65 | target_link_libraries(${COCO_TARGET} PUBLIC "uring") 66 | 67 | # Set C++ standard. 68 | target_compile_features(${COCO_TARGET} PUBLIC cxx_std_20) 69 | 70 | # Enable LTO. 71 | if(COCO_ENABLE_LTO) 72 | set_target_properties(${COCO_TARGET} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 73 | endif() 74 | 75 | # Compile options. 76 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 77 | target_compile_options(${COCO_TARGET} PRIVATE -Wall -Wextra -Wmost -pedantic -Wshadow -Wold-style-cast -Woverloaded-virtual -Wcast-align -Wunreachable-code) 78 | 79 | # Treat warnings as errors. 80 | if(COCO_WARNINGS_AS_ERRORS) 81 | target_compile_options(${COCO_TARGET} PRIVATE -Werror) 82 | endif() 83 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 84 | target_compile_options(${COCO_TARGET} PRIVATE -Wall -Wextra -pedantic -Wshadow -Wold-style-cast -Woverloaded-virtual -Wcast-align -Wunreachable-code -Wno-class-memaccess) 85 | 86 | # Treat warnings as errors. 87 | if(COCO_WARNINGS_AS_ERRORS) 88 | target_compile_options(${COCO_TARGET} PRIVATE -Werror) 89 | endif() 90 | endif() 91 | endforeach() 92 | 93 | if(COCO_BUILD_EXAMPLES) 94 | add_subdirectory(examples) 95 | endif() 96 | -------------------------------------------------------------------------------- /src/coco/task.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/task.hpp" 2 | 3 | #include 4 | 5 | using namespace coco; 6 | using namespace coco::detail; 7 | 8 | coco::detail::io_context_worker::io_context_worker() noexcept 9 | : m_should_exit{false}, m_is_running{false}, m_ring{}, m_wake_up{-1}, 10 | m_wake_up_buffer{}, m_mutex{}, m_tasks{} { 11 | int result = io_uring_queue_init(1024, &m_ring, IORING_SETUP_SQPOLL); 12 | assert(result == 0); 13 | (void)result; 14 | 15 | m_wake_up = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 16 | assert(m_wake_up >= 0); 17 | 18 | m_tasks.reserve(16); 19 | } 20 | 21 | coco::detail::io_context_worker::~io_context_worker() { 22 | assert(!m_is_running.load(std::memory_order_relaxed)); 23 | 24 | close(m_wake_up); 25 | io_uring_queue_exit(&m_ring); 26 | 27 | for (auto &coro : m_tasks) 28 | coro.destroy(); 29 | } 30 | 31 | auto coco::detail::io_context_worker::wake_up() noexcept -> void { 32 | io_uring_sqe *sqe = io_uring_get_sqe(&m_ring); 33 | while (sqe == nullptr) [[unlikely]] { 34 | io_uring_submit(&m_ring); 35 | sqe = io_uring_get_sqe(&m_ring); 36 | } 37 | 38 | sqe->user_data = 0; 39 | uint64_t value = 1; 40 | ssize_t written = write(m_wake_up, &value, sizeof(value)); 41 | assert(written == sizeof(value)); 42 | (void)written; 43 | 44 | io_uring_prep_read(sqe, m_wake_up, &m_wake_up_buffer, 45 | sizeof(m_wake_up_buffer), 0); 46 | int result = io_uring_submit(&m_ring); 47 | assert(result >= 1); 48 | (void)result; 49 | } 50 | 51 | auto coco::detail::io_context_worker::run() noexcept -> void { 52 | if (m_is_running.exchange(true, std::memory_order_acq_rel)) [[unlikely]] 53 | return; 54 | 55 | __kernel_timespec timeout{ 56 | .tv_sec = 1, 57 | .tv_nsec = 0, 58 | }; 59 | 60 | io_uring_cqe *cqe; 61 | task_list tasks; 62 | tasks.reserve(16); 63 | 64 | m_should_exit.store(false, std::memory_order_release); 65 | while (!m_should_exit.load(std::memory_order_acquire)) { 66 | timeout = __kernel_timespec{ 67 | .tv_sec = 1, 68 | .tv_nsec = 0, 69 | }; 70 | 71 | int result = io_uring_wait_cqe_timeout(&m_ring, &cqe, &timeout); 72 | while (result == 0) { 73 | auto *data = static_cast(io_uring_cqe_get_data(cqe)); 74 | if (data == nullptr) { 75 | io_uring_cq_advance(&m_ring, 1); 76 | result = io_uring_peek_cqe(&m_ring, &cqe); 77 | continue; 78 | } 79 | 80 | data->cqe_res = cqe->res; 81 | data->cqe_flags = cqe->flags; 82 | 83 | auto coroutine = std::coroutine_handle::from_address( 84 | data->coroutine); 85 | auto stack_bottom = coroutine.promise().stack_bottom(); 86 | 87 | assert(!coroutine.done()); 88 | coroutine.resume(); 89 | if (stack_bottom.done()) 90 | stack_bottom.destroy(); 91 | 92 | io_uring_cq_advance(&m_ring, 1); 93 | result = io_uring_peek_cqe(&m_ring, &cqe); 94 | } 95 | 96 | { // Handle tasks. 97 | std::lock_guard lock{m_mutex}; 98 | tasks.swap(m_tasks); 99 | } 100 | 101 | for (auto &coroutine : tasks) { 102 | // May be not safe to access coroutine.promise() here. But this 103 | // avoids miss-destorying coroutine. 104 | auto stack_bottom = 105 | std::coroutine_handle::from_address( 106 | coroutine.address()) 107 | .promise() 108 | .stack_bottom(); 109 | 110 | coroutine.resume(); 111 | if (stack_bottom.done()) 112 | stack_bottom.destroy(); 113 | } 114 | 115 | tasks.clear(); 116 | } 117 | 118 | m_is_running.store(false, std::memory_order_release); 119 | } 120 | 121 | auto coco::detail::io_context_worker::stop() noexcept -> void { 122 | if (!m_is_running.load(std::memory_order_acquire)) 123 | return; 124 | 125 | m_should_exit.store(true, std::memory_order_release); 126 | this->wake_up(); 127 | } 128 | 129 | coco::io_context::io_context() noexcept 130 | : io_context{std::thread::hardware_concurrency()} {} 131 | 132 | coco::io_context::io_context(uint32_t num_workers) noexcept 133 | : m_workers{}, m_threads{}, m_num_workers{}, m_next_worker{0} { 134 | num_workers = (num_workers == 0) ? 1 : num_workers; 135 | m_num_workers = num_workers; 136 | 137 | m_workers = std::make_unique(num_workers); 138 | } 139 | 140 | coco::io_context::~io_context() { 141 | this->stop(); 142 | } 143 | 144 | auto coco::io_context::run() noexcept -> void { 145 | m_threads = std::make_unique(m_num_workers - 1); 146 | for (uint32_t i = 0; i < m_num_workers - 1; ++i) 147 | m_threads[i] = 148 | std::jthread([worker = &(m_workers[i])] { worker->run(); }); 149 | 150 | m_workers[m_num_workers - 1].run(); 151 | } 152 | 153 | auto coco::io_context::stop() noexcept -> void { 154 | for (uint32_t i = 0; i < m_num_workers; ++i) 155 | m_workers[i].stop(); 156 | } 157 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: ' 3 | clang-diagnostic-*, 4 | clang-analyzer-*, 5 | modernize-*, 6 | -modernize-avoid-c-arrays, 7 | -modernize-use-default-member-init, 8 | -modernize-use-auto, 9 | -modernize-loop-convert, 10 | cppcoreguidelines-explicit-virtual-functions, 11 | cppcoreguidelines-special-member-functions, 12 | readability-implicit-bool-conversion, 13 | readability-identifier-naming, 14 | performance-*, 15 | -performance-no-int-to-ptr, 16 | -performance-noexcept-move-constructor, 17 | ' 18 | WarningsAsErrors: ' 19 | clang-diagnostic-*, 20 | clang-analyzer-*, 21 | modernize-*, 22 | readability-identifier-naming, 23 | cppcoreguidelines-explicit-virtual-functions, 24 | cppcoreguidelines-special-member-functions, 25 | readability-implicit-bool-conversion, 26 | performance-*, 27 | ' 28 | FormatStyle: file 29 | HeaderFilterRegex: '(include|src)*\.(h|hpp)$' 30 | CheckOptions: 31 | - key: readability-identifier-naming.ClassCase 32 | value: lower_case 33 | - key: readability-identifier-naming.ClassConstantCase 34 | value: UPPER_CASE 35 | - key: readability-identifier-naming.ClassMemberCase 36 | value: lower_case 37 | - key: readability-identifier-naming.ClassMethodCase 38 | value: lower_case 39 | - key: readability-identifier-naming.ConceptCase 40 | value: lower_case 41 | - key: readability-identifier-naming.ConstantCase 42 | value: UPPER_CASE 43 | - key: readability-identifier-naming.ConstantMemberCase 44 | value: lower_case 45 | - key: readability-identifier-naming.ConstantParameterCase 46 | value: lower_case 47 | - key: readability-identifier-naming.ConstantPointerParameterCase 48 | value: lower_case 49 | - key: readability-identifier-naming.ConstexprFunctionCase 50 | value: lower_case 51 | - key: readability-identifier-naming.ConstexprMethodCase 52 | value: lower_case 53 | - key: readability-identifier-naming.ConstexprVariableCase 54 | value: UPPER_CASE 55 | - key: readability-identifier-naming.EnumCase 56 | value: lower_case 57 | - key: readability-identifier-naming.EnumConstantCase 58 | value: UPPER_CASE 59 | - key: readability-identifier-naming.FunctionCase 60 | value: lower_case 61 | - key: readability-identifier-naming.GlobalConstantCase 62 | value: UPPER_CASE 63 | - key: readability-identifier-naming.GlobalConstantPointerCase 64 | value: UPPER_CASE 65 | - key: readability-identifier-naming.GlobalFunctionCase 66 | value: lower_case 67 | - key: readability-identifier-naming.GlobalPointerCase 68 | value: CamelCase 69 | - key: readability-identifier-naming.GlobalVariableCase 70 | value: CamelCase 71 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 72 | value: true 73 | - key: readability-identifier-naming.InlineNamespaceCase 74 | value: lower_case 75 | - key: readability-identifier-naming.LocalConstantCase 76 | value: lower_case 77 | - key: readability-identifier-naming.LocalConstantPointerCase 78 | value: lower_case 79 | - key: readability-identifier-naming.LocalPointerCase 80 | value: lower_case 81 | - key: readability-identifier-naming.LocalVariableCase 82 | value: lower_case 83 | - key: readability-identifier-naming.MemberCase 84 | value: lower_case 85 | - key: readability-identifier-naming.MethodCase 86 | value: lower_case 87 | - key: readability-identifier-naming.NamespaceCase 88 | value: lower_case 89 | - key: readability-identifier-naming.ParameterCase 90 | value: lower_case 91 | - key: readability-identifier-naming.ParameterPackCase 92 | value: lower_case 93 | - key: readability-identifier-naming.PointerParameterCase 94 | value: lower_case 95 | - key: readability-identifier-naming.PrivateMemberCase 96 | value: lower_case 97 | - key: readability-identifier-naming.PrivateMemberPrefix 98 | value: m_ 99 | - key: readability-identifier-naming.PrivateMethodCase 100 | value: lower_case 101 | - key: readability-identifier-naming.ProtectedMemberCase 102 | value: lower_case 103 | - key: readability-identifier-naming.ProtectedMemberPrefix 104 | value: m_ 105 | - key: readability-identifier-naming.ProtectedMethodCase 106 | value: lower_case 107 | - key: readability-identifier-naming.PublicMemberCase 108 | value: lower_case 109 | - key: readability-identifier-naming.PublicMethodCase 110 | value: lower_case 111 | - key: readability-identifier-naming.ScopedEnumConstantCase 112 | value: lower_case 113 | - key: readability-identifier-naming.StaticConstantCase 114 | value: UPPER_CASE 115 | - key: readability-identifier-naming.StaticVariableCase 116 | value: CamelCase 117 | - key: readability-identifier-naming.StructCase 118 | value: lower_case 119 | - key: readability-identifier-naming.TemplateParameterCase 120 | value: CamelCase 121 | - key: readability-identifier-naming.TemplateTemplateParameterCase 122 | value: CamelCase 123 | - key: readability-identifier-naming.TypeTemplateParameterCase 124 | value: CamelCase 125 | - key: readability-identifier-naming.UnionCase 126 | value: lower_case 127 | - key: readability-identifier-naming.ValueTemplateParameterCase 128 | value: CamelCase 129 | - key: readability-identifier-naming.VariableCase 130 | value: lower_case 131 | - key: readability-identifier-naming.VirtualMethodCase 132 | value: lower_case 133 | ... 134 | -------------------------------------------------------------------------------- /include/coco/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "task.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace coco { 9 | 10 | /// \class mutex 11 | /// \brief 12 | /// Mutex for tasks. 13 | class mutex; 14 | 15 | } // namespace coco 16 | 17 | namespace coco { 18 | 19 | /// \class yield_awaitable 20 | /// \brief 21 | /// For internal usage. Awaitable object to yield current coroutine. 22 | class [[nodiscard]] yield_awaitable { 23 | public: 24 | /// \brief 25 | /// C++20 awaitable internal method. Always execute await_suspend(). 26 | auto await_ready() noexcept -> bool { 27 | return false; 28 | } 29 | 30 | /// \brief 31 | /// Append this coroutine to the waiting task list and suspend it 32 | /// immediately. 33 | /// \tparam Promise 34 | /// Type of the promise for the coroutine. 35 | template 36 | auto await_suspend(std::coroutine_handle coro) noexcept -> void { 37 | detail::io_context_worker *worker = coro.promise().worker(); 38 | worker->execute(coro); 39 | } 40 | 41 | /// \brief 42 | /// Called when current coroutine is resumed. Nothing to do here. 43 | auto await_resume() noexcept -> void {} 44 | }; 45 | 46 | /// \class mutex_lock_awaitable 47 | /// \brief 48 | /// Awaitable class for mutex lock operation. 49 | class [[nodiscard]] mutex_lock_awaitable { 50 | public: 51 | /// \brief 52 | /// Initialize a new mutex lock awaitable object. 53 | mutex_lock_awaitable(mutex *mtx) noexcept : m_mutex{mtx} {} 54 | 55 | /// \brief 56 | /// C++20 awaitable internal method. Always execute await_suspend(). 57 | auto await_ready() noexcept -> bool { 58 | return false; 59 | } 60 | 61 | /// \brief 62 | /// Try to acquire current mutex and suspend current coroutine if 63 | /// necessary. 64 | /// \tparam Promise 65 | /// Promise type of current coroutine. 66 | /// \param coro 67 | /// Coroutine handle of current coroutine. 68 | /// \retval true 69 | /// This coroutine should be suspended. 70 | /// \retval false 71 | /// This coroutine should not be suspended. 72 | template 73 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool; 74 | 75 | /// \brief 76 | /// Called when current coroutine is resumed. Nothing to do here. 77 | auto await_resume() noexcept -> void {} 78 | 79 | private: 80 | mutex *m_mutex; 81 | }; 82 | 83 | } // namespace coco 84 | 85 | namespace coco { 86 | 87 | /// \brief 88 | /// Yield current coroutine temporarily. This coroutine will be resumed when 89 | /// other coroutines are handled or suspended. 90 | /// \remarks 91 | /// This method is designed for convenience so that user can simply use 92 | /// `co_await coco::yield()` to yield current coroutine. This is exactly the 93 | /// same as `co_await coco::yield_awaitable{}`. 94 | /// \return 95 | /// A new yield_awaitable object. 96 | [[nodiscard]] constexpr auto yield() noexcept -> yield_awaitable { 97 | return {}; 98 | } 99 | 100 | /// \class mutex 101 | /// \brief 102 | /// Mutex designed for coroutine. 103 | class mutex { 104 | public: 105 | /// \brief 106 | /// Create and initialize this mutex. 107 | mutex() noexcept : m_mutex{}, m_is_locked{false}, m_tasks{} {} 108 | 109 | /// \brief 110 | /// Mutex is not copyable. 111 | mutex(const mutex &other) = delete; 112 | 113 | /// \brief 114 | /// Mutex is not moveable. 115 | mutex(mutex &&other) = delete; 116 | 117 | /// \brief 118 | /// Destroy this mutex. 119 | /// \note 120 | /// It is undefined behavior if this mutex is locked when destroying. 121 | ~mutex() = default; 122 | 123 | /// \brief 124 | /// Mutex is not copyable. 125 | auto operator=(const mutex &other) = delete; 126 | 127 | /// \brief 128 | /// Mutex is not moveable. 129 | auto operator=(mutex &&other) = delete; 130 | 131 | /// \brief 132 | /// Acquire this mutex. Current coroutine will be suspended if this mutex 133 | /// is not available currently and will be resumed once available. 134 | /// \return 135 | /// An awaitable object to acquire this mutex. 136 | auto lock() noexcept -> mutex_lock_awaitable { 137 | return {this}; 138 | } 139 | 140 | /// \brief 141 | /// Try to acquire this mutex. This method always returns immediately. 142 | /// \retval true 143 | /// This mutex is acquired successfully. 144 | /// \retval false 145 | /// Failed to acquire this mutex. 146 | auto try_lock() noexcept -> bool { 147 | std::lock_guard guard{m_mutex}; 148 | if (std::exchange(m_is_locked, true) == false) 149 | return true; 150 | return false; 151 | } 152 | 153 | /// \brief 154 | /// Release this mutex. 155 | /// \note 156 | /// It is undefined behavior to release an unlocked mutex. 157 | auto unlock() noexcept -> void { 158 | // Mutex is used here to avoid possible concurrency conflict which maybe 159 | // a slow implementation. 160 | std::lock_guard guard{m_mutex}; 161 | 162 | // Resume pending task if there is any. 163 | if (!m_tasks.empty()) { 164 | auto [task, worker] = m_tasks.front(); 165 | m_tasks.pop(); 166 | worker->execute(task); 167 | } else { 168 | m_is_locked = false; 169 | } 170 | } 171 | 172 | friend class mutex_lock_awaitable; 173 | 174 | private: 175 | struct pending_task { 176 | std::coroutine_handle<> coroutine; 177 | detail::io_context_worker *worker; 178 | }; 179 | 180 | std::mutex m_mutex; 181 | bool m_is_locked; 182 | std::queue m_tasks; 183 | }; 184 | 185 | } // namespace coco 186 | 187 | namespace coco { 188 | 189 | template 190 | auto coco::mutex_lock_awaitable::await_suspend( 191 | std::coroutine_handle coro) noexcept -> bool { 192 | // Mutex is used here to avoid possible concurrency conflict which maybe a 193 | // slow implementation. 194 | std::lock_guard guard{m_mutex->m_mutex}; 195 | 196 | // Check if this mutex is currently locked. 197 | if (std::exchange(m_mutex->m_is_locked, true) == false) 198 | return false; 199 | 200 | // Failed to acquire mutex, suspend this coroutine. 201 | m_mutex->m_tasks.push({coro, coro.promise().worker()}); 202 | return true; 203 | } 204 | 205 | } // namespace coco 206 | -------------------------------------------------------------------------------- /src/coco/io.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__LP64__) 2 | # define _FILE_OFFSET_BITS 64 3 | #endif 4 | #include "coco/io.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace coco; 16 | using namespace coco::detail; 17 | 18 | auto coco::read_awaitable::suspend(io_context_worker *worker) noexcept -> bool { 19 | assert(worker != nullptr); 20 | m_userdata.cqe_res = 0; 21 | m_userdata.cqe_flags = 0; 22 | 23 | io_uring *ring = worker->io_ring(); 24 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 25 | while (sqe == nullptr) [[unlikely]] { 26 | io_uring_submit(ring); 27 | sqe = io_uring_get_sqe(ring); 28 | } 29 | 30 | io_uring_prep_read(sqe, m_file, m_buffer, m_size, m_offset); 31 | sqe->user_data = reinterpret_cast(&m_userdata); 32 | 33 | int result = io_uring_submit(ring); 34 | if (result < 0) [[unlikely]] { // Result is -errno. 35 | m_userdata.cqe_res = result; 36 | return false; 37 | } 38 | 39 | return true; 40 | } 41 | 42 | auto coco::write_awaitable::suspend(io_context_worker *worker) noexcept 43 | -> bool { 44 | assert(worker != nullptr); 45 | m_userdata.cqe_res = 0; 46 | m_userdata.cqe_flags = 0; 47 | 48 | io_uring *ring = worker->io_ring(); 49 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 50 | while (sqe == nullptr) [[unlikely]] { 51 | io_uring_submit(ring); 52 | sqe = io_uring_get_sqe(ring); 53 | } 54 | 55 | io_uring_prep_write(sqe, m_file, m_data, m_size, m_offset); 56 | sqe->user_data = reinterpret_cast(&m_userdata); 57 | 58 | int result = io_uring_submit(ring); 59 | if (result < 0) [[unlikely]] { // Result is -errno. 60 | m_userdata.cqe_res = result; 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | auto coco::connect_awaitable::await_resume() noexcept -> std::error_code { 68 | if (m_userdata.cqe_res < 0) 69 | return {-m_userdata.cqe_res, std::system_category()}; 70 | 71 | assert(m_target != nullptr); 72 | if (*m_target >= 0) 73 | ::close(*m_target); 74 | 75 | *m_target = m_socket; 76 | return {}; 77 | } 78 | 79 | auto coco::connect_awaitable::suspend( 80 | detail::io_context_worker *worker) noexcept -> bool { 81 | assert(worker != nullptr); 82 | m_userdata.cqe_res = 0; 83 | m_userdata.cqe_flags = 0; 84 | 85 | m_socket = ::socket(m_sockaddr.ss_family, m_type, m_protocol); 86 | if (m_socket < 0) { // Failed to create new socket. 87 | m_userdata.cqe_res = -errno; 88 | return false; 89 | } 90 | 91 | io_uring *ring = worker->io_ring(); 92 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 93 | while (sqe == nullptr) [[unlikely]] { 94 | io_uring_submit(ring); 95 | sqe = io_uring_get_sqe(ring); 96 | } 97 | 98 | io_uring_prep_connect(sqe, m_socket, 99 | reinterpret_cast(&m_sockaddr), m_socklen); 100 | sqe->user_data = reinterpret_cast(&m_userdata); 101 | 102 | int result = io_uring_submit(ring); 103 | if (result < 0) [[unlikely]] { // Result is -errno. 104 | m_userdata.cqe_res = result; 105 | ::close(m_socket); 106 | return false; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | auto coco::send_awaitable::suspend(io_context_worker *worker) noexcept -> bool { 113 | assert(worker != nullptr); 114 | m_userdata.cqe_res = 0; 115 | m_userdata.cqe_flags = 0; 116 | 117 | io_uring *ring = worker->io_ring(); 118 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 119 | while (sqe == nullptr) [[unlikely]] { 120 | io_uring_submit(ring); 121 | sqe = io_uring_get_sqe(ring); 122 | } 123 | 124 | io_uring_prep_send(sqe, m_socket, m_data, m_size, MSG_NOSIGNAL); 125 | sqe->user_data = reinterpret_cast(&m_userdata); 126 | 127 | int result = io_uring_submit(ring); 128 | if (result < 0) [[unlikely]] { // Result is -errno. 129 | m_userdata.cqe_res = result; 130 | return false; 131 | } 132 | 133 | return true; 134 | } 135 | 136 | auto coco::recv_awaitable::suspend(io_context_worker *worker) noexcept -> bool { 137 | assert(worker != nullptr); 138 | m_userdata.cqe_res = 0; 139 | m_userdata.cqe_flags = 0; 140 | 141 | io_uring *ring = worker->io_ring(); 142 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 143 | while (sqe == nullptr) [[unlikely]] { 144 | io_uring_submit(ring); 145 | sqe = io_uring_get_sqe(ring); 146 | } 147 | 148 | io_uring_prep_recv(sqe, m_socket, m_buffer, m_size, 0); 149 | sqe->user_data = reinterpret_cast(&m_userdata); 150 | 151 | int result = io_uring_submit(ring); 152 | if (result < 0) [[unlikely]] { // Result is -errno. 153 | m_userdata.cqe_res = result; 154 | return false; 155 | } 156 | 157 | return true; 158 | } 159 | 160 | auto coco::timeout_awaitable::suspend(io_context_worker *worker) noexcept 161 | -> bool { 162 | itimerspec timeout{ 163 | .it_interval = {}, 164 | .it_value = 165 | { 166 | .tv_sec = m_nanosecond / 1000000000, 167 | .tv_nsec = m_nanosecond % 1000000000, 168 | }, 169 | }; 170 | 171 | int result = timerfd_settime(m_timer, 0, &timeout, nullptr); 172 | if (result == -1) { // Failed to set timer. 173 | m_userdata.cqe_res = -errno; 174 | return false; 175 | } 176 | 177 | assert(worker != nullptr); 178 | m_userdata.cqe_res = 0; 179 | m_userdata.cqe_flags = 0; 180 | 181 | io_uring *ring = worker->io_ring(); 182 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 183 | while (sqe == nullptr) [[unlikely]] { 184 | io_uring_submit(ring); 185 | sqe = io_uring_get_sqe(ring); 186 | } 187 | 188 | io_uring_prep_read(sqe, m_timer, &m_buffer, sizeof(m_buffer), 0); 189 | sqe->user_data = reinterpret_cast(&m_userdata); 190 | 191 | result = io_uring_submit(ring); 192 | if (result < 0) [[unlikely]] { // Result is -errno. 193 | m_userdata.cqe_res = result; 194 | return false; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | auto coco::ipv4_address::set_address(std::string_view address) noexcept 201 | -> bool { 202 | char buffer[INET_ADDRSTRLEN]; 203 | if (address.size() >= INET_ADDRSTRLEN) 204 | return false; 205 | 206 | memcpy(buffer, address.data(), address.size()); 207 | buffer[address.size()] = '\0'; 208 | 209 | in_addr binary; 210 | if (inet_pton(AF_INET, buffer, &binary) != 1) 211 | return false; 212 | 213 | static_assert(sizeof(in_addr) == sizeof(m_address)); 214 | memcpy(&m_address, &binary, sizeof(m_address)); 215 | 216 | return true; 217 | } 218 | 219 | auto coco::ipv4_address::to_string() const noexcept -> std::string { 220 | char buffer[INET_ADDRSTRLEN]{}; 221 | inet_ntop(AF_INET, &m_address, buffer, sizeof(buffer)); 222 | return buffer; 223 | } 224 | 225 | coco::ipv6_address::ipv6_address(uint16_t v0, uint16_t v1, uint16_t v2, 226 | uint16_t v3, uint16_t v4, uint16_t v5, 227 | uint16_t v6, uint16_t v7) noexcept 228 | : m_address{.u16{ntohs(v0), ntohs(v1), ntohs(v2), ntohs(v3), ntohs(v4), 229 | ntohs(v5), ntohs(v6), ntohs(v7)}} {} 230 | 231 | auto coco::ipv6_address::set_address(std::string_view address) noexcept 232 | -> bool { 233 | char buffer[INET6_ADDRSTRLEN]; 234 | if (address.size() >= INET6_ADDRSTRLEN) 235 | return false; 236 | 237 | memcpy(buffer, address.data(), address.size()); 238 | buffer[address.size()] = '\0'; 239 | 240 | in6_addr binary; 241 | if (inet_pton(AF_INET6, buffer, &binary) != 1) 242 | return false; 243 | 244 | static_assert(sizeof(binary) == sizeof(m_address)); 245 | memcpy(&m_address, &binary, sizeof(m_address)); 246 | 247 | return true; 248 | } 249 | 250 | auto coco::ipv6_address::to_string() const noexcept -> std::string { 251 | char buffer[INET6_ADDRSTRLEN]{}; 252 | inet_ntop(AF_INET6, &m_address, buffer, sizeof(buffer)); 253 | return buffer; 254 | } 255 | 256 | auto coco::ip_address::set_address(std::string_view address) noexcept -> bool { 257 | if (address.find(':') != std::string_view::npos) { 258 | ipv6_address new_address; 259 | if (!new_address.set_address(address)) 260 | return false; 261 | 262 | m_address.v6 = new_address; 263 | m_is_ipv6 = true; 264 | } else { 265 | ipv4_address new_address; 266 | if (!new_address.set_address(address)) 267 | return false; 268 | 269 | m_address.v4 = new_address; 270 | m_is_ipv6 = false; 271 | } 272 | 273 | return true; 274 | } 275 | 276 | auto coco::ip_address::to_string() const noexcept -> std::string { 277 | if (m_is_ipv6) 278 | return m_address.v6.to_string(); 279 | return m_address.v4.to_string(); 280 | } 281 | 282 | coco::tcp_connection::~tcp_connection() { 283 | if (m_socket != -1) { 284 | assert(m_socket >= 0); 285 | ::close(m_socket); 286 | } 287 | } 288 | 289 | auto coco::tcp_connection::operator=(tcp_connection &&other) noexcept 290 | -> tcp_connection & { 291 | if (this == &other) [[unlikely]] 292 | return *this; 293 | 294 | if (m_socket != -1) { 295 | assert(m_socket >= 0); 296 | ::close(m_socket); 297 | } 298 | 299 | m_socket = other.m_socket; 300 | other.m_socket = -1; 301 | 302 | return *this; 303 | } 304 | 305 | auto coco::tcp_connection::connect(ip_address address, uint16_t port) noexcept 306 | -> connect_awaitable { 307 | sockaddr_storage temp{}; 308 | socklen_t length; 309 | 310 | if (address.is_ipv4()) { 311 | auto *sock_v4 = reinterpret_cast(&temp); 312 | length = sizeof(sockaddr_in); 313 | 314 | sock_v4->sin_family = AF_INET; 315 | sock_v4->sin_port = htons(port); 316 | memcpy(&sock_v4->sin_addr, &address.ipv4(), sizeof(in_addr)); 317 | } else { 318 | auto *sock_v6 = reinterpret_cast(&temp); 319 | length = sizeof(sockaddr_in6); 320 | 321 | sock_v6->sin6_family = AF_INET6; 322 | sock_v6->sin6_port = htons(port); 323 | memcpy(&sock_v6->sin6_addr, &address.ipv6(), sizeof(in6_addr)); 324 | } 325 | 326 | return {&m_socket, temp, length, SOCK_STREAM, IPPROTO_TCP}; 327 | } 328 | 329 | auto coco::tcp_connection::shutdown() noexcept -> std::error_code { 330 | if (::shutdown(m_socket, SHUT_WR) < 0) 331 | return {errno, std::system_category()}; 332 | return {}; 333 | } 334 | 335 | auto coco::tcp_connection::close() noexcept -> void { 336 | if (m_socket != -1) { 337 | ::close(m_socket); 338 | m_socket = -1; 339 | } 340 | } 341 | 342 | auto coco::tcp_connection::keepalive(bool enable) noexcept -> std::error_code { 343 | int value = enable ? 1 : 0; 344 | int result = 345 | setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value)); 346 | if (result == -1) 347 | return std::error_code{errno, std::system_category()}; 348 | return std::error_code{}; 349 | } 350 | 351 | auto coco::tcp_connection::address() const noexcept -> ip_address { 352 | sockaddr_storage addr{}; 353 | socklen_t length = sizeof(addr); 354 | 355 | int result = 356 | getpeername(m_socket, reinterpret_cast(&addr), &length); 357 | assert(result != -1); 358 | (void)result; 359 | 360 | if (addr.ss_family == AF_INET) { 361 | assert(length == sizeof(sockaddr_in)); 362 | ipv4_address address; 363 | sockaddr_in *v4 = reinterpret_cast(&addr); 364 | memcpy(&address, &v4->sin_addr, sizeof(in_addr)); 365 | return address; 366 | } else { 367 | assert(addr.ss_family == AF_INET6); 368 | assert(length == sizeof(sockaddr_in6)); 369 | ipv6_address address; 370 | sockaddr_in6 *v6 = reinterpret_cast(&addr); 371 | memcpy(&address, &v6->sin6_addr, sizeof(in6_addr)); 372 | return address; 373 | } 374 | } 375 | 376 | auto coco::tcp_connection::port() const noexcept -> uint16_t { 377 | sockaddr_storage addr{}; 378 | socklen_t length = sizeof(addr); 379 | 380 | int result = 381 | getpeername(m_socket, reinterpret_cast(&addr), &length); 382 | assert(result != -1); 383 | (void)result; 384 | 385 | if (addr.ss_family == AF_INET) { 386 | assert(length == sizeof(sockaddr_in)); 387 | sockaddr_in *v4 = reinterpret_cast(&addr); 388 | return ntohs(v4->sin_port); 389 | } else { 390 | assert(addr.ss_family == AF_INET6); 391 | assert(length == sizeof(sockaddr_in6)); 392 | sockaddr_in6 *v6 = reinterpret_cast(&addr); 393 | return ntohs(v6->sin6_port); 394 | } 395 | } 396 | 397 | auto coco::tcp_connection::set_timeout(int64_t microseconds) noexcept 398 | -> std::error_code { 399 | timeval timeout{ 400 | .tv_sec = microseconds / 1000000, 401 | .tv_usec = microseconds % 1000000, 402 | }; 403 | 404 | int result = setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, 405 | sizeof(timeout)); 406 | if (result == -1) 407 | return std::error_code{errno, std::system_category()}; 408 | return std::error_code{}; 409 | } 410 | 411 | auto coco::tcp_server::accept_awaitable::suspend( 412 | detail::io_context_worker *worker) noexcept -> bool { 413 | assert(worker != nullptr); 414 | m_userdata.cqe_res = 0; 415 | m_userdata.cqe_flags = 0; 416 | 417 | io_uring *ring = worker->io_ring(); 418 | io_uring_sqe *sqe = io_uring_get_sqe(ring); 419 | while (sqe == nullptr) [[unlikely]] { 420 | io_uring_submit(ring); 421 | sqe = io_uring_get_sqe(ring); 422 | } 423 | 424 | io_uring_prep_accept(sqe, m_socket, 425 | reinterpret_cast(&m_sockaddr), &m_socklen, 426 | SOCK_CLOEXEC); 427 | sqe->user_data = reinterpret_cast(&m_userdata); 428 | 429 | int result = io_uring_submit(ring); 430 | if (result < 0) [[unlikely]] { // Result is -errno. 431 | m_userdata.cqe_res = result; 432 | return false; 433 | } 434 | 435 | return true; 436 | } 437 | 438 | coco::tcp_server::~tcp_server() { 439 | if (m_socket != -1) { 440 | assert(m_socket >= 0); 441 | ::close(m_socket); 442 | } 443 | } 444 | 445 | auto coco::tcp_server::listen(const ip_address &address, 446 | uint16_t port) noexcept -> std::error_code { 447 | sockaddr_storage temp{}; 448 | socklen_t length; 449 | 450 | if (address.is_ipv4()) { 451 | auto *sock_v4 = reinterpret_cast(&temp); 452 | length = sizeof(sockaddr_in); 453 | 454 | sock_v4->sin_family = AF_INET; 455 | sock_v4->sin_port = htons(port); 456 | memcpy(&sock_v4->sin_addr, &address.ipv4(), sizeof(in_addr)); 457 | } else { 458 | auto *sock_v6 = reinterpret_cast(&temp); 459 | length = sizeof(sockaddr_in6); 460 | 461 | sock_v6->sin6_family = AF_INET6; 462 | sock_v6->sin6_port = htons(port); 463 | memcpy(&sock_v6->sin6_addr, &address.ipv6(), sizeof(in6_addr)); 464 | } 465 | 466 | int new_socket = socket(temp.ss_family, SOCK_STREAM, IPPROTO_TCP); 467 | if (new_socket < 0) 468 | return {errno, std::system_category()}; 469 | 470 | int result = bind(new_socket, reinterpret_cast(&temp), length); 471 | if (result < 0) { 472 | int error = errno; 473 | ::close(new_socket); 474 | return {error, std::system_category()}; 475 | } 476 | 477 | result = ::listen(new_socket, SOMAXCONN); 478 | if (result < 0) { 479 | int error = errno; 480 | ::close(new_socket); 481 | return {error, std::system_category()}; 482 | } 483 | 484 | if (m_socket != -1) 485 | ::close(m_socket); 486 | 487 | m_address = address; 488 | m_port = port; 489 | m_socket = new_socket; 490 | 491 | // Set reuse address and port. 492 | int enable = 1; 493 | setsockopt(new_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); 494 | 495 | enable = 1; 496 | setsockopt(new_socket, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)); 497 | 498 | return {}; 499 | } 500 | 501 | auto coco::tcp_server::close() noexcept -> void { 502 | if (m_socket != -1) { 503 | assert(m_socket >= 0); 504 | ::close(m_socket); 505 | m_socket = -1; 506 | } 507 | } 508 | 509 | coco::binary_file::~binary_file() { 510 | if (m_file != -1) { 511 | assert(m_file >= 0); 512 | ::close(m_file); 513 | } 514 | } 515 | 516 | auto coco::binary_file::operator=(binary_file &&other) noexcept 517 | -> binary_file & { 518 | if (this == &other) [[unlikely]] 519 | return *this; 520 | 521 | if (m_file != -1) 522 | ::close(m_file); 523 | 524 | m_file = other.m_file; 525 | other.m_file = -1; 526 | 527 | return *this; 528 | } 529 | 530 | auto coco::binary_file::size() noexcept -> size_t { 531 | struct stat info {}; 532 | 533 | // FIXME: Report error. 534 | int result = fstat(m_file, &info); 535 | assert(result >= 0); 536 | (void)result; 537 | 538 | return static_cast(info.st_size); 539 | } 540 | 541 | auto coco::binary_file::open(std::string_view path, 542 | flag flags) noexcept -> std::error_code { 543 | char buffer[PATH_MAX]; 544 | if (path.size() >= PATH_MAX) 545 | return std::error_code{EINVAL, std::system_category()}; 546 | 547 | memcpy(buffer, path.data(), path.size()); 548 | buffer[path.size()] = '\0'; 549 | 550 | int unix_flag = O_DIRECT | O_CLOEXEC; 551 | int unix_mode = 0; 552 | if ((flags & flag::write) == flag::write) { 553 | unix_flag |= O_CREAT; 554 | if ((flags & flag::read) == flag::read) 555 | unix_flag |= O_RDWR; 556 | else 557 | unix_flag |= O_WRONLY; 558 | 559 | if ((flags & flag::append) == flag::append) 560 | unix_flag |= O_APPEND; 561 | if ((flags & flag::trunc) == flag::trunc) 562 | unix_flag |= O_TRUNC; 563 | 564 | unix_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 565 | } else if ((flags & flag::read) == flag::read) { 566 | unix_flag |= O_RDONLY; 567 | } 568 | 569 | int result = ::open(buffer, unix_flag, unix_mode); 570 | if (result < 0) 571 | return std::error_code{errno, std::system_category()}; 572 | 573 | if (m_file != -1) 574 | ::close(m_file); 575 | m_file = result; 576 | 577 | return std::error_code{}; 578 | } 579 | 580 | auto coco::binary_file::seek(seek_whence whence, 581 | ptrdiff_t offset) noexcept -> std::error_code { 582 | int unix_whence = 583 | (whence == seek_whence::begin 584 | ? SEEK_SET 585 | : (whence == seek_whence::current ? SEEK_CUR : SEEK_END)); 586 | 587 | auto result = ::lseek(m_file, offset, unix_whence); 588 | if (result < 0) 589 | return std::error_code{errno, std::system_category()}; 590 | 591 | return std::error_code{}; 592 | } 593 | 594 | coco::timer::timer() noexcept 595 | : m_timer{timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)} { 596 | assert(m_timer >= 0); 597 | } 598 | 599 | coco::timer::~timer() { 600 | if (m_timer != -1) 601 | close(m_timer); 602 | } 603 | 604 | auto coco::timer::operator=(timer &&other) noexcept -> timer & { 605 | if (this == &other) [[unlikely]] 606 | return *this; 607 | 608 | if (m_timer != -1) 609 | close(m_timer); 610 | 611 | m_timer = other.m_timer; 612 | other.m_timer = -1; 613 | 614 | return *this; 615 | } 616 | -------------------------------------------------------------------------------- /include/coco/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace coco { 14 | 15 | /// \class task 16 | /// \tparam T 17 | /// Return type of the coroutine. 18 | /// \brief 19 | /// Task type for coroutines. 20 | template class task; 21 | 22 | /// \class promise 23 | /// \tparam T 24 | /// Return type of the coroutine. 25 | /// \brief 26 | /// Promise type for tasks. 27 | template class promise; 28 | 29 | namespace detail { 30 | 31 | /// \class io_context_worker 32 | /// \brief 33 | /// For internal usage. Worker class for io_context. 34 | class io_context_worker; 35 | 36 | } // namespace detail 37 | } // namespace coco 38 | 39 | namespace coco::detail { 40 | 41 | /// \class task_awaitable 42 | /// \tparam T 43 | /// Return type of the coroutine. 44 | /// \brief 45 | /// Awaitable object for tasks. 46 | template class task_awaitable { 47 | public: 48 | using value_type = T; 49 | using promise_type = typename coco::task::promise_type; 50 | using coroutine_handle = std::coroutine_handle; 51 | 52 | /// \brief 53 | /// For internal usage. Create a new task awaitable for the given 54 | /// coroutine. Used by task::operator co_await. 55 | /// \param coroutine 56 | /// Current coroutine handle. 57 | task_awaitable(coroutine_handle coroutine) : m_coroutine(coroutine) {} 58 | 59 | /// \brief 60 | /// For internal usage. Checks if current coroutine should be suspended. 61 | /// Empty coroutine or completed coroutine should not be suspended. 62 | /// \retval true 63 | /// Current coroutine should be suspended. 64 | /// \retval false 65 | /// Current coroutine should not be suspended. 66 | [[nodiscard]] auto await_ready() const noexcept -> bool { 67 | return (m_coroutine == nullptr) || m_coroutine.done(); 68 | } 69 | 70 | /// \brief 71 | /// C++20 coroutine awaitable type method. Suspend the caller coroutine 72 | /// and start this one. 73 | /// \tparam Promise 74 | /// Promise type of the caller coroutine. 75 | /// \param caller 76 | /// Caller coroutine handle. 77 | /// \return 78 | /// Coroutine handle of this task. 79 | template 80 | auto await_suspend(std::coroutine_handle caller) noexcept 81 | -> coroutine_handle; 82 | 83 | /// \brief 84 | /// C++20 coroutine awaitable type method. Get result of the coroutine. 85 | /// \return 86 | /// Return value of the task. 87 | auto await_resume() const noexcept -> value_type { 88 | return std::move(m_coroutine.promise()).result(); 89 | } 90 | 91 | private: 92 | coroutine_handle m_coroutine; 93 | }; 94 | 95 | /// \class promise_awaitable 96 | /// \brief 97 | /// Awaitable object for promise final suspend. 98 | class promise_awaitable { 99 | public: 100 | /// \brief 101 | /// For internal usage. Promise final suspend should always be performed. 102 | /// \return 103 | /// This method always returns false. 104 | [[nodiscard]] auto await_ready() const noexcept -> bool { 105 | return false; 106 | } 107 | 108 | /// \brief 109 | /// C++20 coroutine awaitable type method. Maintain the coroutine promise 110 | /// status and do suspention or resumption. 111 | /// \tparam Promise 112 | /// Promise type of the caller coroutine. 113 | /// \param coroutine 114 | /// Caller coroutine handle. 115 | /// \return 116 | /// A coroutine handle to be resumed. A noop-coroutine handle is returned 117 | /// if the caller is done. 118 | template 119 | auto await_suspend(std::coroutine_handle coroutine) noexcept 120 | -> std::coroutine_handle<>; 121 | 122 | /// \brief 123 | /// C++20 coroutine awaitable type method. Nothing to do here. 124 | auto await_resume() const noexcept -> void {} 125 | }; 126 | 127 | /// \class promise_base 128 | /// \brief 129 | /// Base class for promise types. 130 | class promise_base { 131 | public: 132 | /// \brief 133 | /// Create an empty promise base. 134 | promise_base() noexcept 135 | : m_coroutine{}, m_caller_or_top{this}, m_stack_bottom{this}, 136 | m_worker{nullptr} {} 137 | 138 | /// \brief 139 | /// Promise is not allowed to be copied. 140 | promise_base(const promise_base &other) = delete; 141 | 142 | /// \brief 143 | /// Promise is not allowed to be moved. 144 | promise_base(promise_base &&other) = delete; 145 | 146 | /// \brief 147 | /// Destroy this promise base. 148 | ~promise_base() = default; 149 | 150 | /// \brief 151 | /// Promise is not allowed to be copied. 152 | auto operator=(const promise_base &other) = delete; 153 | 154 | /// \brief 155 | /// Promise is not allowed to be moved. 156 | auto operator=(promise_base &&other) = delete; 157 | 158 | /// \brief 159 | /// C++20 coroutine promise type method. Initial suspend of the coroutine. 160 | /// Tasks are always suspended once created. 161 | /// \return 162 | /// Always returns std::suspend_always. 163 | auto initial_suspend() noexcept -> std::suspend_always { 164 | return {}; 165 | } 166 | 167 | /// \brief 168 | /// C++20 coroutine promise type method. Final suspend of the coroutine. 169 | /// Maintain the coroutine status and do suspention or resumption. 170 | /// \return 171 | /// A customized promise awaitable object. 172 | [[nodiscard]] auto final_suspend() const noexcept 173 | -> detail::promise_awaitable { 174 | return {}; 175 | } 176 | 177 | /// \brief 178 | /// Get current stack top of this coroutine call chain. 179 | /// \return 180 | /// Stack top of this coroutine call chain. 181 | [[nodiscard]] auto stack_top() const noexcept -> std::coroutine_handle<> { 182 | return m_stack_bottom->m_caller_or_top->m_coroutine; 183 | } 184 | 185 | /// \brief 186 | /// Get current stack bottom of this coroutine call chain. 187 | /// \return 188 | /// Stack bottom of this coroutine call chain. 189 | [[nodiscard]] auto stack_bottom() const noexcept 190 | -> std::coroutine_handle<> { 191 | return m_stack_bottom->m_coroutine; 192 | } 193 | 194 | /// \brief 195 | /// Set I/O context for current coroutine. 196 | /// \param[in] io_ctx 197 | /// I/O context to be set for current coroutine. 198 | auto set_worker(io_context_worker *io_ctx) noexcept -> void { 199 | m_stack_bottom->m_worker.store(io_ctx, std::memory_order_release); 200 | } 201 | 202 | /// \brief 203 | /// Get I/O context for current coroutine. 204 | /// \return 205 | /// I/O context for current coroutine. 206 | [[nodiscard]] auto worker() const noexcept -> io_context_worker * { 207 | return m_stack_bottom->m_worker.load(std::memory_order_acquire); 208 | } 209 | 210 | template friend class coco::task; 211 | template friend class coco::detail::task_awaitable; 212 | friend class coco::detail::promise_awaitable; 213 | 214 | protected: 215 | std::coroutine_handle<> m_coroutine; 216 | promise_base *m_caller_or_top; 217 | promise_base *m_stack_bottom; 218 | std::atomic m_worker; 219 | }; 220 | 221 | } // namespace coco::detail 222 | 223 | namespace coco { 224 | 225 | /// \class task 226 | /// \tparam T 227 | /// Return type of the coroutine. 228 | template class promise : public detail::promise_base { 229 | public: 230 | using value_type = T; 231 | using reference = value_type &; 232 | using rvalue_reference = value_type &&; 233 | 234 | /// \brief 235 | /// Create an empty promise. 236 | promise() noexcept : detail::promise_base{}, m_kind{value_kind::null} {} 237 | 238 | /// \brief 239 | /// Promise is not allowed to be copied. 240 | promise(const promise &other) = delete; 241 | 242 | /// \brief 243 | /// Promise is not allowed to be moved. 244 | promise(promise &&other) = delete; 245 | 246 | /// \brief 247 | /// Destroy this promise. 248 | ~promise(); 249 | 250 | /// \brief 251 | /// Promise is not allowed to be copied. 252 | auto operator=(const promise &other) = delete; 253 | 254 | /// \brief 255 | /// Promise is not allowed to be moved. 256 | auto operator=(promise &&other) = delete; 257 | 258 | /// \brief 259 | /// C++20 coroutine promise type method. Get the coroutine object for this 260 | /// promise. 261 | /// \return 262 | /// A task to the coroutine object. 263 | auto get_return_object() noexcept -> task; 264 | 265 | /// \brief 266 | /// C++20 coroutine promise type method. Catch and store unhandled 267 | /// exception in this promise if exists. 268 | auto unhandled_exception() noexcept -> void; 269 | 270 | /// \brief 271 | /// C++20 coroutine promise type method. Set the return value of the 272 | /// coroutine. 273 | /// \tparam From 274 | /// Type of the returned object to be used to construct the actual return 275 | /// value. 276 | /// \param value 277 | /// Returnd object of the coroutine. 278 | template >> 280 | auto return_value(From &&value) noexcept( 281 | std::is_nothrow_constructible_v) -> void; 282 | 283 | /// \brief 284 | /// C++20 coroutine promise type method. Get return value of the 285 | /// coroutine. Exceptions may be thrown if caught any by the coroutine. 286 | /// \return 287 | /// Return reference to return value of the coroutine. 288 | [[nodiscard]] auto result() & -> reference; 289 | 290 | /// \brief 291 | /// C++20 coroutine promise type method. Get return value of the 292 | /// coroutine. Exceptions may be thrown if caught any by the coroutine. 293 | /// \return 294 | /// Return reference to return value of the coroutine. 295 | [[nodiscard]] auto result() && -> rvalue_reference; 296 | 297 | private: 298 | enum class value_kind : intptr_t { 299 | null, 300 | exception, 301 | value, 302 | }; 303 | 304 | value_kind m_kind; 305 | union data { // NOLINT 306 | data() noexcept : null{nullptr} {} 307 | ~data() noexcept {} 308 | 309 | std::nullptr_t null; 310 | std::exception_ptr exception; 311 | alignas(T) char storage[sizeof(T)]; 312 | } m_value; 313 | }; 314 | 315 | /// \class promise 316 | /// \brief 317 | /// Specialized promise type for tasks returning void. 318 | template <> class promise : public detail::promise_base { 319 | public: 320 | using value_type = void; 321 | 322 | /// \brief 323 | /// Create an empty promise. 324 | promise() noexcept : detail::promise_base{}, m_exception{} {} 325 | 326 | /// \brief 327 | /// Promise is not allowed to be copied. 328 | promise(const promise &other) = delete; 329 | 330 | /// \brief 331 | /// Promise is not allowed to be moved. 332 | promise(promise &&other) = delete; 333 | 334 | /// \brief 335 | /// Destroy this promise. 336 | ~promise() = default; 337 | 338 | /// \brief 339 | /// Promise is not allowed to be copied. 340 | auto operator=(const promise &other) = delete; 341 | 342 | /// \brief 343 | /// Promise is not allowed to be moved. 344 | auto operator=(promise &&other) = delete; 345 | 346 | /// \brief 347 | /// C++20 coroutine promise type method. Get the coroutine object for this 348 | /// promise. 349 | /// \return 350 | /// A task to the coroutine object. 351 | auto get_return_object() noexcept -> task; 352 | 353 | /// \brief 354 | /// C++20 coroutine promise type method. Catch and store unhandled 355 | /// exception in this promise if exists. 356 | auto unhandled_exception() noexcept -> void { 357 | m_exception = std::current_exception(); 358 | } 359 | 360 | /// \brief 361 | /// C++20 coroutine promise type method. Marks that this coroutine has no 362 | /// return value. 363 | auto return_void() noexcept -> void {} 364 | 365 | /// \brief 366 | /// C++20 coroutine promise type method. Get return value of the 367 | /// coroutine. Exceptions may be thrown if caught any by the coroutine. 368 | auto result() -> void { 369 | if (m_exception != nullptr) [[unlikely]] 370 | std::rethrow_exception(m_exception); 371 | } 372 | 373 | template friend class coco::detail::task_awaitable; 374 | friend class coco::detail::promise_awaitable; 375 | 376 | private: 377 | std::exception_ptr m_exception; 378 | }; 379 | 380 | /// \class promise 381 | /// \brief 382 | /// Specialized promise type for tasks returning reference. 383 | template class promise : public detail::promise_base { 384 | public: 385 | using value_type = T; 386 | using reference = value_type &; 387 | 388 | /// \brief 389 | /// Create an empty promise. 390 | promise() noexcept 391 | : detail::promise_base{}, m_kind{value_kind::null}, m_value{nullptr} {} 392 | 393 | /// \brief 394 | /// Promise is not allowed to be copied. 395 | promise(const promise &other) = delete; 396 | 397 | /// \brief 398 | /// Promise is not allowed to be moved. 399 | promise(promise &&other) = delete; 400 | 401 | /// \brief 402 | /// Destroy this promise. 403 | ~promise() = default; 404 | 405 | /// \brief 406 | /// Promise is not allowed to be copied. 407 | auto operator=(const promise &other) = delete; 408 | 409 | /// \brief 410 | /// Promise is not allowed to be moved. 411 | auto operator=(promise &&other) = delete; 412 | 413 | /// \brief 414 | /// C++20 coroutine promise type method. Get the coroutine object for this 415 | /// promise. 416 | /// \return 417 | /// A coroutine handle to the coroutine object. 418 | auto get_return_object() noexcept -> task; 419 | 420 | /// \brief 421 | /// C++20 coroutine promise type method. Catch and store unhandled 422 | /// exception in this promise if exists. 423 | auto unhandled_exception() noexcept -> void; 424 | 425 | /// \brief 426 | /// C++20 coroutine promise type method. Set the return value of the 427 | /// coroutine. 428 | /// \param value 429 | /// Return value of the coroutine. 430 | auto return_value(reference value) noexcept -> void; 431 | 432 | /// \brief 433 | /// C++20 coroutine promise type method. Get return value of the 434 | /// coroutine. Exceptions may be thrown if caught any by the coroutine. 435 | /// \return 436 | /// Return reference to return value of the coroutine. 437 | [[nodiscard]] auto result() -> reference; 438 | 439 | private: 440 | enum class value_kind : intptr_t { 441 | null, 442 | exception, 443 | value, 444 | }; 445 | 446 | value_kind m_kind; 447 | union data { // NOLINT 448 | data() noexcept {} 449 | ~data() noexcept {} 450 | 451 | std::nullptr_t null; 452 | std::exception_ptr exception; 453 | value_type *value; 454 | } m_value; 455 | }; 456 | 457 | /// \class task 458 | /// \tparam T 459 | /// Return type of the coroutine. 460 | /// \class 461 | /// Task type for coroutines. 462 | template class task { 463 | public: 464 | using value_type = T; 465 | using promise_type = promise; 466 | using coroutine_handle = std::coroutine_handle; 467 | 468 | /// \brief 469 | /// Create an empty task. Empty task is not a valid coroutine. 470 | task() noexcept : m_coroutine{} {} 471 | 472 | /// \brief 473 | /// Create a task from the given coroutine. 474 | /// \param coroutine 475 | /// Coroutine handle of current task coroutine. 476 | explicit task(coroutine_handle coroutine) noexcept 477 | : m_coroutine{coroutine} {} 478 | 479 | /// \brief 480 | /// Task is not allowed to be copied. 481 | task(const task &other) = delete; 482 | 483 | /// \brief 484 | /// Move constructor for task. 485 | /// \param other 486 | /// The task to be moved. The moved task will be set to empty. 487 | task(task &&other) noexcept : m_coroutine{other.m_coroutine} { 488 | other.m_coroutine = nullptr; 489 | } 490 | 491 | /// \brief 492 | /// Destroy this task and the coroutine context. 493 | ~task() { 494 | if (m_coroutine != nullptr) [[likely]] 495 | m_coroutine.destroy(); 496 | } 497 | 498 | /// \brief 499 | /// Task is not allowed to be copied. 500 | auto operator=(const task &other) = delete; 501 | 502 | /// \brief 503 | /// Move assignment for task. 504 | /// \param other 505 | /// The task to be moved. The moved task will be set to empty. 506 | auto operator=(task &&other) noexcept -> task &; 507 | 508 | /// \brief 509 | /// Checks if this task is empty. 510 | /// \retval true 511 | /// This task is empty. 512 | /// \retval false 513 | /// This task is not empty. 514 | [[nodiscard]] auto empty() const noexcept -> bool { 515 | return (m_coroutine == nullptr); 516 | } 517 | 518 | /// \brief 519 | /// Checks if this task is empty or done. 520 | /// \retval true 521 | /// This task is empty or done. 522 | /// \retval false 523 | /// This task is not empty and not done. 524 | [[nodiscard]] auto done() const noexcept -> bool { 525 | return (m_coroutine == nullptr) || m_coroutine.done(); 526 | } 527 | 528 | /// \brief 529 | /// Resume this task coroutine if not finished. 530 | auto resume() -> void; 531 | 532 | /// \brief 533 | /// Get return value of the coroutine. Exceptions may be thrown if caught 534 | /// any by the coroutine. 535 | /// \note 536 | /// This method does not check if the coroutine is done. It is undefined 537 | /// behavior to call this on a non-finished coroutine. 538 | /// \return 539 | /// Return value of thie coroutine. 540 | auto result() -> value_type { 541 | return std::move(m_coroutine.promise()).result(); 542 | } 543 | 544 | /// \brief 545 | /// Detach the coroutine handle from this task. The task will be set to 546 | /// empty. 547 | /// \return 548 | /// Detached coroutine handle of this task coroutine. 549 | [[nodiscard]] auto detach() noexcept -> coroutine_handle { 550 | auto coroutine = m_coroutine; 551 | m_coroutine = nullptr; 552 | return coroutine; 553 | } 554 | 555 | /// \brief 556 | /// Get address for this coroutine frame. 557 | /// \return 558 | /// Address of the coroutine frame. 559 | [[nodiscard]] auto address() const noexcept -> void * { 560 | return m_coroutine.address(); 561 | } 562 | 563 | /// \brief 564 | /// C++20 coroutine awaitable type method. Suspend the caller coroutine 565 | /// and start this one. 566 | auto operator co_await() const noexcept 567 | -> detail::task_awaitable { 568 | return detail::task_awaitable{m_coroutine}; 569 | } 570 | 571 | private: 572 | coroutine_handle m_coroutine; 573 | }; 574 | 575 | } // namespace coco 576 | 577 | namespace coco::detail { 578 | 579 | template 580 | template 581 | auto task_awaitable::await_suspend( 582 | std::coroutine_handle caller) noexcept -> coroutine_handle { 583 | // Set caller for this coroutine. 584 | promise_base &base = static_cast(m_coroutine.promise()); 585 | promise_base &caller_base = static_cast(caller.promise()); 586 | base.m_caller_or_top = &caller_base; 587 | 588 | // Maintain stack bottom and top. 589 | promise_base *stack_bottom = caller_base.m_stack_bottom; 590 | assert(stack_bottom == stack_bottom->m_stack_bottom); 591 | 592 | base.m_stack_bottom = stack_bottom; 593 | stack_bottom->m_caller_or_top = &base; 594 | 595 | return m_coroutine; 596 | } 597 | 598 | template 599 | auto promise_awaitable::await_suspend( 600 | std::coroutine_handle coroutine) noexcept 601 | -> std::coroutine_handle<> { 602 | promise_base &base = static_cast(coroutine.promise()); 603 | promise_base *stack = base.m_stack_bottom; 604 | 605 | assert(stack->m_caller_or_top == &base); 606 | assert(stack->m_stack_bottom == stack); 607 | 608 | // Stack bottom completed. Nothing to resume. 609 | if (stack == &base) 610 | return std::noop_coroutine(); 611 | 612 | // Set caller coroutine as the top of the stack. 613 | promise_base *caller = base.m_caller_or_top; 614 | stack->m_caller_or_top = caller; 615 | 616 | // Resume caller. 617 | return caller->m_coroutine; 618 | } 619 | 620 | } // namespace coco::detail 621 | 622 | namespace coco { 623 | 624 | template promise::~promise() { 625 | if (m_kind == value_kind::value) [[likely]] 626 | reinterpret_cast(m_value.storage)->~T(); 627 | else if (m_kind == value_kind::exception) [[unlikely]] 628 | m_value.exception.~exception_ptr(); 629 | } 630 | 631 | template auto promise::get_return_object() noexcept -> task { 632 | auto coroutine = std::coroutine_handle::from_promise(*this); 633 | this->m_coroutine = coroutine; 634 | return task{coroutine}; 635 | } 636 | 637 | template auto promise::unhandled_exception() noexcept -> void { 638 | assert(m_kind == value_kind::null); 639 | m_kind = value_kind::exception; 640 | ::new (static_cast(&m_value.exception)) 641 | std::exception_ptr{std::current_exception()}; 642 | } 643 | 644 | template 645 | template 646 | auto promise::return_value(From &&value) noexcept( 647 | std::is_nothrow_constructible_v) -> void { 648 | assert(m_kind == value_kind::null); 649 | m_kind = value_kind::value; 650 | ::new (static_cast(m_value.storage)) 651 | value_type{std::forward(value)}; 652 | } 653 | 654 | template auto promise::result() & -> reference { 655 | if (m_kind == value_kind::exception) [[unlikely]] 656 | std::rethrow_exception(m_value.exception); 657 | 658 | assert(m_kind == value_kind::value); 659 | return *reinterpret_cast(m_value.storage); 660 | } 661 | 662 | template auto promise::result() && -> rvalue_reference { 663 | if (m_kind == value_kind::exception) [[unlikely]] 664 | std::rethrow_exception(m_value.exception); 665 | 666 | assert(m_kind == value_kind::value); 667 | return std::move(*reinterpret_cast(m_value.storage)); 668 | } 669 | 670 | inline auto promise::get_return_object() noexcept -> task { 671 | auto coroutine = std::coroutine_handle::from_promise(*this); 672 | this->m_coroutine = coroutine; 673 | return task{coroutine}; 674 | } 675 | 676 | template 677 | auto promise::get_return_object() noexcept -> task { 678 | auto coroutine = std::coroutine_handle::from_promise(*this); 679 | this->m_coroutine = coroutine; 680 | return task{coroutine}; 681 | } 682 | 683 | template auto promise::unhandled_exception() noexcept -> void { 684 | assert(m_kind == value_kind::null); 685 | m_kind = value_kind::exception; 686 | ::new (static_cast(&m_value.exception)) 687 | std::exception_ptr{std::current_exception()}; 688 | } 689 | 690 | template 691 | auto promise::return_value(reference value) noexcept -> void { 692 | assert(m_kind == value_kind::null); 693 | m_kind = value_kind::value; 694 | m_value.value = std::addressof(value); 695 | } 696 | 697 | template auto promise::result() -> reference { 698 | if (m_kind == value_kind::exception) [[unlikely]] 699 | std::rethrow_exception(m_value.exception); 700 | 701 | assert(m_kind == value_kind::value); 702 | return *m_value.value; 703 | } 704 | 705 | template auto task::operator=(task &&other) noexcept -> task & { 706 | if (this == &other) [[unlikely]] 707 | return *this; 708 | 709 | if (m_coroutine != nullptr) 710 | m_coroutine.destroy(); 711 | 712 | m_coroutine = other.m_coroutine; 713 | other.m_coroutine = nullptr; 714 | 715 | return *this; 716 | } 717 | 718 | template auto task::resume() -> void { 719 | if (this->done()) [[unlikely]] 720 | return; 721 | 722 | auto &base = static_cast(m_coroutine.promise()); 723 | auto stack_top = base.m_stack_bottom->m_caller_or_top->m_coroutine; 724 | stack_top.resume(); 725 | } 726 | 727 | template 728 | auto operator==(const task &task, std::nullptr_t) noexcept -> bool { 729 | return task.empty(); 730 | } 731 | 732 | template 733 | auto operator==(std::nullptr_t, const task &task) noexcept -> bool { 734 | return task.empty(); 735 | } 736 | 737 | } // namespace coco 738 | 739 | namespace coco::detail { 740 | 741 | /// \struct user_data 742 | /// \brief 743 | /// Base class for asynchronize IO user data. 744 | struct user_data { 745 | void *coroutine; 746 | int cqe_res; 747 | uint32_t cqe_flags; 748 | }; 749 | 750 | /// \class io_context_worker 751 | /// \brief 752 | /// For internal usage. Worker class for io_context. 753 | class io_context_worker { 754 | public: 755 | /// \brief 756 | /// Create a new worker. 757 | /// \note 758 | /// Program may terminate if failed to prepare IO uring. 759 | COCO_API io_context_worker() noexcept; 760 | 761 | /// \brief 762 | /// io_context_worker is not allowed to be copied. 763 | io_context_worker(const io_context_worker &other) = delete; 764 | 765 | /// \brief 766 | /// io_context_worker is not allowed to be moved. 767 | io_context_worker(io_context_worker &&other) = delete; 768 | 769 | /// \brief 770 | /// Stop this worker and release all resources. 771 | COCO_API ~io_context_worker(); 772 | 773 | /// \brief 774 | /// io_context_worker is not allowed to be copied. 775 | auto operator=(const io_context_worker &other) = delete; 776 | 777 | /// \brief 778 | /// io_context_worker is not allowed to be moved. 779 | auto operator=(io_context_worker &&other) = delete; 780 | 781 | /// \brief 782 | /// Wake up this worker immediately. 783 | COCO_API auto wake_up() noexcept -> void; 784 | 785 | /// \brief 786 | /// Start listening for IO events. 787 | COCO_API auto run() noexcept -> void; 788 | 789 | /// \brief 790 | /// Stop listening for IO events. This method will send a stop request and 791 | /// return immediately. 792 | COCO_API auto stop() noexcept -> void; 793 | 794 | /// \brief 795 | /// Checks if this worker is running. 796 | /// \retval true 797 | /// This worker is running. 798 | /// \retval false 799 | /// This worker is not running. 800 | [[nodiscard]] auto is_running() const noexcept -> bool { 801 | return m_is_running.load(std::memory_order_relaxed); 802 | } 803 | 804 | /// \brief 805 | /// Execute a task in this worker. This method will wake up this worker 806 | /// immediately. 807 | /// \tparam T 808 | /// Return type of the task. 809 | /// \param coro 810 | /// The task to be scheduled. This worker will take the ownership of the 811 | /// task. The scheduled task will only be resumed once and will be 812 | /// destroyed if it is done after the first resume. 813 | template auto execute(task coro) noexcept -> void { 814 | auto handle = coro.detach(); 815 | auto &base = static_cast(handle.promise()); 816 | base.set_worker(this); 817 | this->execute(handle); 818 | } 819 | 820 | /// \brief 821 | /// Schedule a task in this worker. 822 | /// \tparam T 823 | /// Return type of the task. 824 | /// \param coro 825 | /// The task to be scheduled. This worker will take the ownership of the 826 | /// task. The scheduled task will only be resumed once and will be 827 | /// destroyed if it is done after the first resume. 828 | template auto schedule(task coro) noexcept -> void { 829 | auto handle = coro.detach(); 830 | auto &base = static_cast(handle.promise()); 831 | base.set_worker(this); 832 | this->schedule(handle); 833 | } 834 | 835 | /// \brief 836 | /// For internal usage. Get IO uring of this worker. 837 | /// \return 838 | /// Pointer to the IO uring. 839 | [[nodiscard]] auto io_ring() noexcept -> io_uring * { 840 | return &m_ring; 841 | } 842 | 843 | /// \brief 844 | /// Execute a new task in this worker. This method will wake up this 845 | /// worker immediately. 846 | /// \param coro 847 | /// The task to be scheduled. The scheduled task will only be resumed once 848 | /// and will be destroyed if it is done after the first resume. 849 | auto execute(std::coroutine_handle<> coro) noexcept -> void { 850 | { // Append this task to the task list. 851 | std::lock_guard lock{m_mutex}; 852 | m_tasks.push_back(coro); 853 | } 854 | 855 | this->wake_up(); 856 | } 857 | 858 | /// \brief 859 | /// Schedule a new task in this worker. 860 | /// \param coro 861 | /// The task to be scheduled. The scheduled task will only be resumed once 862 | /// and will be destroyed if it is done after the first resume. 863 | auto schedule(std::coroutine_handle<> coro) noexcept -> void { 864 | // Append this task to the task list only. 865 | std::lock_guard lock{m_mutex}; 866 | m_tasks.push_back(coro); 867 | } 868 | 869 | private: 870 | using task_list = std::vector>; 871 | 872 | std::atomic_bool m_should_exit; 873 | std::atomic_bool m_is_running; 874 | io_uring m_ring; 875 | int m_wake_up; 876 | uint64_t m_wake_up_buffer; 877 | 878 | std::mutex m_mutex; 879 | task_list m_tasks; 880 | }; 881 | 882 | } // namespace coco::detail 883 | 884 | namespace coco { 885 | 886 | /// \class io_context 887 | /// \brief 888 | /// Working context for asynchronous IO operations. 889 | class io_context { 890 | public: 891 | /// \brief 892 | /// Create a new IO context. Number of workers will be set to the number 893 | /// of hardware threads. 894 | COCO_API io_context() noexcept; 895 | 896 | /// \brief 897 | /// Create a new IO context with the specified number of workers. 898 | /// \param num_workers 899 | /// Expected number of workers. This value will be clamped to 1 if it is 900 | /// less than 1. 901 | COCO_API explicit io_context(uint32_t num_workers) noexcept; 902 | 903 | /// \brief 904 | /// io_context is not allowed to be copied. 905 | io_context(const io_context &other) = delete; 906 | 907 | /// \brief 908 | /// io_context is not allowed to be moved. 909 | io_context(io_context &&other) = delete; 910 | 911 | /// \brief 912 | /// Stop all workers and release all resources. 913 | /// \warning 914 | /// There may be memory leaks if there are pending I/O tasks scheduled in 915 | /// asynchronize I/O handler when stopping. This is due to internal 916 | /// implementation of the worker class. This may be fixed in the future. 917 | COCO_API ~io_context(); 918 | 919 | /// \brief 920 | /// io_context is not allowed to be copied. 921 | auto operator=(const io_context &other) = delete; 922 | 923 | /// \brief 924 | /// io_context is not allowed to be moved. 925 | auto operator=(io_context &&other) = delete; 926 | 927 | /// \brief 928 | /// Start io_context threads. This method will also block current thread 929 | /// and handle IO events and tasks. 930 | COCO_API auto run() noexcept -> void; 931 | 932 | /// \brief 933 | /// Stop all workers. This method will send a stop request and return 934 | /// immediately. Workers may not be stopped immediately. 935 | /// \warning 936 | /// There may be memory leaks if there are pending I/O tasks scheduled in 937 | /// asynchronize I/O handler when stopping. This is due to internal 938 | /// implementation of the worker class. This may be fixed in the future. 939 | COCO_API auto stop() noexcept -> void; 940 | 941 | /// \brief 942 | /// Get number of workers in this io_context. 943 | /// \return 944 | /// Number of workers in this io_context. 945 | [[nodiscard]] auto size() const noexcept -> size_t { 946 | return m_num_workers; 947 | } 948 | 949 | /// \brief 950 | /// Execute a new task in a worker. This method will wake up worker 951 | /// immediately. 952 | /// \param coro 953 | /// The task to be scheduled. The scheduled task will only be resumed once 954 | /// and will be destroyed if it is done after the first resume. 955 | template auto execute(task coro) noexcept -> void { 956 | size_t next = m_next_worker.fetch_add(1, std::memory_order_relaxed) % 957 | m_num_workers; 958 | m_workers[next].execute(std::move(coro)); 959 | } 960 | 961 | /// \brief 962 | /// Schedule a new task in a worker. 963 | /// \param coro 964 | /// The task to be scheduled. The scheduled task will only be resumed once 965 | /// and will be destroyed if it is done after the first resume. 966 | template auto schedule(task coro) noexcept -> void { 967 | size_t next = m_next_worker.fetch_add(1, std::memory_order_relaxed) % 968 | m_num_workers; 969 | m_workers[next].schedule(std::move(coro)); 970 | } 971 | 972 | /// \brief 973 | /// Acquire current worker and update the next worker index. 974 | /// \return 975 | /// Reference to current worker. 976 | auto acquire_worker() noexcept -> detail::io_context_worker & { 977 | size_t next = m_next_worker.fetch_add(1, std::memory_order_relaxed) % 978 | m_num_workers; 979 | return m_workers[next]; 980 | } 981 | 982 | private: 983 | using worker_list = std::unique_ptr; 984 | using thread_list = std::unique_ptr; 985 | 986 | worker_list m_workers; 987 | thread_list m_threads; 988 | uint32_t m_num_workers; 989 | std::atomic_uint32_t m_next_worker; 990 | }; 991 | 992 | } // namespace coco 993 | -------------------------------------------------------------------------------- /include/coco/io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "task.hpp" 4 | 5 | #include 6 | 7 | namespace coco { 8 | 9 | /// \class read_awaitable 10 | /// \brief 11 | /// For internal usage. Awaitable object for async read operation. 12 | class [[nodiscard]] read_awaitable { 13 | public: 14 | /// \brief 15 | /// For internal usage. Create a new read awaitable object to prepare for 16 | /// async read operation. 17 | /// \param file 18 | /// File descriptor of the file to be read from. 19 | /// \param[out] buffer 20 | /// Pointer to start of the buffer to store the read data. 21 | /// \param size 22 | /// Expected size in byte of data to be read. 23 | /// \param[out] bytes 24 | /// Optional. Used to store how much bytes are read. 25 | /// \param offset 26 | /// Offset in byte of the file to start the read operation. Pass -1 to use 27 | /// current file pointer. 28 | read_awaitable(int file, void *buffer, uint32_t size, uint32_t *bytes, 29 | uint64_t offset) noexcept 30 | : m_userdata{}, m_file{file}, m_size{size}, m_buffer{buffer}, 31 | m_bytes{bytes}, m_offset{offset} {} 32 | 33 | /// \brief 34 | /// C++20 awaitable internal method. Always execute await_suspend(). 35 | auto await_ready() noexcept -> bool { 36 | return false; 37 | } 38 | 39 | /// \brief 40 | /// Prepare async read operation and suspend this coroutine. 41 | /// \tparam Promise 42 | /// Type of the promise for the coroutine. 43 | /// \param coro 44 | /// Current coroutine handle. 45 | /// \return 46 | /// A boolean value that specifies whether to suspend current coroutine. 47 | /// Current coroutine will be suspended if no error occurs. 48 | template 49 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 50 | m_userdata.coroutine = coro.address(); 51 | return this->suspend(coro.promise().worker()); 52 | } 53 | 54 | /// \brief 55 | /// Resume current coroutine and get result of the async read operation. 56 | /// \return 57 | /// Error code of the async read operation. The error code is 0 if no 58 | /// error occurs. 59 | auto await_resume() noexcept -> std::error_code { 60 | if (m_userdata.cqe_res < 0) 61 | return {-m_userdata.cqe_res, std::system_category()}; 62 | if (m_bytes != nullptr) 63 | *m_bytes = static_cast(m_userdata.cqe_res); 64 | return {}; 65 | } 66 | 67 | private: 68 | /// \brief 69 | /// For internal usage. Actually prepare async read operation and suspend 70 | /// this coroutine. 71 | /// \param[in] worker 72 | /// Worker for current thread. 73 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 74 | 75 | private: 76 | detail::user_data m_userdata; 77 | int m_file; 78 | uint32_t m_size; 79 | void *m_buffer; 80 | uint32_t *m_bytes; 81 | uint64_t m_offset; 82 | }; 83 | 84 | /// \class write_awaitable 85 | /// \brief 86 | /// For internal usage. Awaitable object for async write operation. 87 | class [[nodiscard]] write_awaitable { 88 | public: 89 | /// \brief 90 | /// For internal usage. Create a new write awaitable object to prepare for 91 | /// async write operation. 92 | /// \param file 93 | /// File descriptor of the file to be written to. 94 | /// \param data 95 | /// Pointer to start of the buffer to be written. 96 | /// \param size 97 | /// Expected size in byte of data to be written. 98 | /// \param[out] bytes 99 | /// Optional. Used to store how much bytes are written. 100 | /// \param offset 101 | /// Offset in byte of the file to start the write operation. Pass -1 to 102 | /// use current file pointer. 103 | write_awaitable(int file, const void *data, uint32_t size, uint32_t *bytes, 104 | uint64_t offset) noexcept 105 | : m_userdata{}, m_file{file}, m_size{size}, m_data{data}, 106 | m_bytes{bytes}, m_offset{offset} {} 107 | 108 | /// \brief 109 | /// C++20 awaitable internal method. Always execute await_suspend(). 110 | auto await_ready() noexcept -> bool { 111 | return false; 112 | } 113 | 114 | /// \brief 115 | /// Prepare async write operation and suspend this coroutine. 116 | /// \tparam Promise 117 | /// Type of the promise for the coroutine. 118 | /// \param coro 119 | /// Current coroutine handle. 120 | /// \return 121 | /// A boolean value that specifies whether to suspend current coroutine. 122 | /// Current coroutine will be suspended if no error occurs. 123 | template 124 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 125 | m_userdata.coroutine = coro.address(); 126 | return this->suspend(coro.promise().worker()); 127 | } 128 | 129 | /// \brief 130 | /// Resume current coroutine and get result of the async write operation. 131 | /// \return 132 | /// Error code of the async write operation. The error code is 0 if no 133 | /// error occurs. 134 | auto await_resume() noexcept -> std::error_code { 135 | if (m_userdata.cqe_res < 0) 136 | return {-m_userdata.cqe_res, std::system_category()}; 137 | if (m_bytes != nullptr) 138 | *m_bytes = static_cast(m_userdata.cqe_res); 139 | return {}; 140 | } 141 | 142 | private: 143 | /// \brief 144 | /// For internal usage. Actually prepare async write operation and suspend 145 | /// this coroutine. 146 | /// \param[in] worker 147 | /// Worker for current thread. 148 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 149 | 150 | private: 151 | detail::user_data m_userdata; 152 | int m_file; 153 | uint32_t m_size; 154 | const void *m_data; 155 | uint32_t *m_bytes; 156 | uint64_t m_offset; 157 | }; 158 | 159 | /// \class connect_awaitable 160 | /// \brief 161 | /// For internal usage. Awaitable object for async connect operation. 162 | class [[nodiscard]] connect_awaitable { 163 | public: 164 | /// \brief 165 | /// For internal usage. Try to connect to the specified peer address. 166 | /// \remarks 167 | /// This awaitable will create a new socket and replace the original one 168 | /// if succeeded. The original socket will be closed here if succeeded 169 | /// which I think is a bad design. But it would be convenient to use if 170 | /// creating socket and connect operation are wrapped together. 171 | /// \param[in,out] target 172 | /// The socket to be replaced if succeeded to connect to peer. 173 | /// \param addr 174 | /// Socket address of the peer target. 175 | /// \param length 176 | /// Actual size in byte of the socket address. 177 | /// \param type 178 | /// Type of the new socket. 179 | /// \param protocol 180 | /// Protocol of the new socket. 181 | connect_awaitable(int *target, const sockaddr_storage &addr, 182 | socklen_t length, int type, int protocol) noexcept 183 | : m_userdata{}, m_socket{-1}, m_type{type}, m_protocol{protocol}, 184 | m_socklen{length}, m_sockaddr{addr}, m_target{target} {} 185 | 186 | /// \brief 187 | /// C++20 awaitable internal method. Always execute await_suspend(). 188 | auto await_ready() noexcept -> bool { 189 | return false; 190 | } 191 | 192 | /// \brief 193 | /// Prepare async send operation and suspend this coroutine. 194 | /// \tparam Promise 195 | /// Type of the promise for the coroutine. 196 | /// \param coro 197 | /// Current coroutine handle. 198 | /// \return 199 | /// A boolean value that specifies whether to suspend current coroutine. 200 | /// Current coroutine will be suspended if no error occurs. 201 | template 202 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 203 | m_userdata.coroutine = coro.address(); 204 | return this->suspend(coro.promise().worker()); 205 | } 206 | 207 | /// \brief 208 | /// Resume current coroutine and get result of the async connect 209 | /// operation. 210 | /// \return 211 | /// Error code of the async connect operation. The error code is 0 if no 212 | /// error occurs. 213 | COCO_API auto await_resume() noexcept -> std::error_code; 214 | 215 | private: 216 | /// \brief 217 | /// For internal usage. Actually prepare async connect operation and 218 | /// suspend this coroutine. 219 | /// \param[in] worker 220 | /// Worker for current thread. 221 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 222 | 223 | private: 224 | detail::user_data m_userdata; 225 | int m_socket; 226 | int m_type; 227 | int m_protocol; 228 | socklen_t m_socklen; 229 | sockaddr_storage m_sockaddr; 230 | int *m_target; 231 | }; 232 | 233 | /// \class send_awaitable 234 | /// \brief 235 | /// For internal usage. Awaitable object for async send operation. 236 | class [[nodiscard]] send_awaitable { 237 | public: 238 | /// \brief 239 | /// For internal usage. Create a new send awaitable object to prepare for 240 | /// async send operation. 241 | /// \param sock 242 | /// The socket that this send operation is prepared for. 243 | /// \param data 244 | /// Pointer to start of data to be sent. 245 | /// \param size 246 | /// Expected size in byte of data to be sent. 247 | /// \param[out] bytes 248 | /// Optional. Used to store how much bytes are sent. 249 | send_awaitable(int sock, const void *data, size_t size, 250 | size_t *bytes) noexcept 251 | : m_userdata{}, m_socket{sock}, m_data{data}, m_size{size}, 252 | m_bytes{bytes} {} 253 | 254 | /// \brief 255 | /// C++20 awaitable internal method. Always execute await_suspend(). 256 | auto await_ready() noexcept -> bool { 257 | return false; 258 | } 259 | 260 | /// \brief 261 | /// Prepare async send operation and suspend this coroutine. 262 | /// \tparam Promise 263 | /// Type of the promise for the coroutine. 264 | /// \param coro 265 | /// Current coroutine handle. 266 | /// \return 267 | /// A boolean value that specifies whether to suspend current coroutine. 268 | /// Current coroutine will be suspended if no error occurs. 269 | template 270 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 271 | m_userdata.coroutine = coro.address(); 272 | return this->suspend(coro.promise().worker()); 273 | } 274 | 275 | /// \brief 276 | /// Resume current coroutine and get result of the async send operation. 277 | /// \return 278 | /// Error code of the async send operation. The error code is 0 if no 279 | /// error occurs. 280 | auto await_resume() noexcept -> std::error_code { 281 | if (m_userdata.cqe_res < 0) 282 | return {-m_userdata.cqe_res, std::system_category()}; 283 | if (m_bytes != nullptr) 284 | *m_bytes = static_cast(m_userdata.cqe_res); 285 | return {}; 286 | } 287 | 288 | private: 289 | /// \brief 290 | /// For internal usage. Actually prepare async send operation and suspend 291 | /// this coroutine. 292 | /// \param[in] worker 293 | /// Worker for current thread. 294 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 295 | 296 | private: 297 | detail::user_data m_userdata; 298 | int m_socket; 299 | const void *m_data; 300 | size_t m_size; 301 | size_t *m_bytes; 302 | }; 303 | 304 | /// \class recv_awaitable 305 | /// \brief 306 | /// For internal usage. Awaitable object for async recv operation. 307 | class [[nodiscard]] recv_awaitable { 308 | public: 309 | /// \brief 310 | /// For internal usage. Create a new recv awaitable object to prepare for 311 | /// async recv operation. 312 | /// \param sock 313 | /// The socket that this recv operation is prepared for. 314 | /// \param buffer 315 | /// Pointer to start of the buffer to store the received data. 316 | /// \param size 317 | /// Maximum available size in byte of the receive buffer. 318 | /// \param[out] bytes 319 | /// Optional. Used to store how much bytes are received. 320 | recv_awaitable(int sock, void *buffer, size_t size, size_t *bytes) noexcept 321 | : m_userdata{}, m_socket{sock}, m_buffer{buffer}, m_size{size}, 322 | m_bytes{bytes} {} 323 | 324 | /// \brief 325 | /// C++20 awaitable internal method. Always execute await_suspend(). 326 | auto await_ready() noexcept -> bool { 327 | return false; 328 | } 329 | 330 | /// \brief 331 | /// Prepare async recv operation and suspend this coroutine. 332 | /// \tparam Promise 333 | /// Type of the promise for the coroutine. 334 | /// \param coro 335 | /// Current coroutine handle. 336 | /// \return 337 | /// A boolean value that specifies whether to suspend current coroutine. 338 | /// Current coroutine will be suspended if no error occurs. 339 | template 340 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 341 | m_userdata.coroutine = coro.address(); 342 | return this->suspend(coro.promise().worker()); 343 | } 344 | 345 | /// \brief 346 | /// Resume current coroutine and get result of the async recv operation. 347 | /// \return 348 | /// Error code of the async recv operation. The error code is 0 if no 349 | /// error occurs. 350 | auto await_resume() noexcept -> std::error_code { 351 | if (m_userdata.cqe_res < 0) 352 | return {-m_userdata.cqe_res, std::system_category()}; 353 | if (m_bytes != nullptr) 354 | *m_bytes = static_cast(m_userdata.cqe_res); 355 | return {}; 356 | } 357 | 358 | private: 359 | /// \brief 360 | /// For internal usage. Actually prepare async recv operation and suspend 361 | /// this coroutine. 362 | /// \param[in] worker 363 | /// Worker for current thread. 364 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 365 | 366 | private: 367 | detail::user_data m_userdata; 368 | int m_socket; 369 | void *m_buffer; 370 | size_t m_size; 371 | size_t *m_bytes; 372 | }; 373 | 374 | /// \class timeout_awaitable 375 | /// \brief 376 | /// For internal usage. Awaitable object for async timeout operation. 377 | class [[nodiscard]] timeout_awaitable { 378 | public: 379 | /// \brief 380 | /// For internal usage. Create a new timer awaitable object to prepare for 381 | /// async timeout operation. 382 | /// \param timer 383 | /// Linux timer file descriptor. 384 | /// \param nanosecond 385 | /// Timeout duration in nanoseconds. 386 | timeout_awaitable(int timer, int64_t nanosecond) noexcept 387 | : m_userdata{}, m_timer{timer}, m_nanosecond{nanosecond}, m_buffer{} {} 388 | 389 | /// \brief 390 | /// C++20 awaitable internal method. Always execute await_suspend(). 391 | auto await_ready() noexcept -> bool { 392 | return false; 393 | } 394 | 395 | /// \brief 396 | /// Prepare async timeout and suspend this coroutine. 397 | /// \tparam Promise 398 | /// Type of the promise for the coroutine. 399 | /// \param coro 400 | /// Current coroutine handle. 401 | /// \return 402 | /// A boolean value that specifies whether to suspend current coroutine. 403 | /// Current coroutine will be suspended if no error occurs. 404 | template 405 | auto await_suspend(std::coroutine_handle coro) noexcept -> bool { 406 | m_userdata.coroutine = coro.address(); 407 | return this->suspend(coro.promise().worker()); 408 | } 409 | 410 | /// \brief 411 | /// Resume current coroutine and get result of the async timeout event. 412 | /// \return 413 | /// Error code of the async timeout event. The error code is 0 if no error 414 | /// occurs. 415 | auto await_resume() noexcept -> std::error_code { 416 | if (m_userdata.cqe_res < 0) 417 | return {-m_userdata.cqe_res, std::system_category()}; 418 | return {}; 419 | } 420 | 421 | private: 422 | /// \brief 423 | /// For internal usage. Actually prepare async timeout event and suspend 424 | /// this coroutine. 425 | /// \param[in] worker 426 | /// Worker for current thread. 427 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept -> bool; 428 | 429 | private: 430 | detail::user_data m_userdata; 431 | int m_timer; 432 | int64_t m_nanosecond; 433 | int64_t m_buffer; 434 | }; 435 | 436 | } // namespace coco 437 | 438 | namespace coco { 439 | 440 | /// \class ipv4_address 441 | /// \brief 442 | /// Represents an IPv4 address. This is a trivial type and it is safe to do 443 | /// direct memory operations on ipv4_address objects. 444 | class ipv4_address { 445 | public: 446 | /// \brief 447 | /// Create an empty ipv4 address. Empty ipv4 address should not be used 448 | /// for any purpose. 449 | ipv4_address() noexcept = default; 450 | 451 | /// \brief 452 | /// Create a new ipv4 address from raw bytes. 453 | /// \param first 454 | /// The first byte of the ipv4 address. 455 | /// \param second 456 | /// The second byte of the ipv4 address. 457 | /// \param third 458 | /// The third byte of the ipv4 address. 459 | /// \param fourth 460 | /// The fourth byte of the ipv4 address. 461 | constexpr ipv4_address(uint8_t first, uint8_t second, uint8_t third, 462 | uint8_t fourth) noexcept 463 | : m_address{.u8{first, second, third, fourth}} {} 464 | 465 | /// \brief 466 | /// ipv4_address is trivially copyable. 467 | /// \param other 468 | /// The ipv4_address to copy from. 469 | ipv4_address(const ipv4_address &other) noexcept = default; 470 | 471 | /// \brief 472 | /// ipv4_address is trivially moveable. 473 | /// \param other 474 | /// The ipv4_address to move from. 475 | ipv4_address(ipv4_address &&other) noexcept = default; 476 | 477 | /// \brief 478 | /// ipv4_address is trivially destructible. 479 | ~ipv4_address() = default; 480 | 481 | /// \brief 482 | /// ipv4_address is trivially copyable. 483 | /// \param other 484 | /// The ipv4_address to copy from. 485 | /// \return 486 | /// A reference to the ipv4_address. 487 | auto operator=(const ipv4_address &other) noexcept 488 | -> ipv4_address & = default; 489 | 490 | /// \brief 491 | /// ipv4_address is trivially moveable. 492 | /// \param other 493 | /// The ipv4_address to move from. 494 | /// \return 495 | /// A reference to the ipv4_address. 496 | auto operator=(ipv4_address &&other) noexcept -> ipv4_address & = default; 497 | 498 | /// \brief 499 | /// Set a new ipv4 address from string. 500 | /// \param address 501 | /// A string that represents the ipv4 address to be set. 502 | /// \retval true 503 | /// The ipv4 address is set successfully. 504 | /// \retval false 505 | /// The input string is not a valid ipv4 address. 506 | COCO_API auto set_address(std::string_view address) noexcept -> bool; 507 | 508 | /// \brief 509 | /// Get string representation of this ipv4 address. 510 | /// \return 511 | /// A string that represents the ipv4 address. 512 | [[nodiscard]] COCO_API auto to_string() const noexcept -> std::string; 513 | 514 | /// \brief 515 | /// Create a loopback ipv4 address. 516 | /// \return 517 | /// A loopback ipv4 address. 518 | [[nodiscard]] static constexpr auto loopback() noexcept -> ipv4_address { 519 | return {127, 0, 0, 1}; 520 | } 521 | 522 | /// \brief 523 | /// Create a broadcast ipv4 address. 524 | /// \return 525 | /// A broadcast ipv4 address. 526 | [[nodiscard]] static constexpr auto broadcast() noexcept -> ipv4_address { 527 | return {255, 255, 255, 255}; 528 | } 529 | 530 | /// \brief 531 | /// Create a ipv4 address that listen to all of the network interfaces. 532 | /// \return 533 | /// A ipv4 address that listen to all of the network interfaces. 534 | [[nodiscard]] static constexpr auto any() noexcept -> ipv4_address { 535 | return {0, 0, 0, 0}; 536 | } 537 | 538 | private: 539 | union { 540 | uint8_t u8[4]; 541 | uint32_t u32; 542 | } m_address; 543 | }; 544 | 545 | /// \class ipv6_address 546 | /// \brief 547 | /// Represents an IPv6 address. This is a trivial type and it is safe to do 548 | /// direct memory operations on ipv6_address objects. 549 | class ipv6_address { 550 | public: 551 | /// \brief 552 | /// Create an empty ipv6 address. Empty ipv6 address should not be used 553 | /// for any purpose. 554 | ipv6_address() noexcept = default; 555 | 556 | /// \brief 557 | /// Create a new ipv6 address from raw bytes. Byte order conversion is not 558 | /// performed here. 559 | /// \param v0 560 | /// The first parameter of the ipv6 address. 561 | /// \param v1 562 | /// The second parameter of the ipv6 address. 563 | /// \param v2 564 | /// The third parameter of the ipv6 address. 565 | /// \param v3 566 | /// The fourth parameter of the ipv6 address. 567 | /// \param v4 568 | /// The fifth parameter of the ipv6 address. 569 | /// \param v5 570 | /// The sixth parameter of the ipv6 address. 571 | /// \param v6 572 | /// The seventh parameter of the ipv6 address. 573 | /// \param v7 574 | /// The eighth parameter of the ipv6 address. 575 | COCO_API ipv6_address(uint16_t v0, uint16_t v1, uint16_t v2, uint16_t v3, 576 | uint16_t v4, uint16_t v5, uint16_t v6, 577 | uint16_t v7) noexcept; 578 | 579 | /// \brief 580 | /// ipv6_address is trivially copyable. 581 | /// \param other 582 | /// The ipv6_address to copy from. 583 | ipv6_address(const ipv6_address &other) noexcept = default; 584 | 585 | /// \brief 586 | /// ipv6_address is trivially moveable. 587 | /// \param other 588 | /// The ipv6_address to move from. 589 | ipv6_address(ipv6_address &&other) noexcept = default; 590 | 591 | /// \brief 592 | /// ipv6_address is trivially destructible. 593 | ~ipv6_address() = default; 594 | 595 | /// \brief 596 | /// ipv6_address is trivially copyable. 597 | /// \param other 598 | /// The ipv6_address to copy from. 599 | /// \return 600 | /// A reference to the ipv6_address. 601 | auto operator=(const ipv6_address &other) noexcept 602 | -> ipv6_address & = default; 603 | 604 | /// \brief 605 | /// ipv6_address is trivially moveable. 606 | /// \param other 607 | /// The ipv6_address to move from. 608 | /// \return 609 | /// A reference to the ipv6_address. 610 | auto operator=(ipv6_address &&other) noexcept -> ipv6_address & = default; 611 | 612 | /// \brief 613 | /// Set a new ipv6 address from string. 614 | /// \param address 615 | /// A string that represents the ipv6 address to be set. 616 | /// \retval true 617 | /// The ipv6 address is set successfully. 618 | /// \retval false 619 | /// The input string is not a valid ipv6 address. 620 | COCO_API auto set_address(std::string_view address) noexcept -> bool; 621 | 622 | /// \brief 623 | /// Get string representation of this ipv6 address. 624 | /// \return 625 | /// A string that represents the ipv6 address. 626 | [[nodiscard]] COCO_API auto to_string() const noexcept -> std::string; 627 | 628 | /// \brief 629 | /// Create a loopback ipv6 address. 630 | /// \return 631 | /// A loopback ipv6 address. 632 | [[nodiscard]] static auto loopback() noexcept -> ipv6_address { 633 | return {0, 0, 0, 0, 0, 0, 0, 1}; 634 | } 635 | 636 | /// \brief 637 | /// Create a ipv6 address that listen to all of the network interfaces. 638 | /// \return 639 | /// A ipv6 address that listen to all of the network interfaces. 640 | [[nodiscard]] static auto any() noexcept -> ipv6_address { 641 | return {0, 0, 0, 0, 0, 0, 0, 0}; 642 | } 643 | 644 | private: 645 | union { 646 | char8_t u8[16]; 647 | uint16_t u16[8]; 648 | char32_t u32[4]; 649 | } m_address; 650 | }; 651 | 652 | /// \class ip_address 653 | /// \brief 654 | /// Represents an IP address. This class is a wrapper around ipv4_address 655 | /// and ipv6_address. 656 | class ip_address { 657 | public: 658 | /// \brief 659 | /// Create an empty ip_address. Empty ip_address should not be used for 660 | /// any purpose. 661 | ip_address() noexcept = default; 662 | 663 | /// \brief 664 | /// Create a new ip_address from ipv4_address. 665 | /// \param address 666 | /// The ipv4_address to create the ip_address from. 667 | ip_address(const ipv4_address &address) noexcept 668 | : m_address{.v4{address}}, m_is_ipv6{false} {} 669 | 670 | /// \brief 671 | /// Create a new ip_address from ipv6_address. 672 | /// \param address 673 | /// The ipv6_address to create the ip_address from. 674 | ip_address(const ipv6_address &address) noexcept 675 | : m_address{.v6{address}}, m_is_ipv6{true} {} 676 | 677 | /// \brief 678 | /// ip_address is trivially copyable. 679 | /// \param other 680 | /// The ip_address to copy from. 681 | ip_address(const ip_address &other) noexcept = default; 682 | 683 | /// \brief 684 | /// ip_address is trivially moveable. 685 | /// \param other 686 | /// The ip_address to move from. 687 | ip_address(ip_address &&other) noexcept = default; 688 | 689 | /// \brief 690 | /// ip_address is trivially destructible. 691 | ~ip_address() = default; 692 | 693 | /// \brief 694 | /// ip_address is trivially copyable. 695 | /// \param other 696 | /// The ip_address to copy from. 697 | /// \return 698 | /// A reference to the ip_address. 699 | auto operator=(const ip_address &other) noexcept -> ip_address & = default; 700 | 701 | /// \brief 702 | /// ip_address is trivially moveable. 703 | /// \param other 704 | /// The ip_address to move from. 705 | /// \return 706 | /// A reference to the ip_address. 707 | auto operator=(ip_address &&other) noexcept -> ip_address & = default; 708 | 709 | /// \brief 710 | /// Set an ipv4_address to this ip_address. 711 | /// \param address 712 | /// The ipv4_address to set this ip_address to. 713 | /// \return 714 | /// A reference to this ip_address. 715 | auto operator=(const ipv4_address &address) noexcept -> ip_address & { 716 | m_address.v4 = address; 717 | m_is_ipv6 = false; 718 | return *this; 719 | } 720 | 721 | /// \brief 722 | /// Set an ipv6_address to this ip_address. 723 | /// \param address 724 | /// The ipv6_address to set this ip_address to. 725 | /// \return 726 | /// A reference to this ip_address. 727 | auto operator=(const ipv6_address &address) noexcept -> ip_address & { 728 | m_address.v6 = address; 729 | m_is_ipv6 = true; 730 | return *this; 731 | } 732 | 733 | /// \brief 734 | /// Checks if this is an ipv4 address. 735 | /// \retval true 736 | /// This is an ipv4 address. 737 | /// \retval false 738 | /// This is not an ipv4 address. 739 | [[nodiscard]] auto is_ipv4() const noexcept -> bool { 740 | return !m_is_ipv6; 741 | } 742 | 743 | /// \brief 744 | /// Get this ip_address as an ipv4_address. 745 | /// \note 746 | /// No check is performed to ensure that this is an ipv4 address. It is 747 | /// undefined behavior if this is an ipv6 address. 748 | /// \return 749 | /// The ipv4_address representation of this ip_address. 750 | [[nodiscard]] auto ipv4() noexcept -> ipv4_address & { 751 | assert(!m_is_ipv6); 752 | return m_address.v4; 753 | } 754 | 755 | /// \brief 756 | /// Get this ip_address as an ipv4_address. 757 | /// \note 758 | /// No check is performed to ensure that this is an ipv4 address. It is 759 | /// undefined behavior if this is an ipv6 address. 760 | /// \return 761 | /// The ipv4_address representation of this ip_address. 762 | [[nodiscard]] auto ipv4() const noexcept -> const ipv4_address & { 763 | assert(!m_is_ipv6); 764 | return m_address.v4; 765 | } 766 | 767 | /// \brief 768 | /// Checks if this is an ipv6 address. 769 | /// \retval true 770 | /// This is an ipv6 address. 771 | /// \retval false 772 | /// This is not an ipv6 address. 773 | [[nodiscard]] auto is_ipv6() const noexcept -> bool { 774 | return m_is_ipv6; 775 | } 776 | 777 | /// \brief 778 | /// Get this ip_address as an ipv6_address. 779 | /// \note 780 | /// No check is performed to ensure that this is an ipv6 address. It is 781 | /// undefined behavior if this is an ipv4 address. 782 | /// \return 783 | /// The ipv6_address representation of this ip_address. 784 | [[nodiscard]] auto ipv6() noexcept -> ipv6_address & { 785 | assert(m_is_ipv6); 786 | return m_address.v6; 787 | } 788 | 789 | /// \brief 790 | /// Get this ip_address as an ipv6_address. 791 | /// \note 792 | /// No check is performed to ensure that this is an ipv6 address. It is 793 | /// undefined behavior if this is an ipv4 address. 794 | /// \return 795 | /// The ipv6_address representation of this ip_address. 796 | [[nodiscard]] auto ipv6() const noexcept -> const ipv6_address & { 797 | assert(m_is_ipv6); 798 | return m_address.v6; 799 | } 800 | 801 | /// \brief 802 | /// Set a new ip_address from string. 803 | /// \param address 804 | /// A string that represents the ip_address. This method will 805 | /// automatically detect if the input string is an ipv4 or ipv6 address. 806 | /// \retval true 807 | /// The ip_address is set successfully. 808 | /// \retval false 809 | /// The input string is not a valid ip_address. 810 | COCO_API auto set_address(std::string_view address) noexcept -> bool; 811 | 812 | /// \brief 813 | /// Set this ip_address as an ipv4_address. 814 | /// \param address 815 | /// The ipv4_address to set this ip_address to. 816 | auto set_address(const ipv4_address &address) noexcept -> void { 817 | m_address.v4 = address; 818 | m_is_ipv6 = false; 819 | } 820 | 821 | /// \brief 822 | /// Set this ip_address as an ipv6_address. 823 | /// \param address 824 | /// The ipv6_address to set this ip_address to. 825 | auto set_address(const ipv6_address &address) noexcept -> void { 826 | m_address.v6 = address; 827 | m_is_ipv6 = true; 828 | } 829 | 830 | /// \brief 831 | /// Get string representation of this ip_address. 832 | /// \return 833 | /// A string that represents the ip_address. 834 | [[nodiscard]] COCO_API auto to_string() const noexcept -> std::string; 835 | 836 | /// \brief 837 | /// Create an ipv4 loopback address. 838 | /// \return 839 | /// A loopback ip_address. 840 | [[nodiscard]] static auto ipv4_loopback() noexcept -> ip_address { 841 | return {ipv4_address::loopback()}; 842 | } 843 | 844 | /// \brief 845 | /// Create an ipv4 broadcast address. 846 | /// \return 847 | /// A broadcast ip_address. 848 | [[nodiscard]] static auto ipv4_broadcast() noexcept -> ip_address { 849 | return {ipv4_address::broadcast()}; 850 | } 851 | 852 | /// \brief 853 | /// Create an ipv4 address that listen to all of the network interfaces. 854 | /// \return 855 | /// A ipv4 address that listen to all of the network interfaces. 856 | [[nodiscard]] static auto ipv4_any() noexcept -> ip_address { 857 | return {ipv4_address::any()}; 858 | } 859 | 860 | /// \brief 861 | /// Create an ipv6 loopback address. 862 | /// \return 863 | /// A loopback ip_address. 864 | [[nodiscard]] static auto ipv6_loopback() noexcept -> ip_address { 865 | return {ipv6_address::loopback()}; 866 | } 867 | 868 | /// \brief 869 | /// Create an ipv6 address that listen to all of the network interfaces. 870 | /// \return 871 | /// A ipv6 address that listen to all of the network interfaces. 872 | [[nodiscard]] static auto ipv6_any() noexcept -> ip_address { 873 | return {ipv6_address::any()}; 874 | } 875 | 876 | private: 877 | union { 878 | ipv4_address v4; 879 | ipv6_address v6; 880 | } m_address; 881 | bool m_is_ipv6; 882 | }; 883 | 884 | /// \class tcp_connection 885 | /// \brief 886 | /// Represents a TCP connection. This class is a wrapper around platform 887 | /// specific socket that supports async operations. 888 | class tcp_connection { 889 | public: 890 | /// \brief 891 | /// Create an empty tcp connection. Empty tcp connection should not be 892 | /// used for any purpose. 893 | tcp_connection() noexcept : m_socket{-1} {} 894 | 895 | /// \brief 896 | /// For internal usage. Create a new TCP connection from socket handle. 897 | /// \param handle 898 | /// Socket handle of this TCP connection. 899 | explicit tcp_connection(int handle) noexcept : m_socket{handle} {} 900 | 901 | /// \brief 902 | /// TCP connection is not allowed to be copied. 903 | tcp_connection(const tcp_connection &other) = delete; 904 | 905 | /// \brief 906 | /// Move constructor of tcp_connection. 907 | /// \param other 908 | /// The TCP connection to move from. The moved TCP connection will be set 909 | /// to empty. 910 | tcp_connection(tcp_connection &&other) noexcept : m_socket{other.m_socket} { 911 | other.m_socket = -1; 912 | } 913 | 914 | /// \brief 915 | /// Disconnect and destroy this TCP connection. 916 | COCO_API ~tcp_connection(); 917 | 918 | /// \brief 919 | /// TCP connection is not allowed to be copied. 920 | auto operator=(const tcp_connection &other) = delete; 921 | 922 | /// \brief 923 | /// Move assignment of tcp_connection. 924 | /// \param other 925 | /// The TCP connection to move from. The moved TCP connection will be set 926 | /// to empty. 927 | COCO_API auto operator=(tcp_connection &&other) noexcept 928 | -> tcp_connection &; 929 | 930 | /// \brief 931 | /// Checks if this is an empty TCP connection. 932 | /// \retval true 933 | /// This is an empty TCP connection. 934 | /// \retval false 935 | /// This is not an empty TCP connection. 936 | [[nodiscard]] auto empty() const noexcept -> bool { 937 | return m_socket == -1; 938 | } 939 | 940 | /// \brief 941 | /// Async connect to an endpoint. This method will release current 942 | /// connection and reconnect to the new peer if current connection is not 943 | /// empty. 944 | /// \param address 945 | /// IP address of the target endpoint. 946 | /// \param port 947 | /// Port of the target endpoint. 948 | /// \return 949 | /// A task that connects to the target endpoint. Result of the task is a 950 | /// system error code that represents the connect result. The error code 951 | /// is 0 if succeeded. 952 | COCO_API auto connect(ip_address address, uint16_t port) noexcept 953 | -> connect_awaitable; 954 | 955 | /// \brief 956 | /// Set timeout for asynchronous receive operations. See `received()` for 957 | /// details. 958 | /// \tparam Rep 959 | /// Type representation of the time type. See std::chrono::duration for 960 | /// details. 961 | /// \tparam Period 962 | /// Ratio type that is used to measure how to do conversion between 963 | /// different duration types. See std::chrono::duration for details. 964 | /// \param duration 965 | /// Timeout duration of receive operation. Nanosecond and below ratios are 966 | /// not supported. Pass 0 to remove timeout events. 967 | /// \return 968 | /// An error code that represents the result. Return 0 if succeeded to set 969 | /// timeout for receive operation. 970 | template 971 | auto set_timeout(std::chrono::duration duration) noexcept 972 | -> std::error_code { 973 | static_assert(std::ratio_greater_equal::value); 974 | return this->set_timeout( 975 | std::chrono::duration_cast(duration)); 976 | } 977 | 978 | /// \brief 979 | /// Async send data to peer. 980 | /// \param data 981 | /// Pointer to start of data to be sent. 982 | /// \param size 983 | /// Size in byte of data to be sent. 984 | /// \return 985 | /// A task that sends data to the target endpoint. Result of the task is a 986 | /// system error code that represents the send result. The error code is 0 987 | /// if succeeded. 988 | auto send(const void *data, size_t size) const noexcept -> send_awaitable { 989 | return {m_socket, data, size, nullptr}; 990 | } 991 | 992 | /// \brief 993 | /// Async send data to peer. 994 | /// \param data 995 | /// Pointer to start of data to be sent. 996 | /// \param size 997 | /// Size in byte of data to be sent. 998 | /// \param[out] sent 999 | /// Size in byte of data actually sent. 1000 | /// \return 1001 | /// A task that sends data to the target endpoint. Result of the task is a 1002 | /// system error code that represents the send result. The error code is 0 1003 | /// if succeeded. 1004 | auto send(const void *data, size_t size, size_t &sent) const noexcept 1005 | -> send_awaitable { 1006 | return {m_socket, data, size, &sent}; 1007 | } 1008 | 1009 | /// \brief 1010 | /// Async receive data from peer. 1011 | /// \param[out] buffer 1012 | /// Pointer to start of the buffer to receive data. 1013 | /// \param size 1014 | /// Maximum available size to be received. 1015 | /// \return 1016 | /// A task that receives data to the target endpoint. Result of the task 1017 | /// is a system error code that represents the receive result. The error 1018 | /// code is 0 if succeeded. The error code is `EAGAIN` if timeout occurs. 1019 | auto receive(void *buffer, size_t size) const noexcept -> recv_awaitable { 1020 | return {m_socket, buffer, size, nullptr}; 1021 | } 1022 | 1023 | /// \brief 1024 | /// Async receive data from peer. 1025 | /// \param[out] buffer 1026 | /// Pointer to start of the buffer to receive data. 1027 | /// \param size 1028 | /// Maximum available size to be received. 1029 | /// \param[out] received 1030 | /// Size in byte of data actually received. 1031 | /// \return 1032 | /// A task that receives data to the target endpoint. Result of the task 1033 | /// is a system error code that represents the receive result. The error 1034 | /// code is 0 if succeeded. The error code is `EAGAIN` if timeout occurs. 1035 | auto receive(void *buffer, size_t size, size_t &received) const noexcept 1036 | -> recv_awaitable { 1037 | return {m_socket, buffer, size, &received}; 1038 | } 1039 | 1040 | /// \brief 1041 | /// Shutdown write pipe of this TCP connection. 1042 | /// \return 1043 | /// System error code of this operation. The error code is 0 if succeeded. 1044 | COCO_API auto shutdown() noexcept -> std::error_code; 1045 | 1046 | /// \brief 1047 | /// Close this TCP connection. 1048 | COCO_API auto close() noexcept -> void; 1049 | 1050 | /// \brief 1051 | /// Enable or disable keepalive for this TCP connection. 1052 | /// \param enable 1053 | /// True to enable keepalive. False to disable it. 1054 | /// \return 1055 | /// System error code of this operation. The error code is 0 if succeeded. 1056 | COCO_API auto keepalive(bool enable) noexcept -> std::error_code; 1057 | 1058 | /// \brief 1059 | /// Get peer IP address. 1060 | /// \return 1061 | /// The IP address of the peer. 1062 | [[nodiscard]] COCO_API auto address() const noexcept -> ip_address; 1063 | 1064 | /// \brief 1065 | /// Get peer port number. 1066 | /// \return 1067 | /// The port number of the peer. 1068 | [[nodiscard]] COCO_API auto port() const noexcept -> uint16_t; 1069 | 1070 | private: 1071 | /// \brief 1072 | /// For internal usage. Set timeout for asynchronous receive operations. 1073 | /// The `received` parameter will be set to 0 if timeout occurs. 1074 | /// \param microseconds 1075 | /// Microseconds of the receive timeout duration. Pass 0 to remove the 1076 | /// timeout event. 1077 | /// \return 1078 | /// An error code that represents the result. Return 0 if succeeded to set 1079 | /// timeout for receive operation. 1080 | COCO_API auto set_timeout(int64_t microseconds) noexcept -> std::error_code; 1081 | 1082 | private: 1083 | int m_socket; 1084 | }; 1085 | 1086 | /// \class tcp_server 1087 | /// \brief 1088 | /// TCP server class. TCP server listens to a specific port and accepts 1089 | /// incoming connections. 1090 | class tcp_server { 1091 | public: 1092 | /// \class accept_awaitable 1093 | /// \brief 1094 | /// For internal usage. Accept awaitable for TCP connections. 1095 | class accept_awaitable { 1096 | public: 1097 | /// \brief 1098 | /// For internal usage. Create a new accept awaitable object to 1099 | /// prepare for async accept operation. 1100 | /// \param socket 1101 | /// TCP server socket handle. 1102 | /// \param[out] connection 1103 | accept_awaitable(int socket, tcp_connection *connection) noexcept 1104 | : m_userdata{}, m_socket{socket}, m_socklen{sizeof(m_sockaddr)}, 1105 | m_sockaddr{}, m_connection{connection} {} 1106 | 1107 | /// \brief 1108 | /// C++20 awaitable internal method. Always execute await_suspend(). 1109 | auto await_ready() noexcept -> bool { 1110 | return false; 1111 | } 1112 | 1113 | /// \brief 1114 | /// Prepare async read operation and suspend this coroutine. 1115 | /// \tparam Promise 1116 | /// Type of the promise for the coroutine. 1117 | /// \param coro 1118 | /// Current coroutine handle. 1119 | /// \return 1120 | /// A boolean value that specifies whether to suspend current 1121 | /// coroutine. Current coroutine will be suspended if no error occurs. 1122 | template 1123 | auto await_suspend(std::coroutine_handle coro) noexcept 1124 | -> bool { 1125 | m_userdata.coroutine = coro.address(); 1126 | return this->suspend(coro.promise().worker()); 1127 | } 1128 | 1129 | /// \brief 1130 | /// Resume current coroutine and get result of the async accept 1131 | /// operation. 1132 | /// \return 1133 | /// Error code of the async accept operation. The error code is 0 if 1134 | /// no error occurs. 1135 | auto await_resume() noexcept -> std::error_code { 1136 | if (m_userdata.cqe_res < 0) 1137 | return {-m_userdata.cqe_res, std::system_category()}; 1138 | assert(m_connection != nullptr); 1139 | *m_connection = tcp_connection{m_userdata.cqe_res}; 1140 | return {}; 1141 | } 1142 | 1143 | private: 1144 | /// \brief 1145 | /// For internal usage. Actually prepare async accept operation and 1146 | /// suspend this coroutine. 1147 | /// \param[in] worker 1148 | /// Worker for current thread. 1149 | COCO_API auto suspend(detail::io_context_worker *worker) noexcept 1150 | -> bool; 1151 | 1152 | private: 1153 | detail::user_data m_userdata; 1154 | int m_socket; 1155 | socklen_t m_socklen; 1156 | sockaddr_storage m_sockaddr; 1157 | tcp_connection *m_connection; 1158 | }; 1159 | 1160 | public: 1161 | /// \brief 1162 | /// Create an empty TCP server. 1163 | tcp_server() noexcept : m_address{}, m_port{}, m_socket{-1} {} 1164 | 1165 | /// \brief 1166 | /// TCP server is not allowed to be copied. 1167 | tcp_server(const tcp_server &other) = delete; 1168 | 1169 | /// \brief 1170 | /// Move constructor of TCP server. 1171 | /// \param other 1172 | /// The TCP server to be moved from. The moved TCP server will be set to 1173 | /// empty. 1174 | tcp_server(tcp_server &&other) noexcept 1175 | : m_address{other.m_address}, m_port{other.m_port}, 1176 | m_socket{other.m_socket} { 1177 | other.m_socket = -1; 1178 | } 1179 | 1180 | /// \brief 1181 | /// Destroy this TCP server. 1182 | COCO_API ~tcp_server(); 1183 | 1184 | /// \brief 1185 | /// TCP server is not allowed to be copied. 1186 | auto operator=(const tcp_server &other) = delete; 1187 | 1188 | /// \brief 1189 | /// Move assignment of TCP server. 1190 | /// \param other 1191 | /// The TCP server to be moved from. The moved TCP server will be set to 1192 | /// empty. 1193 | /// \return 1194 | /// Reference to this TCP server. 1195 | COCO_API auto operator=(tcp_server &&other) noexcept -> tcp_server &; 1196 | 1197 | /// \brief 1198 | /// Bind this TCP server to the specified address and start listening to 1199 | /// connections. 1200 | /// \param address 1201 | /// The IP address that this TCP server to bind. 1202 | /// \param port 1203 | /// The local port that this TCP server to bind. 1204 | /// \return 1205 | /// An error code that represents the listen result. The error code is 0 1206 | /// if succeeded to listen to the specified address and port. 1207 | COCO_API auto listen(const ip_address &address, uint16_t port) noexcept 1208 | -> std::error_code; 1209 | 1210 | /// \brief 1211 | /// Try to accept a new TCP connection asynchronously. 1212 | /// \param[out] connection 1213 | /// The TCP connection object that is used to store the new connection. 1214 | /// \return 1215 | /// A task that receives connection from the target endpoint. Result of 1216 | /// the task is a system error code that represents the accept result. The 1217 | /// error code is 0 if succeeded. 1218 | auto accept(tcp_connection &connection) noexcept -> accept_awaitable { 1219 | return {m_socket, &connection}; 1220 | } 1221 | 1222 | /// \brief 1223 | /// Close this TCP server. 1224 | COCO_API auto close() noexcept -> void; 1225 | 1226 | /// \brief 1227 | /// Get local IP address. 1228 | /// \return 1229 | /// The listening IP address. The return value is undefined if `listen()` 1230 | /// is not called. 1231 | [[nodiscard]] auto address() const noexcept -> const ip_address & { 1232 | return m_address; 1233 | } 1234 | 1235 | /// \brief 1236 | /// Get local port number. 1237 | /// \return 1238 | /// The listening port. The return value is undefined if `listen()` is not 1239 | /// called. 1240 | [[nodiscard]] auto port() const noexcept -> uint16_t { 1241 | return m_port; 1242 | } 1243 | 1244 | private: 1245 | ip_address m_address; 1246 | uint16_t m_port; 1247 | int m_socket; 1248 | }; 1249 | 1250 | /// \class binary_file 1251 | /// \brief 1252 | /// Wrapper class for binary file. This class provides async IO utilities for 1253 | /// binary file. 1254 | class binary_file { 1255 | public: 1256 | /// \brief 1257 | /// Open file flags. File flags supports binary operations. 1258 | enum class flag : uint32_t { 1259 | // Open for reading. 1260 | read = 0x0001, 1261 | // Open for writing. A new file will be created if not exist. 1262 | write = 0x0002, 1263 | // The file is opened in append mode. Before each write(2), the file 1264 | // offset is positioned at the end of the file. 1265 | append = 0x0004, 1266 | // The file will be truncated to length 0 once opened. Must be used with 1267 | // flag::write. 1268 | trunc = 0x0008, 1269 | }; 1270 | 1271 | /// \brief 1272 | /// Specifies the file seeking direction type. 1273 | enum class seek_whence : uint16_t { 1274 | begin = 0, 1275 | current = 1, 1276 | end = 2, 1277 | }; 1278 | 1279 | /// \brief 1280 | /// Create an empty binary file object. Empty binary file object cannot be 1281 | /// used for IO. 1282 | binary_file() noexcept : m_file{-1} {} 1283 | 1284 | /// \brief 1285 | /// Binary file is not allowed to be copied. 1286 | binary_file(const binary_file &other) = delete; 1287 | 1288 | /// \brief 1289 | /// Move constructor of binary file. 1290 | /// \param other 1291 | /// The binary file object to be moved. The moved binary file will be set 1292 | /// empty. 1293 | binary_file(binary_file &&other) noexcept : m_file{other.m_file} { 1294 | other.m_file = -1; 1295 | } 1296 | 1297 | /// \brief 1298 | /// Close this binary file and destroy this object. 1299 | COCO_API ~binary_file(); 1300 | 1301 | /// \brief 1302 | /// Binary file is not allowed to be copied. 1303 | auto operator=(const binary_file &other) = delete; 1304 | 1305 | /// \brief 1306 | /// Move assignment of binary file. 1307 | /// \param other 1308 | /// The binary file object to be moved. The moved binary file will be set 1309 | /// empty. 1310 | /// \return 1311 | /// Reference to this binary file object. 1312 | COCO_API auto operator=(binary_file &&other) noexcept -> binary_file &; 1313 | 1314 | /// \brief 1315 | /// Checks if this file is opened. 1316 | /// \retval true 1317 | /// This file is opened. 1318 | /// \retval false 1319 | /// This file is not opened. 1320 | [[nodiscard]] auto is_open() const noexcept -> bool { 1321 | return m_file != -1; 1322 | } 1323 | 1324 | /// \brief 1325 | /// Get size in byte of this file. 1326 | /// \warning 1327 | /// Errors are not handled. This method may be changed in the future. 1328 | /// \return 1329 | /// Size in byte of this file. 1330 | [[nodiscard]] COCO_API auto size() noexcept -> size_t; 1331 | 1332 | /// \brief 1333 | /// Try to open or reopen a file. 1334 | /// \param path 1335 | /// Path of the file to be opened. 1336 | /// \param flags 1337 | /// Open file flags. See binary_file::flag for details. 1338 | /// \return 1339 | /// A system error code that represents the open result. Return 0 if 1340 | /// succeeded. This method does not modify this object if failed. 1341 | COCO_API auto open(std::string_view path, flag flags) noexcept 1342 | -> std::error_code; 1343 | 1344 | /// \brief 1345 | /// Async read data from this file. 1346 | /// \param[out] buffer 1347 | /// Pointer to start of the buffer to read data. 1348 | /// \param size 1349 | /// Expected size of data to be read. 1350 | /// \return 1351 | /// A task that reads data from this file. Result of the task is a system 1352 | /// error code that represents the receive result. The error code is 0 if 1353 | /// succeeded. 1354 | auto read(void *buffer, uint32_t size) noexcept -> read_awaitable { 1355 | return {m_file, buffer, size, nullptr, uint64_t(-1)}; 1356 | } 1357 | 1358 | /// \brief 1359 | /// Async read data from this file. 1360 | /// \param offset 1361 | /// Offset of the file to read from. This offset does not affect the 1362 | /// internal seek pointer. 1363 | /// \param[out] buffer 1364 | /// Pointer to start of the buffer to read data. 1365 | /// \param size 1366 | /// Expected size of data to be read. 1367 | /// \return 1368 | /// A task that reads data from this file. Result of the task is a system 1369 | /// error code that represents the receive result. The error code is 0 if 1370 | /// succeeded. 1371 | auto read(size_t offset, void *buffer, uint32_t size) noexcept 1372 | -> read_awaitable { 1373 | return {m_file, buffer, size, nullptr, offset}; 1374 | } 1375 | 1376 | /// \brief 1377 | /// Async read data from this file. 1378 | /// \param[out] buffer 1379 | /// Pointer to start of the buffer to read data. 1380 | /// \param size 1381 | /// Expected size of data to be read. 1382 | /// \param[out] bytes 1383 | /// Size in byte of data actually read. 1384 | /// \return 1385 | /// A task that reads data from this file. Result of the task is a system 1386 | /// error code that represents the receive result. The error code is 0 if 1387 | /// succeeded. 1388 | auto read(void *buffer, uint32_t size, uint32_t &bytes) noexcept 1389 | -> read_awaitable { 1390 | return {m_file, buffer, size, &bytes, uint64_t(-1)}; 1391 | } 1392 | 1393 | /// \brief 1394 | /// Async read data from this file. 1395 | /// \param offset 1396 | /// Offset of the file to read from. This offset does not affect the 1397 | /// internal seek pointer. 1398 | /// \param[out] buffer 1399 | /// Pointer to start of the buffer to read data. 1400 | /// \param size 1401 | /// Expected size of data to be read. 1402 | /// \param[out] bytes 1403 | /// Size in byte of data actually read. 1404 | /// \return 1405 | /// A task that reads data from this file. Result of the task is a system 1406 | /// error code that represents the receive result. The error code is 0 if 1407 | /// succeeded. 1408 | auto read(size_t offset, void *buffer, uint32_t size, 1409 | uint32_t &bytes) noexcept -> read_awaitable { 1410 | return {m_file, buffer, size, &bytes, offset}; 1411 | } 1412 | 1413 | /// \brief 1414 | /// Async write data to this file. 1415 | /// \param data 1416 | /// Pointer to start of data to be written. 1417 | /// \param size 1418 | /// Expected size in byte of data to be written. 1419 | /// \return 1420 | /// A task that writes data from this file. Result of the task is a system 1421 | /// error code that represents the receive result. The error code is 0 if 1422 | /// succeeded. 1423 | auto write(const void *data, uint32_t size) noexcept -> write_awaitable { 1424 | return {m_file, data, size, nullptr, uint64_t(-1)}; 1425 | } 1426 | 1427 | /// \brief 1428 | /// Async write data to this file. 1429 | /// \param offset 1430 | /// Offset of file to start writing data. This offset does not affect the 1431 | /// internal seek pointer. 1432 | /// \param data 1433 | /// Pointer to start of data to be written. 1434 | /// \param size 1435 | /// Expected size in byte of data to be written. 1436 | /// \return 1437 | /// A task that writes data from this file. Result of the task is a system 1438 | /// error code that represents the receive result. The error code is 0 if 1439 | /// succeeded. 1440 | auto write(size_t offset, const void *data, uint32_t size) noexcept 1441 | -> write_awaitable { 1442 | return {m_file, data, size, nullptr, offset}; 1443 | } 1444 | 1445 | /// \brief 1446 | /// Async write data to this file. 1447 | /// \param data 1448 | /// Pointer to start of data to be written. 1449 | /// \param size 1450 | /// Expected size in byte of data to be written. 1451 | /// \param[out] bytes 1452 | /// Size in byte of data actually written. 1453 | /// \return 1454 | /// A task that writes data from this file. Result of the task is a system 1455 | /// error code that represents the receive result. The error code is 0 if 1456 | /// succeeded. 1457 | auto write(const void *data, uint32_t size, uint32_t &bytes) noexcept 1458 | -> write_awaitable { 1459 | return {m_file, data, size, &bytes, uint64_t(-1)}; 1460 | } 1461 | 1462 | /// \brief 1463 | /// Async write data to this file. 1464 | /// \param offset 1465 | /// Offset of file to start writing data. This offset does not affect the 1466 | /// internal seek pointer. 1467 | /// \param data 1468 | /// Pointer to start of data to be written. 1469 | /// \param size 1470 | /// Expected size in byte of data to be written. 1471 | /// \param[out] bytes 1472 | /// Size in byte of data actually written. 1473 | /// \return 1474 | /// A task that writes data from this file. Result of the task is a system 1475 | /// error code that represents the receive result. The error code is 0 if 1476 | /// succeeded. 1477 | auto write(size_t offset, const void *data, uint32_t size, 1478 | uint32_t &bytes) noexcept -> write_awaitable { 1479 | return {m_file, data, size, &bytes, offset}; 1480 | } 1481 | 1482 | /// \brief 1483 | /// Seek internal pointer of this file. 1484 | /// \param whence 1485 | /// Specifies the file seeking direction type. 1486 | /// \param offset 1487 | /// Offset of the internal IO pointer. 1488 | /// \return 1489 | /// An error code that represents the seek result. Return 0 if succeeded. 1490 | COCO_API auto seek(seek_whence whence, ptrdiff_t offset) noexcept 1491 | -> std::error_code; 1492 | 1493 | /// \brief 1494 | /// Flush data and metadata of this file. 1495 | auto flush() noexcept -> void { 1496 | if (m_file != -1) 1497 | ::fsync(m_file); 1498 | } 1499 | 1500 | /// \brief 1501 | /// Close this binary file. 1502 | auto close() noexcept -> void { 1503 | if (m_file != -1) { 1504 | ::close(m_file); 1505 | m_file = -1; 1506 | } 1507 | } 1508 | 1509 | private: 1510 | int m_file; 1511 | }; 1512 | 1513 | constexpr auto operator~(binary_file::flag flag) noexcept -> binary_file::flag { 1514 | return static_cast(~static_cast(flag)); 1515 | } 1516 | 1517 | constexpr auto operator|(binary_file::flag lhs, binary_file::flag rhs) noexcept 1518 | -> binary_file::flag { 1519 | return static_cast(static_cast(lhs) | 1520 | static_cast(rhs)); 1521 | } 1522 | 1523 | constexpr auto operator&(binary_file::flag lhs, binary_file::flag rhs) noexcept 1524 | -> binary_file::flag { 1525 | return static_cast(static_cast(lhs) & 1526 | static_cast(rhs)); 1527 | } 1528 | 1529 | constexpr auto operator^(binary_file::flag lhs, binary_file::flag rhs) noexcept 1530 | -> binary_file::flag { 1531 | return static_cast(static_cast(lhs) ^ 1532 | static_cast(rhs)); 1533 | } 1534 | 1535 | constexpr auto operator|=(binary_file::flag &lhs, 1536 | binary_file::flag rhs) noexcept 1537 | -> binary_file::flag & { 1538 | lhs = (lhs | rhs); 1539 | return lhs; 1540 | } 1541 | 1542 | constexpr auto operator&=(binary_file::flag &lhs, 1543 | binary_file::flag rhs) noexcept 1544 | -> binary_file::flag & { 1545 | lhs = (lhs & rhs); 1546 | return lhs; 1547 | } 1548 | 1549 | constexpr auto operator^=(binary_file::flag &lhs, 1550 | binary_file::flag rhs) noexcept 1551 | -> binary_file::flag & { 1552 | lhs = (lhs ^ rhs); 1553 | return lhs; 1554 | } 1555 | 1556 | /// \class timer 1557 | /// \brief 1558 | /// Timer that is used to suspend tasks for a while. 1559 | class timer { 1560 | public: 1561 | /// \brief 1562 | /// Create a new timer object. 1563 | COCO_API timer() noexcept; 1564 | 1565 | /// \brief 1566 | /// Timer is not allowed to be copied. 1567 | timer(const timer &other) = delete; 1568 | 1569 | /// \brief 1570 | /// Move constructor of timer. 1571 | /// \param other 1572 | /// The timer to be moved. The moved timer will be invalidated. 1573 | timer(timer &&other) noexcept : m_timer{other.m_timer} { 1574 | other.m_timer = -1; 1575 | } 1576 | 1577 | /// \brief 1578 | /// Destroy this timer. 1579 | COCO_API ~timer(); 1580 | 1581 | /// \brief 1582 | /// Timer is not allowed to be copied. 1583 | auto operator=(const timer &other) = delete; 1584 | 1585 | /// \brief 1586 | /// Move assignment of timer. 1587 | /// \param other 1588 | /// The timer to be moved. The moved timer will be invalidated. 1589 | /// \return 1590 | /// Reference to this timer. 1591 | COCO_API auto operator=(timer &&other) noexcept -> timer &; 1592 | 1593 | /// \brief 1594 | /// Suspend current coroutine for a while. 1595 | /// \note 1596 | /// There could be at most one wait task for the same timer at the same 1597 | /// time. 1598 | /// \tparam Rep 1599 | /// Type representation of the time type. See std::chrono::duration for 1600 | /// details. 1601 | /// \tparam Period 1602 | /// Ratio type that is used to measure how to do conversion between 1603 | /// different duration types. See std::chrono::duration for details. 1604 | /// \param duration 1605 | /// Time to suspend current coroutine. 1606 | /// \return 1607 | /// A task that suspends current coroutine for a while. Result of the task 1608 | /// is a system error code that represents the wait result. Usually it is 1609 | /// not necessary to check the error code. 1610 | template 1611 | auto wait(std::chrono::duration duration) noexcept 1612 | -> timeout_awaitable { 1613 | static_assert(std::ratio_less_equal_v, 1614 | "The maximum available precision is nanosecond."); 1615 | std::chrono::nanoseconds nanoseconds = 1616 | std::chrono::duration_cast(duration); 1617 | return {m_timer, nanoseconds.count()}; 1618 | } 1619 | 1620 | private: 1621 | int m_timer; 1622 | }; 1623 | 1624 | } // namespace coco 1625 | --------------------------------------------------------------------------------