├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Doxyfile ├── Doxyfile-mcss ├── LICENSE ├── README.md ├── TODO ├── conf.py ├── examples ├── CMakeLists.txt ├── async_echo_server.cpp ├── coro_cat.cpp ├── coro_echo_server.cpp ├── sync_cat.cpp └── sync_echo_server.cpp ├── include ├── ark.hpp └── ark │ ├── async.hpp │ ├── async │ ├── async_op.hpp │ ├── callback.hpp │ ├── context.hpp │ └── io_uring │ │ ├── async_syscall.hpp │ │ ├── context.hpp │ │ └── io_uring.hpp │ ├── bindings.hpp │ ├── bindings │ ├── bindings.hpp │ ├── clinux.hpp │ ├── coroutine.hpp │ ├── doxygen_misc.hpp │ └── ign_pipe.hpp │ ├── buffer.hpp │ ├── buffer │ ├── buffer.hpp │ ├── concepts.hpp │ └── sequence.hpp │ ├── coroutine.hpp │ ├── coroutine │ ├── awaitable_op.hpp │ ├── co_async.hpp │ ├── fire_and_forget.hpp │ └── task.hpp │ ├── general.hpp │ ├── general │ ├── event_fd.hpp │ ├── mem_fd.hpp │ ├── normal_file.hpp │ └── pipe_fd.hpp │ ├── io.hpp │ ├── io │ ├── async.hpp │ ├── completion_condition.hpp │ ├── concepts.hpp │ ├── coro.hpp │ ├── fd.hpp │ ├── iovecs.hpp │ └── sync.hpp │ ├── misc │ ├── concepts_polyfill.hpp │ ├── context_exit_guard.hpp │ ├── manual_lifetime.hpp │ └── test_r.hpp │ ├── net.hpp │ └── net │ ├── address.hpp │ ├── tcp.hpp │ └── tcp │ ├── acceptor.hpp │ ├── async.hpp │ ├── coro.hpp │ ├── general.hpp │ ├── socket.hpp │ └── sync.hpp ├── scripts └── generate_doc.sh └── tests ├── CMakeLists.txt ├── include └── test_r.hpp ├── test_general.cpp └── test_net_address.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 80 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: false 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 2 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 1 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /compile_commands.json 3 | /.clangd 4 | __pycache__ 5 | /docs 6 | /.cache 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/googletest"] 2 | path = vendor/googletest 3 | url = https://github.com/google/googletest 4 | [submodule "vendor/GSL"] 5 | path = vendor/GSL 6 | url = https://github.com/microsoft/GSL 7 | [submodule "vendor/function2"] 8 | path = vendor/function2 9 | url = https://github.com/Naios/function2 10 | [submodule "vendor/outcome"] 11 | path = vendor/outcome 12 | url = https://github.com/ned14/outcome 13 | [submodule "vendor/m.css"] 14 | path = vendor/m.css 15 | url = https://github.com/mosra/m.css 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(arkio) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") 8 | if(${WITH_COROUTINES}) 9 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts -stdlib=libc++") 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi") 12 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 13 | message(WARNING "gcc support for coroutines is untested, and libstdc++ lacks coroutine support") 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") 15 | endif() 16 | else() 17 | add_definitions(-DARK_NO_COROUTINES) 18 | endif() 19 | endif() 20 | 21 | find_package(PkgConfig REQUIRED) 22 | pkg_check_modules(URING REQUIRED liburing) 23 | find_package (Threads) 24 | add_subdirectory(vendor/GSL) 25 | add_subdirectory(vendor/function2) 26 | # ned14/outcome uses quickcpplib which do lots of extra work 27 | # here we use a little magic to include the single-header version only .. 28 | add_library(outcome INTERFACE IMPORTED GLOBAL) 29 | target_include_directories(outcome INTERFACE ${PROJECT_SOURCE_DIR}/vendor/outcome/single-header) 30 | 31 | add_library(arkio INTERFACE IMPORTED GLOBAL) 32 | target_include_directories(arkio INTERFACE ${PROJECT_SOURCE_DIR}/include ${URING_INCLUDE_DIRS}) 33 | target_link_libraries(arkio INTERFACE ${URING_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} GSL function2 outcome) 34 | 35 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 36 | add_subdirectory(examples) 37 | 38 | enable_testing() 39 | add_subdirectory(tests) 40 | endif() 41 | 42 | -------------------------------------------------------------------------------- /Doxyfile-mcss: -------------------------------------------------------------------------------- 1 | @INCLUDE = Doxyfile 2 | GENERATE_HTML = NO 3 | GENERATE_XML = YES 4 | XML_PROGRAMLISTING = NO 5 | HIDE_SCOPE_NAMES = NO 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 omegacoleman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | arkio 2 | ===== 3 | 4 | *async io-uring based kernel io library* 5 | 6 | **Documents: ** 7 | 8 | This library is a modern C++ wrapper for the io interface of linux kernel. It provides async interface for kernel io, along with the sync ones. What's more, it supports C++20 Coroutines TS, too. 9 | 10 | The async model is based on kernel's new io-uring interface, and implements the _proactor_ design pattern. 11 | 12 | The _proactor_ design pattern demultiplexes and dispatches events asynchronously, and was made famous by Boost.ASIO in the c++ world. If you are familar with asio, then you will find this library very similar in some way. In fact, some parts of this library is coded to the standard draft asio is working on, which is called the Networking TS. 13 | 14 | # examples 15 | 16 | **See: ** 17 | 18 | # usage 19 | 20 | ## requirements 21 | 22 | In order to use io-uring you will need kernel version >= 5.1. On lower versions of kernels, io-uring has some known bugs making it unable to use for production, and fewer calls were supported, too. To use this library without pain, kernel >= 5.6 is suggested. 23 | 24 | On linux, Coroutine TS support is only mature enough with clang++ and libcxx. If you don't need coroutines, it's not necessary. 25 | 26 | You will also need CMake > 3.1 and liburing. 27 | 28 | This library was written in `-fno-rtti -fno-exceptions` dialect of c++20. It does not require users to turn off those too, however. But it's strongly advised to do so, as it will lead to great performance boost and reduce size of generated binaries. 29 | 30 | ## compiling tests and examples 31 | 32 | ### on fedora 31+ 33 | 34 | ``` 35 | sudo dnf install -y cmake liburing-devel clang libcxx 36 | 37 | cd arkio 38 | git submodule init 39 | git submodule update -r 40 | 41 | mkdir build 42 | cd build 43 | cmake -H.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DWITH_COROUTINES=YES 44 | make 45 | make test 46 | ``` 47 | 48 | as you may have already known, remove `-DWITH_COROUTINES=YES` to disable coroutines. 49 | 50 | ## compiling projects using arkio 51 | 52 | ### with cmake 53 | 54 | first clone this repo as a submodule or subtree, to `vendor/arkio` for example 55 | 56 | ``` cmake 57 | add_subdirectory(vendor/arkio) 58 | 59 | ... 60 | 61 | target_link_libraries(your_program_name PUBLIC arkio) 62 | ``` 63 | 64 | ### without cmake 65 | 66 | add these to your including paths 67 | 68 | ``` 69 | include 70 | vendor/GSL/include 71 | vendor/function2/include 72 | vendor/outcome/single-header 73 | ``` 74 | 75 | remember to include and link liburing by yourself. 76 | 77 | # motivation 78 | 79 | By the time of writing, asio is still using epoll as its low-level kernel interface, which imitates the proactor pattern with reactor pattern. This is inaffective and requires many epoll-only magic techniques. What's more, the asio interface was designed cross-platform, but it is affected greatly by the iocp interface of Microsoft windows. From 5.1, kernel introduced a set of new API named io-uring, which enables us to create real proactor implementions with a great performance boost. This library aims to port that functionality to c++, with APIs tailored just for linux. 80 | 81 | This library aims to get away with those historical burden and a few others. By making use of Coroutine TS and GSL and outcome, modern c++ programming is getting powerful and free as it had never be. IO programming is a major use case of c++, and this library exist to port that enlightenment to it. 82 | 83 | # a few notes 84 | 85 | This repo is in a pre-alpha state, and all the APIs are unstable. 86 | 87 | Please give a star if you like it, that would really encourage me to go on with the project. 88 | 89 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - [x] remove kernel version barriers 2 | - [x] split seekable and unseekable fd 3 | - [x] bind fd to async contexts 4 | - [ ] better tests 5 | - [x] ec_or to ned14/outcome 6 | - [x] pipefd 7 | - [x] doxygen intergration 8 | - [ ] timerfd 9 | - [ ] streambuf migration 10 | - [ ] multithreading 11 | 12 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # mcss doxygen config 2 | 3 | DOXYFILE = 'Doxyfile-mcss' 4 | 5 | STYLESHEETS = [ 6 | '../css/m-dark+documentation.compiled.css' 7 | ] 8 | THEME_COLOR = '#22272e' 9 | FAVICON = 'favicon-dark.png' 10 | 11 | LINKS_NAVBAR1 = [ 12 | ("Modules", 'modules', []), 13 | ("Pages", 'pages', []), 14 | ("Examples", 'page_examples', []) 15 | ] 16 | 17 | LINKS_NAVBAR2 = [ 18 | ("GitHub", []) 19 | ] 20 | 21 | FINE_PRINT = "" 22 | 23 | SHOW_UNDOCUMENTED = True 24 | 25 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXAMPLE_SRCS 2 | sync_cat.cpp;sync_echo_server.cpp;async_echo_server.cpp) 3 | 4 | if(${WITH_COROUTINES}) 5 | list(APPEND EXAMPLE_SRCS coro_cat.cpp;coro_echo_server.cpp) 6 | endif() 7 | 8 | foreach(example_src IN ITEMS ${EXAMPLE_SRCS}) 9 | get_filename_component(example_target ${example_src} NAME_WE) 10 | add_executable(${example_target} ${example_src}) 11 | target_link_libraries(${example_target} PUBLIC arkio) 12 | endforeach() 13 | 14 | -------------------------------------------------------------------------------- /examples/async_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define PRINT_ACCESS_LOG 10 | 11 | namespace program { 12 | 13 | using namespace ark; 14 | namespace tcp = net::tcp; 15 | 16 | struct echo_service : public std::enable_shared_from_this { 17 | tcp::socket s_; 18 | std::array buf_; 19 | 20 | #ifdef PRINT_ACCESS_LOG 21 | net::address addr_; 22 | 23 | echo_service(tcp::socket s, net::address addr) 24 | : s_(std::move(s)), addr_(std::move(addr)) { 25 | std::cout << "connected with peer " << peer_name() << std::endl; 26 | } 27 | 28 | ~echo_service() { 29 | std::cout << "closed connection to " << peer_name() << std::endl; 30 | } 31 | 32 | std::string peer_name() { 33 | auto ret = to_string(addr_); 34 | if (!ret) 35 | return std::string{"["} + ret.error().message() + "]"; 36 | return ret.value(); 37 | } 38 | 39 | #else 40 | echo_service(async_context &ctx, tcp::socket s) : s_(std::move(s)) {} 41 | #endif 42 | 43 | void do_echo() { 44 | async::read(s_, buffer(buf_), transfer_at_least(1), 45 | std::bind(&echo_service::handle_read, shared_from_this(), 46 | std::placeholders::_1)); 47 | } 48 | 49 | void handle_error(error_code ec) { std::cerr << ec.message() << std::endl; } 50 | 51 | void handle_write(result ret) { 52 | if (ret.has_error()) { 53 | handle_error(ret.error()); 54 | return; 55 | } 56 | do_echo(); 57 | } 58 | 59 | void handle_read(result ret) { 60 | if (ret.has_error()) { 61 | handle_error(ret.error()); 62 | return; 63 | } 64 | size_t sz = ret.value(); 65 | if (sz == 0) 66 | return; 67 | async::write(s_, buffer(buf_, sz), 68 | std::bind(&echo_service::handle_write, shared_from_this(), 69 | std::placeholders::_1)); 70 | } 71 | }; 72 | 73 | struct echo_server : public std::enable_shared_from_this { 74 | tcp::acceptor ac_; 75 | net::address addr_; 76 | 77 | echo_server(tcp::acceptor ac) : ac_(std::move(ac)) {} 78 | 79 | void run() { 80 | #ifdef PRINT_ACCESS_LOG 81 | tcp::async::accept(ac_, addr_, 82 | std::bind(&echo_server::handle_connection, 83 | shared_from_this(), std::placeholders::_1)); 84 | #else 85 | tcp::async::accept(ac_, 86 | std::bind(&echo_server::handle_connection, 87 | shared_from_this(), std::placeholders::_1)); 88 | #endif 89 | } 90 | 91 | void handle_connection(result ret) { 92 | if (!ret) { 93 | ac_.context().exit(ret.as_failure()); 94 | return; 95 | } 96 | #ifdef PRINT_ACCESS_LOG 97 | auto svc = std::make_shared(std::move(ret.value()), 98 | std::move(addr_)); 99 | #else 100 | auto svc = std::make_shared(std::move(ret.value())); 101 | #endif 102 | svc->do_echo(); 103 | 104 | run(); 105 | } 106 | }; 107 | 108 | result run() { 109 | async_context ctx; 110 | TryX(ctx.init()); 111 | 112 | net::inet_address ep; 113 | TryX(ep.host("127.0.0.1")); 114 | ep.port(8080); 115 | 116 | auto ac = TryX(tcp::acceptor::create(ctx)); 117 | TryX(tcp::bind(ac, ep)); 118 | TryX(tcp::listen(ac)); 119 | 120 | auto srv = std::make_shared(std::move(ac)); 121 | srv->run(); 122 | 123 | TryX(ctx.run()); 124 | return success(); 125 | } 126 | 127 | } // namespace program 128 | 129 | int main(void) { 130 | auto ret = program::run(); 131 | if (ret.has_error()) { 132 | std::cerr << "error : " << ret.error().message() << std::endl; 133 | std::abort(); 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /examples/coro_cat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace program { 10 | 11 | using namespace ark; 12 | 13 | task> to_stdout(normal_file &f) { 14 | std::array buf; 15 | for (;;) { 16 | size_t sz = 17 | CoTryX(co_await coro::read(f, buffer(buf), transfer_at_least(1))); 18 | if (sz == 0) { 19 | co_return success(); 20 | } 21 | std::cout.write(buf.data(), sz); 22 | std::cout.flush(); 23 | } 24 | } 25 | 26 | task> coro_cat(async_context &ctx, 27 | const std::vector &filenames) { 28 | context_exit_guard g_(ctx); 29 | 30 | for (auto &filename : filenames) { 31 | auto f = CoTryX(normal_file::open(ctx, filename, O_RDONLY)); 32 | CoTryX(co_await to_stdout(f)); 33 | } 34 | 35 | co_return success(); 36 | } 37 | 38 | result run(int argc, char **argv) { 39 | async_context ctx; 40 | TryX(ctx.init()); 41 | 42 | std::vector filenames{}; 43 | for (int i = 1; i < argc; ++i) 44 | filenames.emplace_back(argv[i]); 45 | 46 | auto fut = co_async(coro_cat(ctx, filenames)); 47 | TryX(ctx.run()); 48 | TryX(fut.get()); 49 | 50 | return success(); 51 | } 52 | 53 | } // namespace program 54 | 55 | int main(int argc, char **argv) { 56 | auto ret = program::run(argc, argv); 57 | if (ret.has_error()) { 58 | std::cerr << "error : " << ret.error().message() << std::endl; 59 | std::abort(); 60 | } 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /examples/coro_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define PRINT_ACCESS_LOG 10 | 11 | namespace program { 12 | 13 | using namespace ark; 14 | namespace tcp = net::tcp; 15 | 16 | task> handle_conn(tcp::socket s) { 17 | for (;;) { 18 | std::array buf; 19 | 20 | size_t sz = 21 | CoTryX(co_await coro::read(s, buffer(buf), transfer_at_least(1))); 22 | 23 | if (sz == 0) 24 | break; 25 | 26 | CoTryX(co_await coro::write(s, buffer(buf, sz))); 27 | } 28 | co_return success(); 29 | } 30 | 31 | #ifdef PRINT_ACCESS_LOG 32 | task run_handle_conn(tcp::socket s, net::address addr) { 33 | auto addr_ret = to_string(addr); 34 | std::string addr_s; 35 | if (addr_ret.has_error()) 36 | addr_s = "[invalid addr]"; 37 | else 38 | addr_s = addr_ret.value(); 39 | std::cout << "accepted connection from " << addr_s << std::endl; 40 | #else 41 | task run_handle_conn(tcp::socket s) { 42 | #endif 43 | auto ret = co_await handle_conn(std::move(s)); 44 | if (ret.has_error()) 45 | std::cerr << ret.error().message() << std::endl; 46 | #ifdef PRINT_ACCESS_LOG 47 | std::cout << "connection to " << addr_s << " ended" << std::endl; 48 | #endif 49 | } 50 | 51 | task> echo_srv(tcp::acceptor &ac) { 52 | context_exit_guard g_(ac.context()); 53 | 54 | for (;;) { 55 | #ifdef PRINT_ACCESS_LOG 56 | net::address addr; 57 | tcp::socket s = CoTryX(co_await tcp::coro::accept(ac, addr)); 58 | co_async(run_handle_conn(std::move(s), std::move(addr))); 59 | #else 60 | tcp::socket s = CoTryX(co_await tcp::coro::accept(ac)); 61 | co_async(run_handle_conn(std::move(s))); 62 | #endif 63 | } 64 | } 65 | 66 | result run() { 67 | async_context ctx; 68 | TryX(ctx.init()); 69 | 70 | net::inet_address ep; 71 | TryX(ep.host("127.0.0.1")); 72 | ep.port(8080); 73 | 74 | auto ac = TryX(tcp::acceptor::create(ctx)); 75 | TryX(tcp::bind(ac, ep)); 76 | TryX(tcp::listen(ac)); 77 | 78 | auto fut = co_async(echo_srv(ac)); 79 | TryX(ctx.run()); 80 | TryX(fut.get()); 81 | 82 | return success(); 83 | } 84 | 85 | } // namespace program 86 | 87 | int main(void) { 88 | auto ret = program::run(); 89 | if (ret.has_error()) { 90 | std::cerr << "error : " << ret.error().message() << std::endl; 91 | std::abort(); 92 | } 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /examples/sync_cat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace program { 10 | 11 | using namespace ark; 12 | 13 | result run(int argc, char **argv) { 14 | 15 | for (int i = 1; i < argc; i++) { 16 | auto f = TryX(normal_file::open({argv[i]}, O_RDONLY)); 17 | std::array buf; 18 | for (;;) { 19 | size_t sz = TryX(sync::read(f, buffer(buf), transfer_at_least(1))); 20 | if (sz == 0) { 21 | break; 22 | } 23 | std::cout.write(buf.data(), sz); 24 | } 25 | } 26 | 27 | return success(); 28 | } 29 | 30 | } // namespace program 31 | 32 | int main(int argc, char **argv) { 33 | auto ret = program::run(argc, argv); 34 | if (ret.has_error()) { 35 | std::cerr << "error : " << ret.error().message() << std::endl; 36 | std::abort(); 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /examples/sync_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define PRINT_ACCESS_LOG 10 | 11 | namespace program { 12 | 13 | using namespace ark; 14 | namespace tcp = net::tcp; 15 | 16 | result run() { 17 | net::inet_address ep; 18 | TryX(ep.host("127.0.0.1")); 19 | ep.port(8080); 20 | 21 | auto ac = TryX(tcp::acceptor::create()); 22 | TryX(tcp::bind(ac, ep)); 23 | TryX(tcp::listen(ac)); 24 | 25 | for (;;) { 26 | #ifdef PRINT_ACCESS_LOG 27 | net::address addr; 28 | tcp::socket s = TryX(tcp::sync::accept(ac, addr)); 29 | auto in_addr = TryX(net::inet_address::from_address(addr)); 30 | std::string in_addr_s = TryX(to_string(in_addr)); 31 | std::cout << "accepted connection from " << in_addr_s << std::endl; 32 | #else 33 | tcp::socket s = TryX(tcp::sync::accept(ac).value()); 34 | #endif 35 | for (;;) { 36 | std::array buf; 37 | size_t sz = TryX(sync::read(s, buffer(buf), transfer_at_least(1))); 38 | if (sz == 0) 39 | break; 40 | TryX(sync::write(s, buffer(buf, sz))); 41 | } 42 | #ifdef PRINT_ACCESS_LOG 43 | std::cout << "connection ended" << std::endl; 44 | #endif 45 | } 46 | 47 | return success(); 48 | } 49 | 50 | } // namespace program 51 | 52 | int main(void) { 53 | auto ret = program::run(); 54 | if (ret.has_error()) { 55 | std::cerr << "error : " << ret.error().message() << std::endl; 56 | std::abort(); 57 | } 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /include/ark.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! 4 | * \brief grand namespace for all non-specific symbols of arkio 5 | */ 6 | namespace ark {} // namespace ark 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifndef ARK_NO_COROUTINES 14 | #include 15 | #endif 16 | 17 | #include 18 | -------------------------------------------------------------------------------- /include/ark/async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup async 4 | * \brief This module provides fundamental class for async operations. 5 | * 6 | * like \ref ::ark::async_context and \ref ::ark::callback 7 | */ 8 | 9 | #include "ark/async/async_op.hpp" 10 | #include "ark/async/callback.hpp" 11 | #include "ark/async/context.hpp" 12 | -------------------------------------------------------------------------------- /include/ark/async/async_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ark { 10 | 11 | template class async_op { 12 | public: 13 | using locals_t = typename Impl::locals_t; 14 | using ret_t = typename Impl::ret_t; 15 | using callback_t = callback; 16 | 17 | private: 18 | callback_t cb_; 19 | 20 | public: 21 | async_context &ctx_; 22 | unique_ptr locals_; 23 | 24 | async_op(async_context &ctx, callback_t &&cb, 25 | unique_ptr locals) noexcept 26 | : ctx_(ctx), cb_(forward(cb)), locals_(move(locals)) {} 27 | 28 | template 29 | callback 30 | yield(unique_function next) noexcept { 31 | return [moved_this(move(*this)), next(move(next))](CallRet ret) mutable { 32 | next(moved_this, ret); 33 | }; 34 | } 35 | 36 | syscall_callback_t yield_syscall( 37 | unique_function 38 | next) noexcept { 39 | return yield(move(next)); 40 | } 41 | 42 | void run() noexcept { Impl::run(*this); } 43 | 44 | template >> 45 | void complete(Ret ret) noexcept { 46 | cb_(move(ret)); 47 | } 48 | 49 | template >> 50 | void complete() noexcept { 51 | cb_(); 52 | } 53 | }; 54 | 55 | } // namespace ark 56 | 57 | /*! \endcond */ 58 | -------------------------------------------------------------------------------- /include/ark/async/callback.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ark { 6 | 7 | /*! \addtogroup async 8 | * @{ 9 | */ 10 | 11 | /*! 12 | * \brief a movable noncopyable unique_function invoked to signal completion 13 | * 14 | * arkio callbacks are function2::unique_function, which is just like 15 | * std::function but movable and non-copyable. So, it is safe to move capture 16 | * values, and use the async io operation to prolong their lifetime inside 17 | * async_context. 18 | * 19 | * ahout function2 : https://github.com/Naios/function2 20 | * the document for unique_function is inside readme 21 | * 22 | * \tparam RetType if void, the signature of callback function is void(), 23 | * otherwise void(RetType ret) 24 | */ 25 | template 26 | struct callback : public unique_function { 27 | using unique_function::unique_function; 28 | using ret_type = RetType; 29 | }; 30 | 31 | /*! 32 | * \copydoc callback 33 | */ 34 | template <> struct callback : public unique_function { 35 | using unique_function::unique_function; 36 | using ret_type = void; 37 | }; 38 | 39 | /*! \cond INTERNAL_CLASSES */ 40 | 41 | using syscall_callback_t = callback>; 42 | 43 | /*! \endcond */ 44 | 45 | /*! @} */ 46 | 47 | } // namespace ark 48 | -------------------------------------------------------------------------------- /include/ark/async/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ark { 10 | 11 | /*! \addtogroup async 12 | * @{ 13 | */ 14 | 15 | #ifdef USING_DOXYGEN 16 | 17 | /*! 18 | * \brief context for running async functions 19 | * 20 | * The async_context could be bound to io objects, often as their first argument 21 | * to static construct functions. Then if async io operations got completed, the 22 | * callbacks would get invoked inside run() of the bound context. 23 | * 24 | * Currently, only single-threaded context run() invocation is supported, 25 | * however, calling its functions from other thread or submitting io operations 26 | * is supported. 27 | */ 28 | class async_context { 29 | public: 30 | /*! 31 | * \brief constructor 32 | * 33 | * the default constructed async_context is in an unusable state, you'll have 34 | * to call init() before doing anything 35 | */ 36 | async_context() noexcept; 37 | 38 | /*! 39 | * \brief init the context 40 | */ 41 | result init() noexcept; 42 | 43 | /*! 44 | * \brief start the event loop 45 | * 46 | * start the event loop on current thread, continuously wait for and process 47 | * events, and invoke callbacks 48 | */ 49 | result run() noexcept; 50 | 51 | /*! 52 | * \brief let run() successfully return 53 | */ 54 | void exit() noexcept; 55 | 56 | /*! 57 | * \brief let run() return with given result 58 | */ 59 | void exit(result ret); 60 | }; 61 | 62 | #else 63 | 64 | using async_context = io_uring_async::singlethread_uring_async_context; 65 | 66 | namespace async_syscall = io_uring_async::syscall; 67 | 68 | #endif 69 | 70 | /*! 71 | * \brief able to bound to an async_context 72 | * 73 | * io objects inherited from this class is able to store a nullable reference to 74 | * an async_context, then the context will be used for async io operations. 75 | */ 76 | class with_async_context { 77 | private: 78 | async_context *ctx_{nullptr}; 79 | 80 | public: 81 | /*! 82 | * \brief retrieves the context 83 | * 84 | * \pre a not-null async_context must have been bound to this object using 85 | * \ref set_async_context 86 | */ 87 | async_context &context() const noexcept { 88 | Ensures(ctx_ != nullptr); 89 | return *ctx_; 90 | } 91 | 92 | protected: 93 | /*! 94 | * \brief bound this io object to an async_context 95 | * 96 | * \pre must get called before any async operation has started, and must not 97 | * get called after that 98 | */ 99 | void set_async_context(async_context *ctx) noexcept { ctx_ = ctx; } 100 | }; 101 | 102 | /*! @} */ 103 | 104 | } // namespace ark 105 | -------------------------------------------------------------------------------- /include/ark/async/io_uring/async_syscall.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace ark { 11 | namespace io_uring_async { 12 | namespace syscall { 13 | template 14 | inline result 15 | read(UringContext &ctx, int fd, void *buf, unsigned nbytes, 16 | clinux::off_t offset, syscall_callback_t &&cb) noexcept { 17 | return ctx.add_sqe( 18 | [&ctx, fd, buf, nbytes, offset](sqe_ref sqe) { 19 | sqe.prep_read(fd, buf, nbytes, offset); 20 | }, 21 | forward(cb)); 22 | } 23 | 24 | template 25 | inline result 26 | write(UringContext &ctx, int fd, const void *buf, unsigned nbytes, 27 | clinux::off_t offset, syscall_callback_t &&cb) noexcept { 28 | return ctx.add_sqe( 29 | [&ctx, fd, buf, nbytes, offset](sqe_ref sqe) { 30 | sqe.prep_write(fd, buf, nbytes, offset); 31 | }, 32 | forward(cb)); 33 | } 34 | 35 | template 36 | inline result 37 | connect(UringContext &ctx, int fd, const clinux::sockaddr *addr, 38 | clinux::socklen_t addrlen, syscall_callback_t &&cb) noexcept { 39 | return ctx.add_sqe([&ctx, fd, addr, addrlen]( 40 | sqe_ref sqe) { sqe.prep_connect(fd, addr, addrlen); }, 41 | forward(cb)); 42 | } 43 | 44 | template 45 | inline result 46 | readv(UringContext &ctx, int fd, const clinux::iovec *iovecs, unsigned nr_vecs, 47 | clinux::off_t offset, syscall_callback_t &&cb) noexcept { 48 | return ctx.add_sqe( 49 | [&ctx, fd, iovecs, nr_vecs, offset](sqe_ref sqe) { 50 | sqe.prep_readv(fd, iovecs, nr_vecs, offset); 51 | }, 52 | forward(cb)); 53 | } 54 | 55 | template 56 | inline result 57 | writev(UringContext &ctx, int fd, const clinux::iovec *iovecs, unsigned nr_vecs, 58 | clinux::off_t offset, syscall_callback_t &&cb) noexcept { 59 | return ctx.add_sqe( 60 | [&ctx, fd, iovecs, nr_vecs, offset](sqe_ref sqe) { 61 | sqe.prep_writev(fd, iovecs, nr_vecs, offset); 62 | }, 63 | forward(cb)); 64 | } 65 | 66 | template 67 | inline result 68 | accept(UringContext &ctx, int fd, clinux::sockaddr *addr, 69 | clinux::socklen_t *addrlen, int flags, 70 | syscall_callback_t &&cb) noexcept { 71 | return ctx.add_sqe( 72 | [&ctx, fd, addr, addrlen, flags](sqe_ref sqe) { 73 | sqe.prep_accept(fd, addr, addrlen, flags); 74 | }, 75 | forward(cb)); 76 | } 77 | 78 | } // namespace syscall 79 | } // namespace io_uring_async 80 | } // namespace ark 81 | 82 | /*! \endcond */ 83 | -------------------------------------------------------------------------------- /include/ark/async/io_uring/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ark { 10 | namespace io_uring_async { 11 | using callback_t = unique_function ret)>; 12 | 13 | class base_singlethread_uring_async_context { 14 | public: 15 | using token_t = uintptr_t; 16 | using callback_t = unique_function ret)>; 17 | 18 | private: 19 | io_uring r_; 20 | map callbacks_; 21 | 22 | mutex m_submission_; 23 | mutex m_callbacks_; 24 | 25 | token_t callbacks_idx_; 26 | 27 | static const constexpr unsigned batch_size = 1024; 28 | 29 | int waker_evfd_; 30 | 31 | bool inited_; 32 | bool exiting_; 33 | error_code exiting_error_; 34 | 35 | template 37 | result base_add_sqe(const PrepSqeCallable &prep_sqe) noexcept { 38 | OUTCOME_TRY(sqe, r_.get_sqe()); 39 | prep_sqe(sqe); 40 | token_t tok = callbacks_idx_; 41 | sqe.set_data(reinterpret_cast(callbacks_idx_++)); 42 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 43 | sqe.dump(); 44 | #endif 45 | auto ret = wake(); 46 | if (ret.has_error()) { 47 | cancel(tok); 48 | return ret.as_failure(); 49 | } 50 | return tok; 51 | } 52 | 53 | result add_waker() noexcept { 54 | auto ret = 55 | add_sqe([this](sqe_ref sqe) { sqe.prep_poll_add(waker_evfd_, POLLIN); }, 56 | [this](result ret) { 57 | if (!ret) { 58 | this->exit(ret.error()); 59 | return; 60 | } 61 | array b; 62 | auto read_ret = clinux::read(waker_evfd_, b.data(), b.size()); 63 | if (read_ret == -1) { 64 | this->exit(errno_ec()); 65 | return; 66 | } 67 | auto next_ret = this->add_waker(); 68 | if (next_ret.has_error()) 69 | this->exit(next_ret.error()); 70 | }); 71 | if (ret.has_error()) 72 | return ret.as_failure(); 73 | return success(); 74 | } 75 | 76 | void submit() noexcept { 77 | lock_guard g_submission(m_submission_); 78 | int s = r_.submit(); 79 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 80 | cerr << "### SUBMITTED : " << s << endl; 81 | #endif 82 | } 83 | 84 | public: 85 | base_singlethread_uring_async_context() noexcept 86 | : inited_(false), exiting_(false), callbacks_idx_(0) {} 87 | 88 | result init() noexcept { 89 | Expects(!inited_); 90 | 91 | int waker_ret = clinux::eventfd(0, 0); 92 | if (waker_ret == -1) 93 | return errno_ec(); 94 | waker_evfd_ = waker_ret; 95 | 96 | auto ret = r_.queue_init(batch_size, 0); 97 | if (ret.has_error()) 98 | return ret.as_failure(); 99 | 100 | inited_ = true; 101 | return add_waker(); 102 | } 103 | 104 | void exit() noexcept { 105 | exiting_ = true; 106 | auto ret = wake(); 107 | if (ret.has_error()) { 108 | cerr << "error occurred, waking up thread failed : " 109 | << ret.error().message() << endl; 110 | abort(); 111 | } 112 | } 113 | 114 | void exit(result ret) noexcept { 115 | if (ret.has_error()) { 116 | exiting_error_ = ret.error(); 117 | } 118 | exit(); 119 | } 120 | 121 | result wake() noexcept { 122 | static uint64_t d{1}; 123 | auto write_ret = clinux::write(waker_evfd_, &d, sizeof(d)); 124 | if (write_ret == -1) { 125 | return errno_ec(); 126 | } 127 | return success(); 128 | } 129 | 130 | template 132 | result add_sqe(const PrepSqeCallable &prep_sqe) noexcept { 133 | lock_guard g_submission(m_submission_); 134 | return base_add_sqe(prep_sqe); 135 | } 136 | 137 | template 139 | result add_sqe(const PrepSqeCallable &prep_sqe, 140 | callback_t &&callback) noexcept { 141 | lock_guard g_submission(m_submission_); 142 | lock_guard g_callbacks(m_callbacks_); 143 | OUTCOME_TRY(tok, base_add_sqe(prep_sqe)); 144 | callbacks_[tok] = forward(callback); 145 | return tok; 146 | } 147 | 148 | void cancel(const token_t token) noexcept { 149 | lock_guard g_callbacks(m_callbacks_); 150 | callbacks_.erase(token); 151 | } 152 | 153 | result run() noexcept { 154 | for (;;) { 155 | if (exiting_) { 156 | exiting_ = false; 157 | if (exiting_error_) { 158 | error_code ret = exiting_error_; 159 | exiting_error_ = {}; 160 | return ret; 161 | } else { 162 | return success(); 163 | } 164 | } 165 | submit(); 166 | auto ret = r_.wait(); 167 | if (ret.has_error()) 168 | return ret.as_failure(); 169 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 170 | cerr << "### WOKE" << endl; 171 | #endif 172 | 173 | array cqe_buffer; 174 | auto cqe_end = r_.peek_batch_cqe(cqe_buffer.begin(), batch_size); 175 | list>> run_callbacks; 176 | { 177 | lock_guard g_callbacks(m_callbacks_); 178 | for (auto it = cqe_buffer.begin(); it != cqe_end; ++it) { 179 | unowning_cqe_ref cqe{*it}; 180 | 181 | token_t tok = reinterpret_cast(cqe.get_data()); 182 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 183 | cerr << "### GOT TOK IN CQE : " << tok << endl; 184 | #endif 185 | auto it_callback = callbacks_.find(tok); 186 | if (it_callback != callbacks_.end()) { 187 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 188 | cerr << "### FOUND CALLBACK" << endl; 189 | #endif 190 | run_callbacks.emplace_back(forward(it_callback->second), 191 | cqe.to_result()); 192 | callbacks_.erase(it_callback); 193 | } 194 | 195 | *it = {}; 196 | } 197 | } 198 | for (auto &it : run_callbacks) { 199 | it.first(it.second); 200 | } 201 | } 202 | } 203 | 204 | base_singlethread_uring_async_context( 205 | base_singlethread_uring_async_context &&) = delete; 206 | base_singlethread_uring_async_context & 207 | operator=(base_singlethread_uring_async_context &&) = delete; 208 | base_singlethread_uring_async_context( 209 | const base_singlethread_uring_async_context &) = delete; 210 | base_singlethread_uring_async_context & 211 | operator=(const base_singlethread_uring_async_context &) = delete; 212 | }; 213 | 214 | class singlethread_uring_async_context { 215 | public: 216 | using base_t = base_singlethread_uring_async_context; 217 | using token_t = base_t::token_t; 218 | using callback_t = base_t::callback_t; 219 | 220 | private: 221 | unique_ptr base_; 222 | 223 | public: 224 | singlethread_uring_async_context() noexcept 225 | : base_(make_unique()) {} 226 | 227 | result init() noexcept { return base_->init(); } 228 | 229 | result wake() noexcept { return base_->wake(); } 230 | 231 | template 233 | result add_sqe(const PrepSqeCallable &prep_sqe) noexcept { 234 | return base_->add_sqe(prep_sqe); 235 | } 236 | 237 | template 239 | result add_sqe(const PrepSqeCallable &prep_sqe, 240 | callback_t &&callback) noexcept { 241 | return base_->add_sqe(prep_sqe, forward(callback)); 242 | } 243 | 244 | void cancel(const token_t token) noexcept { base_->cancel(token); } 245 | 246 | result run() noexcept { return base_->run(); } 247 | 248 | void exit() noexcept { return base_->exit(); } 249 | 250 | void exit(result ret) noexcept { return base_->exit(move(ret)); } 251 | }; 252 | } // namespace io_uring_async 253 | } // namespace ark 254 | 255 | /*! \endcond */ 256 | -------------------------------------------------------------------------------- /include/ark/async/io_uring/io_uring.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ark { 10 | namespace io_uring_async { 11 | namespace liburing { 12 | using ::io_uring; 13 | using ::io_uring_cqe; 14 | using ::io_uring_cqe_get_data; 15 | using ::io_uring_cqe_seen; 16 | using ::io_uring_get_sqe; 17 | using ::io_uring_peek_batch_cqe; 18 | using ::io_uring_prep_accept; 19 | using ::io_uring_prep_connect; 20 | using ::io_uring_prep_nop; 21 | using ::io_uring_prep_poll_add; 22 | using ::io_uring_prep_read; 23 | using ::io_uring_prep_readv; 24 | using ::io_uring_prep_write; 25 | using ::io_uring_prep_writev; 26 | using ::io_uring_queue_exit; 27 | using ::io_uring_queue_init; 28 | using ::io_uring_sqe; 29 | using ::io_uring_sqe_set_data; 30 | using ::io_uring_sqe_set_flags; 31 | using ::io_uring_submit; 32 | using ::io_uring_wait_cqe; 33 | } // namespace liburing 34 | 35 | class sqe_ref { 36 | private: 37 | liburing::io_uring_sqe *sqe_; 38 | 39 | public: 40 | sqe_ref(not_null sqe) noexcept : sqe_(sqe) {} 41 | 42 | void prep_nop() noexcept { liburing::io_uring_prep_nop(sqe_); } 43 | 44 | void prep_read(int fd, void *buf, unsigned nbytes, 45 | clinux::off_t offset) noexcept { 46 | liburing::io_uring_prep_read(sqe_, fd, buf, nbytes, offset); 47 | } 48 | 49 | void prep_write(int fd, const void *buf, unsigned nbytes, 50 | clinux::off_t offset) noexcept { 51 | liburing::io_uring_prep_write(sqe_, fd, buf, nbytes, offset); 52 | } 53 | 54 | void prep_readv(int fd, const clinux::iovec *iovecs, unsigned nr_vecs, 55 | clinux::off_t offset) noexcept { 56 | liburing::io_uring_prep_readv(sqe_, fd, iovecs, nr_vecs, offset); 57 | } 58 | 59 | void prep_writev(int fd, const clinux::iovec *iovecs, unsigned nr_vecs, 60 | clinux::off_t offset) noexcept { 61 | liburing::io_uring_prep_writev(sqe_, fd, iovecs, nr_vecs, offset); 62 | } 63 | 64 | void prep_connect(int fd, const clinux::sockaddr *addr, 65 | clinux::socklen_t addrlen) noexcept { 66 | liburing::io_uring_prep_connect(sqe_, fd, addr, addrlen); 67 | } 68 | 69 | void prep_accept(int fd, clinux::sockaddr *addr, clinux::socklen_t *addrlen, 70 | int flags) noexcept { 71 | liburing::io_uring_prep_accept(sqe_, fd, addr, addrlen, flags); 72 | } 73 | 74 | void prep_poll_add(int fd, short poll_mask) noexcept { 75 | liburing::io_uring_prep_poll_add(sqe_, fd, poll_mask); 76 | } 77 | 78 | void set_data(void *data) noexcept { 79 | liburing::io_uring_sqe_set_data(sqe_, data); 80 | } 81 | 82 | void set_flags(unsigned flags) noexcept { 83 | liburing::io_uring_sqe_set_flags(sqe_, flags); 84 | } 85 | 86 | #ifdef ARK_ADVANCED_DEBUG_VERBOSITY 87 | void dump() noexcept { 88 | cerr << "===SQE INFO===" << endl; 89 | cerr << "opcode=" << static_cast(sqe_->opcode) << endl; 90 | cerr << "flags=" << static_cast(sqe_->flags) << endl; 91 | cerr << "ioprio=" << sqe_->ioprio << endl; 92 | cerr << "fd=" << sqe_->fd << endl; 93 | cerr << "addr=" << sqe_->addr << endl; 94 | cerr << "len=" << sqe_->len << endl; 95 | cerr << "rw_flags=" << sqe_->rw_flags << endl; 96 | cerr << "user_data=" << static_cast(sqe_->user_data) << endl; 97 | cerr << "===END SQE INFO===" << endl; 98 | } 99 | #endif 100 | }; 101 | 102 | class io_uring; 103 | 104 | template class cqe_ref { 105 | private: 106 | class empty_class {}; 107 | conditional_t parent_; 108 | liburing::io_uring_cqe *cqe_; 109 | 110 | friend cqe_ref; 111 | friend io_uring; 112 | 113 | void seen() noexcept { 114 | if constexpr (Owning) { 115 | if (cqe_ == nullptr) { 116 | return; 117 | } 118 | liburing::io_uring_cqe_seen(&parent_->ring_, cqe_); 119 | } 120 | } 121 | 122 | public: 123 | cqe_ref() noexcept : cqe_(nullptr) {} 124 | 125 | template = 0> 126 | cqe_ref(not_null cqe, io_uring &parent) noexcept 127 | : cqe_(cqe), parent_(&parent) {} 128 | 129 | template = 0> 130 | cqe_ref(cqe_ref &from) noexcept : cqe_{from.cqe_} {} 131 | 132 | template = 0> 133 | cqe_ref(cqe_ref &&other) noexcept : cqe_(other.cqe_), parent_(other.parent_) { 134 | other.cqe_ = nullptr; 135 | other.parent_ = nullptr; 136 | } 137 | 138 | template = 0> 139 | cqe_ref(cqe_ref &&other) noexcept : cqe_(other.cqe_) {} 140 | 141 | template = 0> 142 | cqe_ref(const cqe_ref &other) noexcept { 143 | cqe_ = other.cqe_; 144 | } 145 | 146 | template = 0> 147 | cqe_ref &operator=(cqe_ref &&other) noexcept { 148 | seen(); 149 | cqe_ = other.cqe_; 150 | parent_ = other.parent_; 151 | other.cqe_ = nullptr; 152 | other.parent_ = nullptr; 153 | return *this; 154 | } 155 | 156 | template = 0> 157 | cqe_ref &operator=(cqe_ref &&other) noexcept { 158 | seen(); 159 | cqe_ = other.cqe_; 160 | return *this; 161 | } 162 | 163 | template = 0> 164 | cqe_ref &operator=(const cqe_ref &other) noexcept { 165 | seen(); 166 | cqe_ = other.cqe_; 167 | return *this; 168 | } 169 | 170 | ~cqe_ref() { seen(); } 171 | 172 | void *get_data() noexcept { return liburing::io_uring_cqe_get_data(cqe_); } 173 | 174 | template result to_result() noexcept { 175 | if (cqe_->res < 0) { 176 | return error_code{-cqe_->res, system_category()}; 177 | } 178 | return static_cast(cqe_->res); 179 | } 180 | }; 181 | 182 | using owning_cqe_ref = cqe_ref; 183 | using unowning_cqe_ref = cqe_ref; 184 | 185 | class io_uring { 186 | private: 187 | liburing::io_uring ring_; 188 | bool inited_{false}; 189 | 190 | friend owning_cqe_ref; 191 | 192 | public: 193 | io_uring() noexcept {} 194 | 195 | result queue_init(unsigned entries, unsigned flags) noexcept { 196 | Expects(!inited_); 197 | int ret = liburing::io_uring_queue_init(entries, &ring_, flags); 198 | if (ret < 0) { 199 | return as_ec(-ret); 200 | } 201 | inited_ = true; 202 | return success(); 203 | } 204 | 205 | result get_sqe() noexcept { 206 | Expects(inited_); 207 | liburing::io_uring_sqe *ret = liburing::io_uring_get_sqe(&ring_); 208 | if (ret != nullptr) { 209 | return sqe_ref{ret}; 210 | } 211 | return as_ec(ENOBUFS); 212 | } 213 | 214 | int submit() noexcept { 215 | Expects(inited_); 216 | return liburing::io_uring_submit(&ring_); 217 | } 218 | 219 | result wait() noexcept { 220 | Expects(inited_); 221 | liburing::io_uring_cqe *p_discarded; 222 | int ret = liburing::io_uring_wait_cqe(&ring_, &p_discarded); 223 | if (ret < 0) { 224 | return as_ec(-ret); 225 | } 226 | return success(); 227 | } 228 | 229 | template 230 | OutputIt peek_batch_cqe(OutputIt it, size_t batch) noexcept { 231 | Expects(inited_); 232 | vector peeked{batch, nullptr}; 233 | const size_t peeked_n = 234 | liburing::io_uring_peek_batch_cqe(&ring_, peeked.data(), batch); 235 | for (int i = 0; i < peeked_n; i++) { 236 | *(it++) = owning_cqe_ref{peeked[i], *this}; 237 | } 238 | return it; 239 | } 240 | 241 | ~io_uring() { 242 | if (inited_) { 243 | liburing::io_uring_queue_exit(&ring_); 244 | } 245 | } 246 | 247 | io_uring(io_uring &&) = delete; 248 | io_uring(const io_uring &) = delete; 249 | io_uring &operator=(io_uring &&) = delete; 250 | io_uring &operator=(const io_uring &) = delete; 251 | }; 252 | } // namespace io_uring_async 253 | } // namespace ark 254 | 255 | /*! \endcond */ 256 | -------------------------------------------------------------------------------- /include/ark/bindings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #ifndef ARK_NO_COROUTINES 7 | #include 8 | #endif 9 | -------------------------------------------------------------------------------- /include/ark/bindings/bindings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /*! \cond NOT_DOCUMENTED */ 23 | 24 | namespace outcome = OUTCOME_V2_NAMESPACE; 25 | 26 | /*! \endcond */ 27 | 28 | namespace ark { 29 | 30 | /*! \addtogroup misc 31 | * @{ 32 | */ 33 | 34 | /*! \cond NOT_DOCUMENTED */ 35 | 36 | using byte = char; 37 | using fu2::function; 38 | using fu2::unique_function; 39 | using gsl::dynamic_extent; 40 | using gsl::not_null; 41 | using gsl::span; 42 | using gsl::strict_not_null; 43 | using outcome::success; 44 | using std::abort; 45 | using std::add_const_t; 46 | using std::add_pointer_t; 47 | using std::addressof; 48 | using std::array; 49 | using std::basic_string; 50 | using std::basic_string_view; 51 | using std::begin; 52 | using std::cbegin; 53 | using std::cend; 54 | using std::cerr; 55 | using std::conditional_t; 56 | using std::copy; 57 | using std::declval; 58 | using std::enable_if_t; 59 | using std::end; 60 | using std::endl; 61 | using std::errc; 62 | using std::error_code; 63 | using std::exchange; 64 | using std::false_type; 65 | using std::fill; 66 | using std::forward; 67 | using std::is_const_v; 68 | using std::is_convertible_v; 69 | using std::is_pointer_v; 70 | using std::is_same_v; 71 | using std::is_standard_layout_v; 72 | using std::is_trivially_copyable_v; 73 | using std::is_void_v; 74 | using std::istringstream; 75 | using std::list; 76 | using std::lock_guard; 77 | using std::make_error_code; 78 | using std::make_pair; 79 | using std::make_unique; 80 | using std::map; 81 | using std::max; 82 | using std::min; 83 | using std::move; 84 | using std::mutex; 85 | using std::numeric_limits; 86 | using std::optional; 87 | using std::ostringstream; 88 | using std::pair; 89 | using std::remove_const_t; 90 | using std::size_t; 91 | using std::string; 92 | using std::stringstream; 93 | using std::system_category; 94 | using std::system_error; 95 | using std::terminate; 96 | using std::true_type; 97 | using std::unique_ptr; 98 | using std::vector; 99 | 100 | /*! \endcond */ 101 | 102 | #ifndef USING_DOXYGEN 103 | 104 | using outcome::result; 105 | 106 | #else 107 | 108 | /*! 109 | * \brief result class from outcome library 110 | * 111 | * this binds an error_code to the given type 112 | * 113 | * \see \ref info_error 114 | */ 115 | template class result; 116 | 117 | #endif 118 | 119 | /*! 120 | * \brief alias the macro from outcome library to CamelCase 121 | * 122 | * in order to match the grammatical CamelCased macros in GSL, like Expects 123 | * 124 | * \see \ref info_error 125 | */ 126 | #define Try OUTCOME_TRY 127 | 128 | /*! 129 | * \copydoc Try 130 | */ 131 | #define CoTry OUTCOME_CO_TRY 132 | 133 | /*! 134 | * \copydoc Try 135 | */ 136 | #define TryX OUTCOME_TRYX 137 | 138 | /*! 139 | * \copydoc Try 140 | */ 141 | #define CoTryX OUTCOME_CO_TRYX 142 | 143 | /*! @} */ 144 | 145 | } // namespace ark 146 | -------------------------------------------------------------------------------- /include/ark/bindings/clinux.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! 4 | * \brief contains symbols normally placed in linux-headers 5 | * 6 | * arkio demands a few symbols from linux-headers, which is the c header files 7 | * for linux kernel. Functions and structs are aliased by keyword 'using', while 8 | * macros remain in the global. 9 | * 10 | * This approach is to avoid naming conflicts. 11 | */ 12 | namespace clinux {} 13 | 14 | /*! \cond FILE_NOT_DOCUMENTED */ 15 | 16 | #include 17 | 18 | extern "C" { 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | } 34 | 35 | namespace ark { 36 | namespace clinux { 37 | using ::accept4; 38 | using ::bind; 39 | using ::close; 40 | using ::connect; 41 | using ::eventfd; 42 | using ::htons; 43 | using ::inet_ntop; 44 | using ::inet_pton; 45 | using ::iovec; 46 | using ::listen; 47 | using ::loff_t; 48 | using ::lseek; 49 | using ::memfd_create; 50 | using ::mkostemp; 51 | using ::ntohs; 52 | using ::off_t; 53 | using ::open; 54 | using ::pipe2; 55 | using ::preadv2; 56 | using ::pwritev2; 57 | using ::read; 58 | using ::readv; 59 | using ::sa_family_t; 60 | using ::signal; 61 | using ::sockaddr; 62 | using ::sockaddr_in; 63 | using ::sockaddr_in6; 64 | using ::sockaddr_storage; 65 | using ::socket; 66 | using ::socklen_t; 67 | using ::splice; 68 | using ::vmsplice; 69 | using ::write; 70 | using ::writev; 71 | 72 | using version_t = long; 73 | 74 | static const inline version_t version_code = LINUX_VERSION_CODE; 75 | 76 | inline const constexpr version_t version(const int major, const int minor, 77 | const int micro) noexcept { 78 | return KERNEL_VERSION(major, minor, micro); 79 | } 80 | 81 | } // namespace clinux 82 | 83 | inline error_code as_ec(int errc_int) noexcept { 84 | return make_error_code(static_cast(errc_int)); 85 | } 86 | 87 | inline error_code errno_ec() noexcept { return as_ec(errno); } 88 | 89 | } // namespace ark 90 | 91 | /*! \endcond */ 92 | -------------------------------------------------------------------------------- /include/ark/bindings/coroutine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace ark { 11 | using std::future; 12 | using std::promise; 13 | using std::experimental::coroutine_handle; 14 | using std::experimental::suspend_always; 15 | using std::experimental::suspend_never; 16 | } // namespace ark 17 | 18 | /*! \endcond */ 19 | -------------------------------------------------------------------------------- /include/ark/bindings/doxygen_misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef USING_DOXYGEN 4 | 5 | /*! 6 | * \mainpage arkio 7 | * 8 | * arkio is a next-generation c++ io library, implementing async io-uring based 9 | * kernel io. 10 | * 11 | * This library is a modern C++ wrapper for the io interface of linux kernel. It 12 | * provides async interface for kernel io, along with the sync ones. What's 13 | * more, it supports C++20 Coroutines TS, too. 14 | * 15 | * The async model is based on kernel's new io-uring interface, and implements 16 | * the _proactor_ design pattern. 17 | * 18 | * The _proactor_ design pattern demultiplexes and dispatches events 19 | * asynchronously, and was made famous by Boost.ASIO in the c++ world. If you 20 | * are familar with asio, then you will find this library very similar in some 21 | * way. In fact, some parts of this library is coded to the standard draft asio 22 | * is working on, which is called the Networking TS. 23 | * 24 | * \htmlonly 25 | * 31 | * 37 | * \endhtmlonly 38 | */ 39 | 40 | /*! 41 | * \example coro_echo_server.cpp 42 | * 43 | * This is an echo server, implemented using coroutines. It is able to handle 44 | * multiple connections in a single thread. 45 | */ 46 | 47 | /*! 48 | * \example async_echo_server.cpp 49 | * 50 | * This is an echo server, implemented using callbacks. It is able to handle 51 | * multiple connections in a single thread. 52 | */ 53 | 54 | /*! 55 | * \example sync_echo_server.cpp 56 | * 57 | * This is an echo server, implemented using sync apis. Unable to handle 58 | * multiple connections at once. 59 | */ 60 | 61 | /*! 62 | * \example coro_cat.cpp 63 | * 64 | * This is a simplified cat(1) utility which prints files to screen, implemented 65 | * using coroutines. 66 | */ 67 | 68 | /*! 69 | * \example sync_cat.cpp 70 | * 71 | * This is a simplified cat(1) utility which prints files to screen, implemented 72 | * using sync apis. 73 | */ 74 | 75 | /*! 76 | * \page page_examples Examples 77 | * 78 | * \section echo_server echo server 79 | * 80 | * - \ref coro_echo_server.cpp 81 | * - \ref async_echo_server.cpp 82 | * - \ref sync_echo_server.cpp 83 | * 84 | * The echo server is a tcp server which send back everything it received. These 85 | * examples demonstrate how network io is performed with arkio. 86 | * 87 | * \section cat_utility cat utility 88 | * 89 | * - \ref coro_cat.cpp 90 | * - \ref sync_cat.cpp 91 | * 92 | * The cat(1) utility reads a series of filenames from command line and print 93 | * their content to the console. These examples demonstrates how normal file io 94 | * is performed with arkio. Notice how file-based async io is fencelessly 95 | * combined with network io. This could only be done with io-uring based 96 | * libraries. 97 | * 98 | */ 99 | 100 | /*! 101 | * \page info_coro Coroutines TS and Coroutine Task Type 102 | * 103 | * The coroutine function of arkio requires C++20 coroutines ts. However, the 104 | * original technical standard alone is not enough for writing nested 105 | * coroutines, and would normally require libraries like cppcoro() to provide 106 | * aiding classes and functions. 107 | * 108 | * This library does not use cppcoro, however, but implemented a set of tools 109 | * introduced in another paper, 110 | * p1056r0(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1056r0.html) 111 | * to support coroutine nesting and spawning, for details, see \ref ::ark::task 112 | * and \ref ::ark::co_async 113 | * 114 | * For complete examples on how to write coroutine programs with arkio, see: 115 | * 116 | * - \ref coro_echo_server.cpp 117 | * - \ref coro_cat.cpp 118 | */ 119 | 120 | /*! 121 | * \page info_network the Networking TS 122 | * 123 | * This library implemented some parts of networking ts, if you are familar with 124 | * asio, you will find the apis very similar in some way. However, there are a 125 | * few differences: 126 | * 127 | * - eof is not an error (in read operations), will just return when eof 128 | * encountered (catering to the linux kernel interface) 129 | * - error handling via outcome, see \ref info_error 130 | * - much simpler async_context, comparing to io_context/executors, no scheduler 131 | * nor executor indeed. 132 | * - support for general fd io, not only networking 133 | * - cancellations not supported (linux kernel support for cancellations is 134 | * buggy) 135 | * ... 136 | * 137 | * In addition, some parts of this library is coded 100% compatible to the 138 | * latest draft standard of networking ts, 139 | * n4771(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf): 140 | * 141 | * - the whole \ref buffer module 142 | * - the CompletionCondition 143 | */ 144 | 145 | /*! 146 | * \page info_error Error Handling and Outcome 147 | * 148 | * It's recommended to use -fno-exceptions with this library. How does it handle 149 | * errors? The answer is that it uses the outcome 150 | * library(https://github.com/ned14/outcome). 151 | * 152 | * Internally, \ref ::ark::result is aliased to 153 | * outcome::result(outcome::std_result in fact), and these macros are also 154 | * aliased to match the style of GSL macros: 155 | * - \ref ::Try -> OUTCOME_TRY 156 | * - \ref ::CoTry -> OUTCOME_CO_TRY 157 | * 158 | * The other parts of outcome library is not used for simplicity. 159 | * 160 | * Refer to the documents of outcome library(https://ned14.github.io/outcome/) 161 | * for usage. 162 | * 163 | * This is (in my opinion) not the optimal state of c++ error handling, will 164 | * switch to 165 | * herbceptions(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r1.pdf) 166 | * when the time is right. 167 | */ 168 | 169 | #endif 170 | -------------------------------------------------------------------------------- /include/ark/bindings/ign_pipe.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | namespace detail { 10 | class ign_pipe { 11 | public: 12 | ign_pipe() noexcept { clinux::signal(SIGPIPE, SIG_IGN); } 13 | }; 14 | 15 | #ifndef ARK_NO_IGN_PIPE 16 | static const inline ign_pipe __ign_pipe{}; 17 | #endif 18 | } // namespace detail 19 | } // namespace ark 20 | 21 | /*! \endcond */ 22 | -------------------------------------------------------------------------------- /include/ark/buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup buffer 4 | * \brief This module provides buffer implemention as defined in N4771 5 | * Networking TS draft's 'Buffer' section. 6 | * 7 | * \see \ref info_network 8 | * 9 | * Buffers are built upon gsl::span, and the buffers all inherit from spans. 10 | * See \ref ::ark::base_buffer for details. gsl::span has bound checks, which 11 | * also make buffers of this module 'safe buffers' with bound checks, too. 12 | * 13 | * for more information about gsl: https://github.com/microsoft/GSL/ 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | -------------------------------------------------------------------------------- /include/ark/buffer/buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ark { 6 | 7 | /*! \addtogroup buffer 8 | * @{ 9 | */ 10 | 11 | /*! 12 | * \brief base type for mutable_buffer and const_buffer 13 | * 14 | * base_buffer is a buffer of specified type of chars. It is inherited from 15 | * gsl::span and could be constructed from it, too. As defined in N4771, 16 | * buffer types are very similar to spans, so only a few extra functions are 17 | * added here. Most common functions, such as begin() or operator[], were 18 | * implemented and documented by gsl::span. 19 | * 20 | * Notice that the base_buffer represents an unowning view of the underlying 21 | * type, and marks it for usage of buffered io operations like ark::sync::read 22 | * and ark::sync:write. It's the user's responsibility to keep the underlying 23 | * memory available before any io operation completes. 24 | * 25 | * \tparam CharT type of chars, if const qualified, then the buffer is immutable 26 | * and the data should not get modified. Ill-formed if sizeof(CharT) != 1 27 | */ 28 | template class base_buffer : public span { 29 | public: 30 | using span::span; 31 | 32 | static_assert(sizeof(CharT) == 1); 33 | 34 | /*! \brief construct from a gsl::span */ 35 | base_buffer(span sp) : span(move(sp)) {} 36 | 37 | /*! \brief construct from pointers with size */ 38 | base_buffer(void *p, size_t n) : span(static_cast(p), n) {} 39 | 40 | /*! \brief construct from const pointers with size 41 | * 42 | * internally uses static_cast, so won't work with non-const CharT 43 | */ 44 | base_buffer(const void *p, size_t n) 45 | : span(static_cast(p), n) {} 46 | 47 | /*! \brief ascending the buffer */ 48 | base_buffer &operator+=(size_t offset) noexcept { 49 | *this = this->subspan(offset); 50 | return *this; 51 | } 52 | }; 53 | 54 | /*! 55 | * \brief a view to a span of memory as mutable buffer 56 | * 57 | * \remark [buffer.mutable] as defined in N4771, see \ref info_network 58 | */ 59 | using mutable_buffer = base_buffer; 60 | 61 | /*! 62 | * \brief a view to a span of memory as const buffer 63 | * 64 | * \remark [buffer.const] as defined in N4771, see \ref info_network 65 | */ 66 | using const_buffer = base_buffer; 67 | 68 | /*! 69 | * \brief returns an ascended buffer by n bytes 70 | * 71 | * \remark [buffer.arithmetic] as defined in N4771, see \ref info_network 72 | */ 73 | inline mutable_buffer operator+(const mutable_buffer &b, size_t n) noexcept { 74 | return {static_cast(b.data()) + min(n, b.size()), 75 | b.size() - min(n, b.size())}; 76 | } 77 | 78 | /*! \copydoc operator+(const mutable_buffer &,size_t) */ 79 | inline mutable_buffer operator+(size_t n, const mutable_buffer &b) noexcept { 80 | return operator+(b, n); 81 | } 82 | 83 | /*! \copydoc operator+(const mutable_buffer &,size_t) */ 84 | inline const_buffer operator+(const const_buffer &b, size_t n) noexcept { 85 | return {static_cast(b.data()) + min(n, b.size()), 86 | b.size() - min(n, b.size())}; 87 | } 88 | 89 | /*! \copydoc operator+(const mutable_buffer &,size_t) */ 90 | inline const_buffer operator+(size_t n, const const_buffer &b) noexcept { 91 | return operator+(b, n); 92 | } 93 | 94 | /*! 95 | * \brief creates a \ref ::ark::mutable_buffer or \ref ::ark::const_buffer 96 | * 97 | * depending on the undelying type, creates a \ref ::ark::mutable_buffer or \ref 98 | * ::ark::const_buffer, an additional size_t could also be served to limit the 99 | * size of the buffer returned. 100 | * 101 | * \remark [buffer.creation] as defined in N4771, see \ref info_network 102 | */ 103 | inline mutable_buffer buffer(void *p, size_t n) noexcept { return {p, n}; } 104 | 105 | /*! \copydoc buffer(void *,size_t) */ 106 | inline const_buffer buffer(const void *p, size_t n) noexcept { return {p, n}; } 107 | 108 | /*! \copydoc buffer(void *,size_t) */ 109 | inline mutable_buffer buffer(const mutable_buffer &b) noexcept { return b; } 110 | 111 | /*! \copydoc buffer(void *,size_t) */ 112 | inline mutable_buffer buffer(const mutable_buffer &b, size_t n) noexcept { 113 | return {b.data(), min(b.size(), n)}; 114 | } 115 | 116 | /*! \copydoc buffer(void *,size_t) */ 117 | inline const_buffer buffer(const const_buffer &b) noexcept { return b; } 118 | 119 | /*! \copydoc buffer(void *,size_t) */ 120 | inline const_buffer buffer(const const_buffer &b, size_t n) noexcept { 121 | return {b.data(), min(b.size(), n)}; 122 | } 123 | 124 | /*! \cond DETAIL_MACROS */ 125 | 126 | #define ARK_BUFFER_CONSTRAIN_T \ 127 | static_assert(is_trivially_copyable_v || is_standard_layout_v, \ 128 | "T must be TriviallyCopyable or StandardLayout") 129 | 130 | #define ARK_BUFFER_IMPL_DATA \ 131 | return buffer(begin(data) != end(data) ? std::addressof(*begin(data)) \ 132 | : nullptr, \ 133 | (end(data) - begin(data)) * sizeof(*begin(data))); 134 | 135 | #define ARK_BUFFER_IMPL_DATA_CONSTRAIN_T \ 136 | ARK_BUFFER_CONSTRAIN_T; \ 137 | return buffer(begin(data) != end(data) ? std::addressof(*begin(data)) \ 138 | : nullptr, \ 139 | (end(data) - begin(data)) * sizeof(*begin(data))); 140 | 141 | #define ARK_BUFFER_IMPL_DATA_N return buffer(buffer(data), n); 142 | 143 | #define ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T \ 144 | ARK_BUFFER_CONSTRAIN_T; \ 145 | return buffer(buffer(data), n); 146 | 147 | /*! \endcond */ 148 | 149 | /*! \copydoc buffer(void *,size_t) */ 150 | template mutable_buffer buffer(T (&data)[N]) noexcept { 151 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 152 | } 153 | 154 | /*! \copydoc buffer(void *,size_t) */ 155 | template const_buffer buffer(const T (&data)[N]) noexcept { 156 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 157 | } 158 | 159 | /*! \copydoc buffer(void *,size_t) */ 160 | template mutable_buffer buffer(array &data) noexcept { 161 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 162 | } 163 | 164 | /*! \copydoc buffer(void *,size_t) */ 165 | template 166 | const_buffer buffer(array &data) noexcept { 167 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 168 | } 169 | 170 | /*! \copydoc buffer(void *,size_t) */ 171 | template 172 | const_buffer buffer(const array &data) noexcept { 173 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 174 | } 175 | 176 | /*! \copydoc buffer(void *,size_t) */ 177 | template 178 | mutable_buffer buffer(vector &data) noexcept { 179 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 180 | } 181 | 182 | /*! \copydoc buffer(void *,size_t) */ 183 | template 184 | const_buffer buffer(const vector &data) noexcept { 185 | ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 186 | } 187 | 188 | /*! \copydoc buffer(void *,size_t) */ 189 | template 190 | mutable_buffer buffer(basic_string &data) noexcept { 191 | ARK_BUFFER_IMPL_DATA 192 | } 193 | 194 | /*! \copydoc buffer(void *,size_t) */ 195 | template 196 | const_buffer 197 | buffer(const basic_string &data) noexcept { 198 | ARK_BUFFER_IMPL_DATA 199 | } 200 | 201 | /*! \copydoc buffer(void *,size_t) */ 202 | template 203 | const_buffer buffer(basic_string_view data) noexcept { 204 | ARK_BUFFER_IMPL_DATA 205 | } 206 | 207 | /*! \copydoc buffer(void *,size_t) */ 208 | template 209 | mutable_buffer buffer(T (&data)[N], size_t n) noexcept { 210 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 211 | } 212 | 213 | /*! \copydoc buffer(void *,size_t) */ 214 | template 215 | const_buffer buffer(const T (&data)[N], size_t n) noexcept { 216 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 217 | } 218 | 219 | /*! \copydoc buffer(void *,size_t) */ 220 | template 221 | mutable_buffer buffer(array &data, size_t n) noexcept { 222 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 223 | } 224 | 225 | /*! \copydoc buffer(void *,size_t) */ 226 | template 227 | const_buffer buffer(array &data, size_t n) noexcept { 228 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 229 | } 230 | 231 | /*! \copydoc buffer(void *,size_t) */ 232 | template 233 | const_buffer buffer(const array &data, size_t n) noexcept { 234 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 235 | } 236 | 237 | /*! \copydoc buffer(void *,size_t) */ 238 | template 239 | mutable_buffer buffer(vector &data, size_t n) noexcept { 240 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 241 | } 242 | 243 | /*! \copydoc buffer(void *,size_t) */ 244 | template 245 | const_buffer buffer(const vector &data, size_t n) noexcept { 246 | ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 247 | } 248 | 249 | /*! \copydoc buffer(void *,size_t) */ 250 | template 251 | mutable_buffer buffer(basic_string &data, 252 | size_t n) noexcept { 253 | ARK_BUFFER_IMPL_DATA_N 254 | } 255 | 256 | /*! \copydoc buffer(void *,size_t) */ 257 | template 258 | const_buffer buffer(const basic_string &data, 259 | size_t n) noexcept { 260 | ARK_BUFFER_IMPL_DATA_N 261 | } 262 | 263 | /*! \copydoc buffer(void *,size_t) */ 264 | template 265 | const_buffer buffer(basic_string_view data, size_t n) noexcept { 266 | ARK_BUFFER_IMPL_DATA_N 267 | } 268 | 269 | #undef ARK_BUFFER_CONSTRAIN_T 270 | #undef ARK_BUFFER_IMPL_DATA_CONSTRAIN_T 271 | #undef ARK_BUFFER_IMPL_DATA_N_CONSTRAIN_T 272 | #undef ARK_BUFFER_IMPL_DATA 273 | #undef ARK_BUFFER_IMPL_DATA_N 274 | 275 | /*! @} */ 276 | 277 | } // namespace ark 278 | -------------------------------------------------------------------------------- /include/ark/buffer/concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | 10 | namespace concepts { 11 | 12 | template concept MutableBufferSequence = requires(T bseq) { 13 | requires convertible_to; 15 | requires convertible_to; 16 | }; 17 | 18 | template concept ConstBufferSequence = requires(T bseq) { 19 | requires convertible_to; 20 | requires convertible_to; 21 | }; 22 | 23 | } // namespace concepts 24 | 25 | } // namespace ark 26 | -------------------------------------------------------------------------------- /include/ark/buffer/sequence.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | /*! \addtogroup buffer 10 | * @{ 11 | */ 12 | 13 | /*! 14 | * \brief returns an iterator to the first buffer of ConstBufferSequence 15 | * 16 | * returns std::addressof(b) for buffers, and c.begin() for iterables of buffers 17 | * 18 | * \remark [buffer.seq.access] as defined in N4771, see \ref info_network 19 | */ 20 | inline const mutable_buffer * 21 | buffer_sequence_begin(const mutable_buffer &b) noexcept { 22 | return addressof(b); 23 | } 24 | 25 | /*! \copydoc buffer_sequence_begin(const mutable_buffer &) */ 26 | inline const const_buffer * 27 | buffer_sequence_begin(const const_buffer &b) noexcept { 28 | return addressof(b); 29 | } 30 | 31 | /*! \copydoc buffer_sequence_begin(const mutable_buffer &) */ 32 | template || 33 | is_same_v)>> 34 | auto buffer_sequence_begin(C &c) noexcept { 35 | return c.begin(); 36 | } 37 | 38 | /*! \copydoc buffer_sequence_begin(const mutable_buffer &) */ 39 | template || 40 | is_same_v)>> 41 | auto buffer_sequence_begin(const C &c) noexcept { 42 | return c.begin(); 43 | } 44 | 45 | /*! 46 | * \brief returns an iterator past the last buffer of BufferSequence 47 | * 48 | * returns std::addressof(b)+1 for buffers, and c.end() for iterables of buffers 49 | * 50 | * \remark [buffer.seq.access] as defined in N4771, see \ref info_network 51 | */ 52 | inline const mutable_buffer * 53 | buffer_sequence_end(const mutable_buffer &b) noexcept { 54 | return addressof(b) + 1; 55 | } 56 | 57 | /*! \copydoc buffer_sequence_end(const mutable_buffer &) */ 58 | inline const const_buffer *buffer_sequence_end(const const_buffer &b) noexcept { 59 | return addressof(b) + 1; 60 | } 61 | 62 | /*! \copydoc buffer_sequence_end(const mutable_buffer &) */ 63 | template || 64 | is_same_v)>> 65 | auto buffer_sequence_end(C &c) noexcept { 66 | return c.end(); 67 | } 68 | 69 | /*! \copydoc buffer_sequence_end(const mutable_buffer &) */ 70 | template || 71 | is_same_v)>> 72 | auto buffer_sequence_end(const C &c) noexcept { 73 | return c.end(); 74 | } 75 | 76 | /*! 77 | * \brief returns the sum size of a ConstBufferSequence 78 | * 79 | * time complexity O(sizeof(buffers)) 80 | * 81 | * \remark [buffer.size] as defined in N4771, see \ref info_network 82 | */ 83 | template 84 | size_t buffer_size(const ConstBufferSequence &buffers) noexcept { 85 | size_t total_size = 0; 86 | auto i = buffer_sequence_begin(buffers); 87 | auto end = buffer_sequence_end(buffers); 88 | for (; i != end; ++i) { 89 | const_buffer b(*i); 90 | total_size += b.size(); 91 | } 92 | return total_size; 93 | } 94 | 95 | /*! 96 | * \brief copy the underlying bytes from one buffer to another 97 | * 98 | * \param[in] max_size the max bytes allowed to copy 99 | * 100 | * \pre no overlaps allowed between source and dest, otherwise the behavior is 101 | * undefined 102 | * 103 | * \remark [buffer.copy] as defined in N4771, see \ref info_network 104 | */ 105 | template 106 | size_t buffer_copy(const MutableBufferSequence &dest, 107 | const ConstBufferSequence &source, size_t max_size) { 108 | auto it_src = buffer_sequence_begin(source); 109 | auto it_dst = buffer_sequence_begin(dest); 110 | auto end_src = buffer_sequence_end(source); 111 | auto end_dst = buffer_sequence_end(dest); 112 | 113 | size_t done_sz = 0; 114 | const_buffer curr_src(*it_src); 115 | mutable_buffer curr_dst(*it_dst); 116 | while ((it_src != end_src) && (it_dst != end_dst)) { 117 | size_t curr_sz = min(curr_src.size(), curr_dst.size()); 118 | std::memcpy(curr_dst.data(), curr_src.data(), curr_sz); 119 | done_sz += curr_sz; 120 | 121 | if (max_size == curr_sz) { 122 | return max_size; 123 | } else { 124 | max_size -= curr_sz; 125 | } 126 | if (curr_src.size() == curr_sz) { 127 | it_src++; 128 | if (it_src != end_src) 129 | curr_src = const_buffer(*it_src); 130 | } else { 131 | curr_src += curr_sz; 132 | } 133 | if (curr_dst.size() == curr_sz) { 134 | it_dst++; 135 | if (it_dst != end_dst) 136 | curr_dst = mutable_buffer(*it_dst); 137 | } else { 138 | curr_dst += curr_sz; 139 | } 140 | } 141 | } 142 | 143 | /*! @} */ 144 | 145 | }; // namespace ark 146 | -------------------------------------------------------------------------------- /include/ark/coroutine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup coroutine 4 | * \brief This module provides tools used by coroutines ts related programming. 5 | * 6 | * Unlike other coroutine libraries, e.g. libunifex or cppcoro, this module is 7 | * centered with some most basic and simple tools, like proposed by p1056, 8 | * task<> and co_async. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | -------------------------------------------------------------------------------- /include/ark/coroutine/awaitable_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ark { 10 | 11 | template struct awaitable_op { 12 | private: 13 | optional ret_{}; 14 | 15 | public: 16 | virtual void invoke(callback &&cb) noexcept = 0; 17 | 18 | bool await_ready() noexcept { return ret_.has_value(); } 19 | 20 | void await_suspend(coroutine_handle ch) noexcept { 21 | invoke([ch, this](Ret ret) mutable { 22 | this->ret_.emplace(move(ret)); 23 | ch.resume(); 24 | }); 25 | } 26 | 27 | auto await_resume() noexcept { return *move(ret_); } 28 | }; 29 | 30 | template <> struct awaitable_op { 31 | private: 32 | bool ready{false}; 33 | 34 | public: 35 | virtual void invoke(callback &&cb) noexcept = 0; 36 | 37 | bool await_ready() noexcept { return ready; } 38 | 39 | void await_suspend(coroutine_handle ch) noexcept { 40 | invoke([ch, this]() mutable { 41 | ready = true; 42 | ch.resume(); 43 | }); 44 | } 45 | 46 | void await_resume() noexcept {} 47 | }; 48 | 49 | } // namespace ark 50 | 51 | /*! \endcond */ 52 | -------------------------------------------------------------------------------- /include/ark/coroutine/co_async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ark { 10 | 11 | /*! \addtogroup coroutine 12 | * @{ 13 | */ 14 | 15 | /*! 16 | * \brief start the task as another coroutine task series. 17 | * 18 | * the caller lost ownership of the task once it is started, so discarding the 19 | * returned future does not abort the task from running. 20 | * 21 | * \remark as defined in p1056r0 (with a different name), see \ref info_coro 22 | */ 23 | template inline future co_async(task tsk) noexcept { 24 | promise prom_; 25 | auto fut = prom_.get_future(); 26 | ([](promise prom_, task tsk) mutable -> fire_and_forget { 27 | if constexpr (is_void_v) { 28 | co_await move(tsk); 29 | prom_.set_value(); 30 | } else { 31 | prom_.set_value(co_await move(tsk)); 32 | } 33 | })(move(prom_), move(tsk)); 34 | return fut; 35 | } 36 | 37 | /*! @} */ 38 | 39 | } // namespace ark 40 | -------------------------------------------------------------------------------- /include/ark/coroutine/fire_and_forget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | /*! 10 | * \brief coroutine functions returning this type is internally cut-off with the 11 | * caller. 12 | * 13 | * Even if the caller discard the returning value, the underlying coroutine 14 | * won't get halted. 15 | * 16 | * Not exposed in concerns the caller would be 'too forgetful', use co_async 17 | * instead. 18 | */ 19 | class fire_and_forget { 20 | public: 21 | class promise_type { 22 | public: 23 | fire_and_forget get_return_object() noexcept { return {}; } 24 | 25 | auto initial_suspend() noexcept { return suspend_never{}; } 26 | 27 | auto final_suspend() noexcept { return suspend_never{}; } 28 | 29 | void return_void() noexcept {} 30 | 31 | void unhandled_exception() noexcept { terminate(); } 32 | 33 | void get() noexcept {} 34 | }; 35 | }; 36 | 37 | } // namespace ark 38 | 39 | /*! \endcond */ 40 | -------------------------------------------------------------------------------- /include/ark/coroutine/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | /*! \addtogroup coroutine 10 | * @{ 11 | */ 12 | 13 | /*! 14 | * \brief task<> as defined in p1056, without exception support 15 | * 16 | * This class could be used as return value in coroutines, so that the coroutine 17 | * could co_await the awaited functions provided in coro::, and return a value T 18 | * to the caller 19 | * 20 | * Exceptions not supported as this library uses outcome. Use task> 21 | * and CoTry for error handling 22 | * 23 | * \remark as defined in p1056r0, see \ref info_coro 24 | */ 25 | template class task; 26 | 27 | #ifndef USING_DOXYGEN 28 | 29 | template class task_promise { 30 | public: 31 | task_promise() noexcept {} 32 | 33 | ~task_promise() noexcept { clear(); } 34 | 35 | task get_return_object() noexcept; 36 | 37 | suspend_always initial_suspend() { return {}; } 38 | 39 | auto final_suspend() noexcept { 40 | struct awaiter { 41 | bool await_ready() noexcept { return false; } 42 | auto await_suspend(coroutine_handle h) noexcept { 43 | return h.promise().continuation_; 44 | } 45 | void await_resume() noexcept {} 46 | }; 47 | return awaiter{}; 48 | } 49 | 50 | template , int> = 0> 51 | void return_value(U &&value) noexcept { 52 | clear(); 53 | value_.construct((U &&) value); 54 | state_ = state_t::value; 55 | } 56 | 57 | void unhandled_exception() noexcept { terminate(); } 58 | 59 | T get() noexcept { return move(value_).get(); } 60 | 61 | private: 62 | friend class task; 63 | 64 | void clear() noexcept { 65 | switch (exchange(state_, state_t::empty)) { 66 | case state_t::empty: 67 | break; 68 | case state_t::value: 69 | value_.destruct(); 70 | break; 71 | } 72 | } 73 | 74 | coroutine_handle continuation_; 75 | enum class state_t { empty, value }; 76 | state_t state_ = state_t::empty; 77 | union { 78 | manual_lifetime value_; 79 | }; 80 | }; 81 | 82 | template <> class task_promise { 83 | public: 84 | task_promise() noexcept {} 85 | 86 | ~task_promise() noexcept { clear(); } 87 | 88 | task get_return_object() noexcept; 89 | 90 | suspend_always initial_suspend() noexcept { return {}; } 91 | 92 | auto final_suspend() noexcept { 93 | struct awaiter { 94 | bool await_ready() noexcept { return false; } 95 | auto await_suspend(coroutine_handle h) noexcept { 96 | return h.promise().continuation_; 97 | } 98 | void await_resume() noexcept {} 99 | }; 100 | return awaiter{}; 101 | } 102 | 103 | void return_void() noexcept { 104 | clear(); 105 | value_.construct(); 106 | state_ = state_t::value; 107 | } 108 | 109 | void unhandled_exception() noexcept { terminate(); } 110 | 111 | void get() noexcept {} 112 | 113 | private: 114 | friend class task; 115 | 116 | void clear() noexcept { 117 | switch (exchange(state_, state_t::empty)) { 118 | case state_t::empty: 119 | break; 120 | case state_t::value: 121 | value_.destruct(); 122 | break; 123 | } 124 | } 125 | 126 | enum class state_t { empty, value }; 127 | 128 | coroutine_handle continuation_; 129 | state_t state_ = state_t::empty; 130 | union { 131 | manual_lifetime value_; 132 | }; 133 | }; 134 | 135 | template class task { 136 | public: 137 | using promise_type = task_promise; 138 | using handle_t = coroutine_handle; 139 | 140 | explicit task(handle_t h) noexcept : coro_(h) {} 141 | 142 | task(task &&t) noexcept : coro_(exchange(t.coro_, {})) {} 143 | 144 | ~task() noexcept { 145 | if (coro_) { 146 | coro_.destroy(); 147 | } 148 | } 149 | 150 | auto operator co_await() &&noexcept { 151 | struct awaiter { 152 | public: 153 | explicit awaiter(handle_t coro) noexcept : coro_(coro) {} 154 | bool await_ready() noexcept { return false; } 155 | auto await_suspend(coroutine_handle h) noexcept { 156 | coro_.promise().continuation_ = h; 157 | return coro_; 158 | } 159 | T await_resume() noexcept { return coro_.promise().get(); } 160 | 161 | private: 162 | handle_t coro_; 163 | }; 164 | return awaiter(coro_); 165 | } 166 | 167 | private: 168 | handle_t coro_; 169 | }; 170 | 171 | template task task_promise::get_return_object() noexcept { 172 | return task(coroutine_handle>::from_promise(*this)); 173 | } 174 | 175 | inline task task_promise::get_return_object() noexcept { 176 | return task(coroutine_handle>::from_promise(*this)); 177 | } 178 | 179 | #endif 180 | 181 | /*! @} */ 182 | 183 | } // namespace ark 184 | -------------------------------------------------------------------------------- /include/ark/general.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup general 4 | * \brief This module contains general io objects like pipes or files 5 | * 6 | * While network related io objects has many dependencies like 7 | * addresses, some of others remain simple like pipes, files, etc. Those io 8 | * objects inherit from \ref ::ark::seekable_fd and \ref ::ark::fd, 9 | * and provides functions for constructing them. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /include/ark/general/event_fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | 10 | /*! \addtogroup general 11 | * @{ 12 | */ 13 | 14 | /*! 15 | * \brief wraps fildes created by eventfd(2) as an io object 16 | */ 17 | class event_fd : public fd { 18 | protected: 19 | /*! 20 | * \brief constructs from int fildes 21 | * 22 | * \param[in] fd_int must be an fildes opened by eventfd(2) 23 | */ 24 | event_fd(int fd_int) : fd(fd_int) {} 25 | 26 | private: 27 | static result __create(async_context *ctx, unsigned int count, 28 | int flags) noexcept { 29 | int ret = clinux::eventfd(count, flags); 30 | if (ret == -1) { 31 | return errno_ec(); 32 | } 33 | event_fd f{ret}; 34 | f.set_async_context(ctx); 35 | return move(f); 36 | } 37 | 38 | public: 39 | /*! 40 | * \brief construct and return an event_fd just like calling eventfd(2) 41 | */ 42 | static result create(unsigned int count, int flags) noexcept { 43 | return __create(nullptr, count, flags); 44 | } 45 | 46 | /*! 47 | * \brief construct and return an event_fd just like calling eventfd(2), and 48 | * bind to the given \ref ::ark::async_context 49 | */ 50 | static result create(async_context &ctx, unsigned int count, 51 | int flags) noexcept { 52 | return __create(&ctx, count, flags); 53 | } 54 | }; 55 | 56 | /*! @} */ 57 | 58 | } // namespace ark 59 | -------------------------------------------------------------------------------- /include/ark/general/mem_fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | 10 | /*! \addtogroup general 11 | * @{ 12 | */ 13 | 14 | /*! 15 | * \brief wraps fildes created by memfd_create(2) as an io object 16 | */ 17 | class mem_fd : public seekable_fd { 18 | protected: 19 | /*! 20 | * \brief constructs from int fildes 21 | * 22 | * \param[in] fd_int must be an fildes opened by memfd_create(2) 23 | */ 24 | mem_fd(int fd_int) : seekable_fd(fd_int) {} 25 | 26 | private: 27 | static result __create(async_context *ctx, const string &name, 28 | int flags) noexcept { 29 | int ret = clinux::memfd_create(name.c_str(), flags); 30 | if (ret == -1) { 31 | return errno_ec(); 32 | } 33 | mem_fd f{ret}; 34 | f.set_async_context(ctx); 35 | return move(f); 36 | } 37 | 38 | public: 39 | /*! 40 | * \brief construct and return an event_fd just like calling memfd_create(2) 41 | */ 42 | static result create(const string &name, int flags) noexcept { 43 | return __create(nullptr, name, flags); 44 | } 45 | 46 | /*! 47 | * \brief construct and return an event_fd just like calling memfd_create(2), 48 | * and bind to the given \ref ::ark::async_context 49 | */ 50 | static result create(async_context &ctx, const string &name, 51 | int flags) noexcept { 52 | return __create(&ctx, name, flags); 53 | } 54 | }; 55 | 56 | /*! @} */ 57 | 58 | } // namespace ark 59 | -------------------------------------------------------------------------------- /include/ark/general/normal_file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | 10 | /*! \addtogroup general 11 | * @{ 12 | */ 13 | 14 | /*! 15 | * \brief wraps fildes that represents an normal file as an io object 16 | */ 17 | class normal_file : public seekable_fd { 18 | protected: 19 | /*! 20 | * \brief constructs from int fildes 21 | * 22 | * \param[in] fd_int must be an fildes that represents an normal file 23 | */ 24 | normal_file(int fd_int) : seekable_fd(fd_int) {} 25 | 26 | private: 27 | static result __open(async_context *ctx, string path, int flags, 28 | mode_t mode) noexcept { 29 | int ret = clinux::open(path.c_str(), flags, mode); 30 | if (ret == -1) { 31 | return errno_ec(); 32 | } 33 | normal_file ret_fd(ret); 34 | ret_fd.set_async_context(ctx); 35 | return move(ret_fd); 36 | } 37 | 38 | static result __mkostemp(async_context *ctx, const string &templ, 39 | int flags) noexcept { 40 | int ret = clinux::mkostemp(const_cast(templ.c_str()), flags); 41 | if (ret == -1) { 42 | return errno_ec(); 43 | } 44 | normal_file ret_fd(ret); 45 | ret_fd.set_async_context(ctx); 46 | return move(ret_fd); 47 | } 48 | 49 | public: 50 | /*! 51 | * \brief create an normal_file like calling open(2) 52 | * 53 | * \post the corresponding open(2) must return a normal file, if it returned 54 | * something else, the behavior is undefined, which often cause the later 55 | * operations to fail with ESEEK. 56 | */ 57 | static result open(string path, int flags) noexcept { 58 | return __open(nullptr, path, flags, 0755); 59 | } 60 | 61 | /*! 62 | * \brief create an normal_file like calling open(2) 63 | * 64 | * \post the corresponding open(2) must return a normal file, if it returned 65 | * something else, the behavior is undefined, which often cause the later 66 | * operations to fail with ESEEK. 67 | */ 68 | static result open(string path, int flags, 69 | mode_t mode) noexcept { 70 | return __open(nullptr, path, flags, mode); 71 | } 72 | 73 | /*! 74 | * \brief create an normal_file like calling open(2), and bind to given \ref 75 | * ::ark::async_context 76 | * 77 | * \post the corresponding open(2) must return a normal file, if it returned 78 | * something else, the behavior is undefined, which often cause the later 79 | * operations to fail with ESEEK. 80 | */ 81 | static result open(async_context &ctx, string path, 82 | int flags) noexcept { 83 | return __open(&ctx, path, flags, 0755); 84 | } 85 | 86 | /*! 87 | * \brief create an normal_file like calling open(2), and bind to given \ref 88 | * ::ark::async_context 89 | * 90 | * \post the corresponding open(2) must return a normal file, if it returned 91 | * something else, the behavior is undefined, which often cause the later 92 | * operations to fail with ESEEK. 93 | */ 94 | static result open(async_context &ctx, string path, int flags, 95 | mode_t mode) noexcept { 96 | return __open(&ctx, path, flags, mode); 97 | } 98 | 99 | /*! 100 | * \brief create an normal_file like calling mkostemp(2) 101 | */ 102 | static result mkostemp(const string &templ, int flags) noexcept { 103 | return __mkostemp(nullptr, templ, flags); 104 | } 105 | 106 | /*! 107 | * \brief create an normal_file like calling mkostemp(2), and bind to given 108 | * \ref ::ark::async_context 109 | */ 110 | static result mkostemp(async_context &ctx, const string &templ, 111 | int flags) noexcept { 112 | return __mkostemp(&ctx, templ, flags); 113 | } 114 | }; 115 | 116 | /*! @} */ 117 | 118 | } // namespace ark 119 | -------------------------------------------------------------------------------- /include/ark/general/pipe_fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ark { 9 | 10 | /*! \addtogroup general 11 | * @{ 12 | */ 13 | 14 | /*! 15 | * \brief wraps fildes created by pipe2(2) as an io object 16 | */ 17 | class pipe_fd : public fd { 18 | protected: 19 | /*! 20 | * \brief constructs from int fildes 21 | * 22 | * \param[in] fd_int must be an fildes opened by pipe2(2) 23 | */ 24 | pipe_fd(int fd_int) : fd(fd_int) {} 25 | 26 | private: 27 | static result> __create(async_context *ctx) noexcept { 28 | int pipefd[2]; 29 | int ret = clinux::pipe2(pipefd, 0); 30 | if (ret == -1) { 31 | return errno_ec(); 32 | } 33 | pipe_fd f_in{pipefd[0]}; 34 | pipe_fd f_out{pipefd[1]}; 35 | f_in.set_async_context(ctx); 36 | f_out.set_async_context(ctx); 37 | return move(make_pair(move(f_in), move(f_out))); 38 | } 39 | 40 | public: 41 | /*! 42 | * \brief construct a std::pair of pipe_fd just like calling pipe2(2) 43 | * 44 | * on success, returns the {in, out} end of the pipe as a pair 45 | */ 46 | static result> create() noexcept { 47 | return __create(nullptr); 48 | } 49 | 50 | /*! 51 | * \brief construct a std::pair of pipe_fd just like calling pipe2(2), and 52 | * bind both of them to the given \ref ::ark::async_context 53 | * 54 | * on success, returns the {in, out} end of the pipe as a pair 55 | */ 56 | static result> create(async_context &ctx) noexcept { 57 | return __create(&ctx); 58 | } 59 | }; 60 | 61 | /*! @} */ 62 | 63 | } // namespace ark 64 | -------------------------------------------------------------------------------- /include/ark/io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup io 4 | * \brief This module provides base class for io objects and corresponding read 5 | * / write functions. 6 | * 7 | * The read / write functions were provided in three flavors, under three 8 | * namespaces: 9 | * - \ref ::ark::sync 10 | * - \ref ::ark::async 11 | * - \ref ::ark::coro 12 | * 13 | * The async io functions takes an additional \ref ::ark::callback which was 14 | * invoked by \ref ::ark::async_context on completion. The coro io functions 15 | * are Awaitables, which could be used with co_await, and will be resumed in 16 | * \ref ::ark::async_context on completion. 17 | * 18 | * The io objects can be bundled with an \ref ::ark::async_context (by 19 | * inheriting \ref ::ark::with_async_context), which is necessary for usage with 20 | * async or coro variants of io functions. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef ARK_NO_COROUTINES 30 | #include 31 | #endif 32 | -------------------------------------------------------------------------------- /include/ark/io/async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ark { 12 | 13 | /*! \addtogroup io 14 | * @{ 15 | */ 16 | 17 | namespace async { 18 | 19 | /*! \cond HIDDEN_CLASSES */ 20 | 21 | // apply the right constraints for BufferType depending on IoOperation 22 | template 23 | struct io_operation_buffer_type_helper {}; 24 | 25 | template 26 | struct io_operation_buffer_type_helper { 28 | using buffer_ref_type = const MutableBufferSequence &; 29 | }; 30 | 31 | template 32 | struct io_operation_buffer_type_helper { 34 | using buffer_ref_type = const ConstBufferSequence &; 35 | }; 36 | 37 | template 39 | struct async_io_impl { 40 | using buffer_ref_type = 41 | typename io_operation_buffer_type_helper::buffer_ref_type; 43 | 44 | struct locals_t { 45 | Fd &f_; 46 | buffer_ref_type b_; 47 | CompletionCondition cond_; 48 | size_t done_sz_; 49 | vector iov_; 50 | 51 | locals_t(Fd &f, buffer_ref_type b, CompletionCondition cond) noexcept 52 | : f_(f), b_(b), cond_(cond), done_sz_(0) { 53 | auto sz = buffer_sequence_end(b) - buffer_sequence_begin(b); 54 | iov_.reserve(sz); 55 | } 56 | }; 57 | using ret_t = result; 58 | using op_t = 59 | async_op>; 60 | 61 | static void run(op_t &op) noexcept { 62 | size_t to_transfer_max = 63 | op.locals_->cond_(buffer_size(op.locals_->b_), op.locals_->done_sz_); 64 | if (!to_transfer_max) 65 | return op.complete(op.locals_->done_sz_); 66 | op.locals_->iov_.clear(); 67 | transform_to_iovecs(op.locals_->b_, op.locals_->done_sz_, to_transfer_max, 68 | back_inserter(op.locals_->iov_)); 69 | 70 | auto &ctx = op.ctx_; 71 | auto f_get = op.locals_->f_.get(); 72 | auto iov_d = op.locals_->iov_.data(); 73 | auto iov_s = op.locals_->iov_.size(); 74 | clinux::off_t off = 0; 75 | if constexpr (concepts::Seekable) { 76 | off = op.locals_->f_.offset(); 77 | } 78 | if constexpr (is_same_v) { 79 | auto ret = async_syscall::readv(ctx, f_get, iov_d, iov_s, off, 80 | op.yield_syscall(go_on)); 81 | if (!ret) 82 | op.complete(ret.error()); 83 | } else if constexpr (is_same_v) { 84 | auto ret = async_syscall::writev(ctx, f_get, iov_d, iov_s, off, 85 | op.yield_syscall(go_on)); 86 | if (!ret) 87 | op.complete(ret.error()); 88 | } 89 | } 90 | 91 | static void go_on(op_t &op, result ret) noexcept { 92 | if (!ret) { 93 | return op.complete(ret.error()); 94 | } 95 | size_t ret_sz = static_cast(ret.value()); 96 | if constexpr (is_same_v) { 97 | if (ret_sz == 0) { // eof 98 | return op.complete(op.locals_->done_sz_); 99 | } 100 | } 101 | op.locals_->done_sz_ += ret_sz; 102 | if constexpr (concepts::Seekable) { 103 | op.locals_->f_.feed(ret_sz); 104 | } 105 | run(op); 106 | } 107 | }; 108 | 109 | /*! \endcond */ 110 | 111 | /*! 112 | * \brief read from fd to buffer until eof or completion condition is met. 113 | * 114 | * returns instantly, cb is invoked on completion or error. 115 | */ 116 | template 119 | inline void read(Fd &f, const MutableBufferSequence &b, 120 | CompletionCondition cond, 121 | callback> &&cb) noexcept { 122 | using impl_t = async_io_impl; 124 | async_op(f.context(), forward>>(cb), 125 | make_unique(f, b, cond)) 126 | .run(); 127 | } 128 | 129 | /*! 130 | * \brief read from fd to buffer until eof or completion condition is met. 131 | * 132 | * returns instantly, cb is invoked on completion or error, same as read(f, b, 133 | * transfer_all(), cb). 134 | */ 135 | template 137 | inline void read(Fd &f, const MutableBufferSequence &b, 138 | callback> &&cb) noexcept { 139 | read(f, b, transfer_all(), forward>>(cb)); 140 | } 141 | 142 | /*! 143 | * \brief write to fd from buffer until completion condition is met. 144 | * 145 | * returns instantly, cb is invoked on completion or error. 146 | */ 147 | template 149 | inline void write(Fd &f, const ConstBufferSequence &b, CompletionCondition cond, 150 | callback> &&cb) noexcept { 151 | using impl_t = async_io_impl; 153 | async_op(f.context(), forward>>(cb), 154 | make_unique(f, b, cond)) 155 | .run(); 156 | } 157 | 158 | /*! 159 | * \brief write to fd from buffer until completion condition is met. 160 | * 161 | * returns instantly, cb is invoked on completion or error, same as write(f, b, 162 | * transfer_all(), cb). 163 | */ 164 | template 165 | inline void write(Fd &f, const ConstBufferSequence &b, 166 | callback> &&cb) noexcept { 167 | write(f, b, transfer_all(), forward>>(cb)); 168 | } 169 | 170 | } // namespace async 171 | 172 | /*! @} */ 173 | 174 | } // namespace ark 175 | -------------------------------------------------------------------------------- /include/ark/io/completion_condition.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ark { 6 | 7 | /*! \addtogroup io 8 | * @{ 9 | */ 10 | 11 | /*! \cond HIDDEN_CLASSES */ 12 | 13 | struct transfer_all_t { 14 | size_t operator()(size_t buffer_sz, size_t done) noexcept { 15 | return (done < buffer_sz) ? (buffer_sz - done) : 0; 16 | } 17 | }; 18 | 19 | struct transfer_at_least_t { 20 | size_t n; 21 | transfer_at_least_t(size_t n) : n(n) {} 22 | 23 | size_t operator()(size_t buffer_sz, size_t done) noexcept { 24 | return (done < min(n, buffer_sz)) ? (buffer_sz - done) : 0; 25 | } 26 | }; 27 | 28 | struct transfer_exactly_t { 29 | size_t n; 30 | transfer_exactly_t(size_t n) : n(n) {} 31 | 32 | size_t operator()(size_t buffer_sz, size_t done) noexcept { 33 | return (done < min(n, buffer_sz)) ? (min(n, buffer_sz) - done) : 0; 34 | } 35 | }; 36 | 37 | /*! \endcond */ 38 | 39 | /*! 40 | * \brief returns a \ref ::ark::concepts::CompletionCondition which denotes 41 | * transfering until all bytes in provided buffer is done. 42 | * 43 | * \remark see \ref info_network 44 | */ 45 | constexpr auto transfer_all() noexcept { return transfer_all_t{}; } 46 | 47 | /*! 48 | * \brief returns a \ref ::ark::concepts::CompletionCondition which denotes 49 | * transfering at least n bytes, or until all bytes in provided buffer is done. 50 | * 51 | * \remark see \ref info_network 52 | */ 53 | auto transfer_at_least(size_t n) noexcept { return transfer_at_least_t{n}; } 54 | 55 | /*! 56 | * \brief returns a \ref ::ark::concepts::CompletionCondition which denotes 57 | * transfering exactly n bytes, or until all bytes in provided buffer is done. 58 | * 59 | * \remark see \ref info_network 60 | */ 61 | auto transfer_exactly(size_t n) noexcept { return transfer_exactly_t{n}; } 62 | 63 | /*! @} */ 64 | 65 | } // namespace ark 66 | -------------------------------------------------------------------------------- /include/ark/io/concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | namespace concepts { 10 | 11 | /*! \addtogroup io 12 | * @{ 13 | */ 14 | 15 | /*!\class ark::concepts::Fd 16 | * \remark this is a c++20 concept 17 | * \brief denotes a type which resembles a fildes to kernel 18 | */ 19 | 20 | /*!\fn int ark::concepts::Fd::get() noexcept 21 | * \brief returns the undelying fildes 22 | */ 23 | 24 | /*! \cond CXX20_CONCEPTS */ 25 | template concept Fd = requires(T f) { 26 | { f.get() } 27 | noexcept->same_as; 28 | }; 29 | /*! \endcond */ 30 | 31 | /*!\class ark::concepts::Seekable 32 | * \remark this is a c++20 concept 33 | * \brief has a set of interface related to offsets for io operations 34 | */ 35 | 36 | /*!\fn void ark::concepts::Seekable::seek() noexcept 37 | * \brief sets the current offset 38 | */ 39 | 40 | /*!\fn void ark::concepts::Seekable::feed(off_t len) noexcept 41 | * \brief denotes an io operation succeeded with length 42 | * 43 | * often implemented as seek(offset() + len) 44 | */ 45 | 46 | /*!\fn off_t ark::concepts::Seekable::offset() noexcept 47 | * \brief returns the current offset 48 | */ 49 | 50 | /*! \cond CXX20_CONCEPTS */ 51 | template concept Seekable = requires(T u, clinux::off_t off) { 52 | u.seek(off); 53 | u.feed(off); 54 | { u.offset() } 55 | noexcept->same_as; 56 | }; 57 | /*! \endcond */ 58 | 59 | /*!\class ark::concepts::SeekableFd 60 | * \remark this is a c++20 concept 61 | * \brief a type which satisfies \ref ::ark::concepts::Seekable and \ref 62 | * ::ark::concepts::Fd 63 | */ 64 | 65 | /*! \cond CXX20_CONCEPTS */ 66 | template concept SeekableFd = Fd &&Seekable; 67 | /*! \endcond */ 68 | 69 | /*!\class ark::concepts::NonseekableFd 70 | * \remark this is a c++20 concept 71 | * \brief a type which does NOT satisfy \ref ::ark::concepts::Seekable but do 72 | * satisfy \ref ::ark::concepts::Fd 73 | */ 74 | 75 | /*! \cond CXX20_CONCEPTS */ 76 | template concept NonseekableFd = Fd && (!Seekable); 77 | /*! \endcond */ 78 | 79 | /*!\class ark::concepts::CompletionCondition 80 | * \remark this is a c++20 concept 81 | * \brief used with io operations like \ref ::ark::write to resemble where 82 | * should it stop 83 | */ 84 | 85 | /*! \cond CXX20_CONCEPTS */ 86 | template 87 | concept CompletionCondition = requires(T cond, size_t buffer_sz, size_t done) { 88 | { cond(buffer_sz, done) } 89 | noexcept->same_as; 90 | }; 91 | /*! \endcond */ 92 | 93 | /*! @} */ 94 | 95 | } // namespace concepts 96 | 97 | /*! \cond HIDDEN_CLASSES */ 98 | 99 | // overload resoilution guidance classes 100 | namespace io_operation { 101 | class read {}; 102 | class write {}; 103 | } // namespace io_operation 104 | 105 | namespace concepts { 106 | 107 | namespace internal { 108 | 109 | template 110 | concept IoOperation = 111 | is_same_v || is_same_v; 112 | 113 | } 114 | 115 | } // namespace concepts 116 | 117 | /* \endcond */ 118 | 119 | } // namespace ark 120 | -------------------------------------------------------------------------------- /include/ark/io/coro.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ark { 11 | 12 | /*! \addtogroup io 13 | * @{ 14 | */ 15 | 16 | /*! 17 | * \brief contains apis that returns an Awaitable 18 | */ 19 | namespace coro { 20 | 21 | /*! \cond HIDDEN_CLASSES */ 22 | 23 | template 26 | struct read_awaitable : public awaitable_op> { 27 | Fd &f_; 28 | const MutableBufferSequence &b_; 29 | CompletionCondition cond_; 30 | 31 | read_awaitable(Fd &f, const MutableBufferSequence &b, 32 | CompletionCondition cond) noexcept 33 | : f_(f), b_(b), cond_(cond) {} 34 | void invoke(callback> &&cb) noexcept override { 35 | async::read(f_, b_, cond_, forward>>(cb)); 36 | } 37 | }; 38 | 39 | template 41 | struct write_awaitable : public awaitable_op> { 42 | Fd &f_; 43 | const ConstBufferSequence &b_; 44 | CompletionCondition cond_; 45 | 46 | write_awaitable(Fd &f, const ConstBufferSequence &b, 47 | CompletionCondition cond) noexcept 48 | : f_(f), b_(b), cond_(cond) {} 49 | void invoke(callback> &&cb) noexcept override { 50 | async::write(f_, b_, cond_, forward>>(cb)); 51 | } 52 | }; 53 | 54 | /*! \endcond */ 55 | 56 | /*! 57 | * \brief returns an Awaitable which read from fd to buffer until eof or 58 | * completion condition is met. 59 | * 60 | * returns an Awaitable which yields an result when co_awaited. 61 | */ 62 | template 65 | inline auto read(Fd &f, const MutableBufferSequence &b, 66 | CompletionCondition cond) noexcept { 67 | return read_awaitable(f, b, cond); 68 | } 69 | 70 | /*! 71 | * \brief returns an Awaitable which read from fd to buffer until eof or 72 | * completion condition is met. 73 | * 74 | * same as read(f, b, transfer_all()) 75 | */ 76 | template 78 | inline auto read(Fd &f, const MutableBufferSequence &b) noexcept { 79 | return read_awaitable(f, b, transfer_all()); 80 | } 81 | 82 | /*! 83 | * \brief returns an Awaitable which write to fd from buffer until completion 84 | * condition is met. 85 | * 86 | * returns an Awaitable which yields an result when co_awaited. 87 | */ 88 | template 90 | inline auto write(Fd &f, const ConstBufferSequence &b, 91 | CompletionCondition cond) noexcept { 92 | return write_awaitable(f, b, cond); 93 | } 94 | 95 | /*! 96 | * \brief returns an Awaitable which write to fd from buffer until completion 97 | * condition is met. 98 | * 99 | * same as write(f, b, transfer_all()) 100 | */ 101 | template 102 | inline auto write(Fd &f, const ConstBufferSequence &b) noexcept { 103 | return write_awaitable(f, b, transfer_all()); 104 | } 105 | 106 | } // namespace coro 107 | 108 | /*! @} */ 109 | 110 | } // namespace ark 111 | -------------------------------------------------------------------------------- /include/ark/io/fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | /*! \addtogroup io 10 | * @{ 11 | */ 12 | 13 | /*! 14 | * \brief An io object, denotes a non lseek()-able fd as defined in kernel. 15 | * 16 | * \implements ::ark::concepts::Fd 17 | * \implements ::ark::concepts::NonseekableFd 18 | * 19 | * Performs an RAII-style close() only once on exit. 20 | * 21 | * Often used a s base class for varieties of fds, if your fd should be accessed 22 | * with an offset, you should use \ref ::ark::seekable_fd instead. 23 | */ 24 | class fd : public with_async_context { 25 | private: 26 | struct base_type { 27 | int fd_; 28 | 29 | base_type(int fd_int) noexcept : fd_(fd_int) {} 30 | 31 | result close() noexcept { 32 | if (clinux::close(fd_) != 0) { 33 | return errno_ec(); 34 | } 35 | return success(); 36 | } 37 | 38 | int get() const noexcept { return fd_; } 39 | 40 | ~base_type() noexcept { static_cast(this->close()); } 41 | }; 42 | unique_ptr base_; 43 | 44 | public: 45 | static constexpr bool seekable = false; 46 | 47 | /*! 48 | * \brief call close() on the fd 49 | * 50 | * if the object is empty, the operation succeeds. 51 | */ 52 | result close() noexcept { 53 | if (base_) { 54 | return base_->close(); 55 | } 56 | return success(); 57 | } 58 | 59 | /*! 60 | * \brief returns the int fildes 61 | */ 62 | int get() const noexcept { 63 | Expects(base_); 64 | return base_->get(); 65 | } 66 | 67 | protected: 68 | /*! 69 | * \brief constructs an empty fd 70 | */ 71 | fd() noexcept {} 72 | 73 | /*! 74 | * \brief constructs an fd with fildes fd_int 75 | */ 76 | fd(int fd_int) noexcept : base_(make_unique(fd_int)) {} 77 | }; 78 | 79 | /*! 80 | * \brief An io object, denotes a lseek()-able fd as defined in kernel. 81 | * 82 | * \implements ::ark::concepts::Fd 83 | * \implements ::ark::concepts::Seekable 84 | * \implements ::ark::concepts::SeekableFd 85 | * 86 | * Same as \ref ::ark::fd , stores an offset within, as the kernel offset has 87 | * race problems and undefined behaviors on error. The offset is auto increased 88 | * on completion of io operations. 89 | */ 90 | class seekable_fd : public with_async_context { 91 | private: 92 | struct base_type { 93 | int fd_; 94 | clinux::off_t offset_{0}; 95 | 96 | base_type(int fd_int) noexcept : fd_(fd_int) {} 97 | 98 | result close() noexcept { 99 | if (clinux::close(fd_) != 0) { 100 | return errno_ec(); 101 | } 102 | return success(); 103 | } 104 | 105 | int get() const noexcept { return fd_; } 106 | 107 | ~base_type() noexcept { static_cast(this->close()); } 108 | 109 | clinux::off_t offset() const noexcept { return offset_; } 110 | void offset(clinux::off_t o) noexcept { offset_ = o; } 111 | }; 112 | unique_ptr base_; 113 | 114 | public: 115 | static constexpr bool seekable = true; 116 | 117 | /*! 118 | * \brief call close() on the fd 119 | * 120 | * if the object is empty, the operation succeeds. 121 | */ 122 | result close() noexcept { 123 | if (base_) { 124 | return base_->close(); 125 | } 126 | return success(); 127 | } 128 | 129 | /*! 130 | * \brief returns the int fildes 131 | */ 132 | int get() const noexcept { 133 | Expects(base_); 134 | return base_->get(); 135 | } 136 | 137 | /*! 138 | * \brief returns the offset of next io operations 139 | */ 140 | clinux::off_t offset() const noexcept { 141 | Expects(base_); 142 | return base_->offset(); 143 | } 144 | 145 | /*! 146 | * \brief sets the offset of next io operations 147 | */ 148 | void seek(clinux::off_t off) noexcept { 149 | Expects(base_); 150 | base_->offset(off); 151 | } 152 | 153 | /*! 154 | * \brief similar to seek(offset() + rel_off) 155 | */ 156 | void feed(clinux::off_t rel_off) noexcept { 157 | Expects(base_); 158 | base_->offset(base_->offset() + rel_off); 159 | } 160 | 161 | protected: 162 | /*! 163 | * \brief constructs an empty seekable_fd 164 | */ 165 | seekable_fd() noexcept {} 166 | 167 | /*! 168 | * \brief constructs an seekable_fd with fildes fd_int and offset set to 0 169 | */ 170 | seekable_fd(int fd_int) noexcept : base_(make_unique(fd_int)) {} 171 | }; 172 | 173 | /*! @} */ 174 | 175 | } // namespace ark 176 | -------------------------------------------------------------------------------- /include/ark/io/iovecs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace ark { 10 | 11 | inline clinux::iovec to_iovec(const const_buffer &buf) noexcept { 12 | return clinux::iovec{.iov_base = 13 | static_cast(const_cast(buf.data())), 14 | .iov_len = buf.size()}; 15 | } 16 | 17 | template 18 | inline OutputIt transform_to_iovecs(const ConstBufferSequence &bseq, 19 | size_t skip, size_t max_len, 20 | OutputIt d_it) noexcept { 21 | for (auto it = buffer_sequence_begin(bseq); it != buffer_sequence_end(bseq); 22 | ++it) { 23 | const_buffer b{*it}; 24 | if (b.size() <= skip) { 25 | skip -= b.size(); 26 | continue; 27 | } 28 | b += skip; 29 | skip = 0; 30 | 31 | if (max_len < b.size()) { 32 | b = buffer(b, max_len); 33 | } 34 | 35 | d_it = to_iovec(b); 36 | d_it++; 37 | max_len -= b.size(); 38 | if (max_len == 0) { 39 | break; 40 | } 41 | } 42 | return d_it; 43 | } 44 | 45 | template 46 | inline auto to_iovecs(const ConstBufferSequence &bseq, size_t skip, 47 | size_t max_len) noexcept { 48 | vector ret; 49 | ret.reserve(bseq.size()); 50 | transform_to_iovecs(bseq, skip, max_len, back_inserter(ret)); 51 | return move(ret); 52 | } 53 | } // namespace ark 54 | 55 | /*! \endcond */ 56 | -------------------------------------------------------------------------------- /include/ark/io/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ark { 12 | 13 | /*! \addtogroup io 14 | * @{ 15 | */ 16 | 17 | namespace sync { 18 | 19 | /*! 20 | * \brief read from fd to buffer until eof or completion condition is met. 21 | * 22 | * blocks until complete or error. 23 | */ 24 | template 27 | inline result read(Fd &f, const MutableBufferSequence &b, 28 | CompletionCondition cond) { 29 | size_t done_sz = 0; 30 | 31 | while (size_t to_transfer_max = cond(buffer_size(b), done_sz)) { 32 | auto iov = to_iovecs(b, done_sz, to_transfer_max); 33 | ssize_t syscall_ret; 34 | if constexpr (concepts::Seekable) { 35 | syscall_ret = 36 | clinux::preadv2(f.get(), iov.data(), iov.size(), f.offset(), 0); 37 | } else { 38 | syscall_ret = clinux::readv(f.get(), iov.data(), iov.size()); 39 | } 40 | if (syscall_ret == -1) { 41 | return errno_ec(); 42 | } else if (syscall_ret == 0) { // eof 43 | return done_sz; 44 | } else { 45 | if constexpr (concepts::Seekable) { 46 | f.feed(syscall_ret); 47 | } 48 | done_sz += syscall_ret; 49 | } 50 | } 51 | 52 | return done_sz; 53 | } 54 | 55 | /*! 56 | * \brief read from fd to buffer until eof or completion condition is met. 57 | * 58 | * blocks until complete or error, same as read(f, b, transfer_all()). 59 | */ 60 | template 62 | inline result read(Fd &f, const MutableBufferSequence &b) { 63 | return read(f, b, transfer_all()); 64 | } 65 | 66 | /*! 67 | * \brief write to fd from buffer until completion condition is met. 68 | * 69 | * blocks until complete or error. 70 | */ 71 | template 73 | inline result write(Fd &f, const ConstBufferSequence &b, 74 | CompletionCondition cond) { 75 | size_t done_sz = 0; 76 | 77 | while (size_t to_transfer_max = cond(buffer_size(b), done_sz)) { 78 | auto iov = to_iovecs(b, done_sz, to_transfer_max); 79 | ssize_t syscall_ret; 80 | if constexpr (concepts::Seekable) { 81 | syscall_ret = 82 | clinux::pwritev2(f.get(), iov.data(), iov.size(), f.offset(), 0); 83 | } else { 84 | syscall_ret = clinux::writev(f.get(), iov.data(), iov.size()); 85 | } 86 | if (syscall_ret == -1) { 87 | return errno_ec(); 88 | } else { 89 | if constexpr (concepts::Seekable) { 90 | f.feed(syscall_ret); 91 | } 92 | done_sz += syscall_ret; 93 | } 94 | } 95 | 96 | return done_sz; 97 | } 98 | 99 | /*! 100 | * \brief write to fd from buffer until completion condition is met. 101 | * 102 | * blocks until complete or error, same as write(f, b, transfer_all()). 103 | */ 104 | template 105 | inline result write(Fd &f, const ConstBufferSequence &b) { 106 | return write(f, b, transfer_all()); 107 | } 108 | 109 | } // namespace sync 110 | 111 | /*! @} */ 112 | 113 | } // namespace ark 114 | -------------------------------------------------------------------------------- /include/ark/misc/concepts_polyfill.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ark { 6 | 7 | namespace concepts { 8 | 9 | namespace detail { 10 | template concept SameHelper = std::is_same_v; 11 | } 12 | 13 | template 14 | concept same_as = detail::SameHelper &&detail::SameHelper; 15 | 16 | template 17 | concept convertible_to = std::is_convertible_v &&requires( 18 | std::add_rvalue_reference_t (&f)()) { 19 | static_cast(f()); 20 | }; 21 | 22 | } // namespace concepts 23 | 24 | } // namespace ark 25 | -------------------------------------------------------------------------------- /include/ark/misc/context_exit_guard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ark { 7 | 8 | /*! \addtogroup misc 9 | * @{ 10 | */ 11 | 12 | /*! 13 | * \brief RAII-style helper for async_context.exit() 14 | * 15 | * call async_context.exit() on destruct, often used in coroutine main function 16 | * 17 | * could use with co_async to accomplish something like well-known sync_wait for 18 | * awaitables in coroutines ts 19 | */ 20 | class context_exit_guard { 21 | private: 22 | async_context &ctx_; 23 | 24 | public: 25 | context_exit_guard(async_context &ctx) : ctx_(ctx) {} 26 | 27 | ~context_exit_guard() { ctx_.exit(); } 28 | }; 29 | 30 | /*! @} */ 31 | 32 | } // namespace ark 33 | -------------------------------------------------------------------------------- /include/ark/misc/manual_lifetime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | 7 | namespace ark { 8 | 9 | template struct manual_lifetime { 10 | public: 11 | manual_lifetime() noexcept {} 12 | ~manual_lifetime() noexcept {} 13 | 14 | template void construct(Args &&...args) noexcept { 15 | ::new (static_cast(addressof(value))) 16 | T(static_cast(args)...); 17 | } 18 | 19 | void destruct() noexcept { value.~T(); } 20 | 21 | T &get() & { return value; } 22 | const T &get() const & { return value; } 23 | T &&get() && { return (T &&) value; } 24 | const T &&get() const && { return (const T &&)value; } 25 | 26 | private: 27 | union { 28 | T value; 29 | }; 30 | }; 31 | 32 | template struct manual_lifetime { 33 | manual_lifetime() noexcept : ptr(nullptr) {} 34 | 35 | void construct(T &value) noexcept { ptr = addressof(value); } 36 | void destruct() noexcept { ptr = nullptr; } 37 | 38 | T &get() const noexcept { return *ptr; } 39 | 40 | private: 41 | T *ptr; 42 | }; 43 | 44 | template struct manual_lifetime { 45 | manual_lifetime() noexcept : ptr(nullptr) {} 46 | 47 | void construct(T &&value) noexcept { ptr = addressof(value); } 48 | void destruct() noexcept { ptr = nullptr; } 49 | 50 | T &&get() const noexcept { return *ptr; } 51 | 52 | private: 53 | T *ptr; 54 | }; 55 | 56 | template <> struct manual_lifetime { 57 | void construct() noexcept {} 58 | void destruct() noexcept {} 59 | void get() const noexcept {} 60 | }; 61 | 62 | } // namespace ark 63 | 64 | /*! \endcond */ 65 | -------------------------------------------------------------------------------- /include/ark/misc/test_r.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \cond FILE_NOT_DOCUMENTED */ 4 | 5 | #include 6 | #include 7 | 8 | #define TEST_R(_x_suite, _x_name) \ 9 | ark::result _x_suite##_##_x_name##_body(); \ 10 | \ 11 | TEST(_x_suite, _x_name) { \ 12 | ark::result ret = _x_suite##_##_x_name##_body(); \ 13 | if (ret.has_error()) { \ 14 | ASSERT_FALSE(ret.has_error()) \ 15 | << "error returned from TEST_R : " << ret.error().message(); \ 16 | } \ 17 | } \ 18 | \ 19 | ark::result _x_suite##_##_x_name##_body() 20 | 21 | /*! \endcond */ 22 | -------------------------------------------------------------------------------- /include/ark/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*! \addtogroup net 4 | * \brief This module implements network related stuff. 5 | * 6 | * Due to the complexity of network stack, the common stuff, like network 7 | * address, is placed under namespace \ref ::ark::net, while specific protocol 8 | * implementions placed under sub-namespaces, like \ref ::ark::net::tcp. 9 | */ 10 | 11 | namespace ark { 12 | /*! 13 | * \brief namespace for module \ref net 14 | */ 15 | namespace net {} 16 | } // namespace ark 17 | 18 | #include 19 | 20 | #include 21 | -------------------------------------------------------------------------------- /include/ark/net/address.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ark { 6 | namespace net { 7 | 8 | /*! \addtogroup net 9 | * @{ 10 | */ 11 | 12 | /*! 13 | * \brief a network address, of unspecified address family 14 | */ 15 | class address { 16 | protected: 17 | clinux::sockaddr_storage sa_; 18 | 19 | public: 20 | /*! 21 | * \brief create an address object, able to hold a network address 22 | * 23 | * sets sa_family to AF_UNSPEC 24 | */ 25 | address() { sa_ptr()->sa_family = AF_UNSPEC; } 26 | 27 | /*! 28 | * \brief returns the address family 29 | */ 30 | clinux::sa_family_t sa_family() const noexcept { return sa_ptr()->sa_family; } 31 | 32 | /*! 33 | * \brief returns a pointer to the underlying sockaddr 34 | */ 35 | clinux::sockaddr *sa_ptr() noexcept { 36 | return reinterpret_cast(addressof(sa_)); 37 | } 38 | 39 | /*! 40 | * \brief returns a pointer to the underlying sockaddr 41 | */ 42 | const clinux::sockaddr *sa_ptr() const noexcept { 43 | return reinterpret_cast(addressof(sa_)); 44 | } 45 | 46 | /*! 47 | * \brief always return size of sockaddr_storage 48 | */ 49 | clinux::socklen_t sa_len() const noexcept { return sizeof(sa_); } 50 | }; 51 | 52 | /*! 53 | * \brief Provides common members and types for network address of specific 54 | * address family 55 | */ 56 | template 57 | class address_with_family : public address { 58 | public: 59 | using size_type = clinux::socklen_t; 60 | static const clinux::sa_family_t address_family = AddrFamily; 61 | static const clinux::socklen_t size = sizeof(SockAddr); 62 | using sockaddr_ptr_t = add_pointer_t; 63 | using const_sockaddr_ptr_t = add_pointer_t>; 64 | 65 | address_with_family() : address() { 66 | address::sa_ptr()->sa_family = address_family; 67 | } 68 | address_with_family(const address &addr) : address(addr) { 69 | Expects(address::sa_ptr()->sa_family == address_family); 70 | } 71 | 72 | /*! 73 | * \brief returns a pointer to the underlying sockaddr 74 | */ 75 | sockaddr_ptr_t sa_ptr() noexcept { 76 | return reinterpret_cast(address::sa_ptr()); 77 | } 78 | 79 | /*! 80 | * \brief returns a pointer to the underlying sockaddr 81 | */ 82 | const_sockaddr_ptr_t sa_ptr() const noexcept { 83 | return reinterpret_cast(address::sa_ptr()); 84 | } 85 | 86 | /*! 87 | * \brief returns size of the corresponding sockaddr structure 88 | */ 89 | size_type sa_len() const noexcept { return size; } 90 | 91 | /*! 92 | * \brief erase specific address family bound type info, upcast to an \ref 93 | * ::ark::net::address 94 | */ 95 | address to_address() noexcept { return *(this); } 96 | }; 97 | 98 | /*! 99 | * \brief denotes an IPV4 network address 100 | */ 101 | class inet_address : public address_with_family { 102 | private: 103 | using base_type = address_with_family; 104 | 105 | public: 106 | using base_type::address_family; 107 | using base_type::base_type; 108 | using base_type::const_sockaddr_ptr_t; 109 | using base_type::size; 110 | using base_type::size_type; 111 | using base_type::sockaddr_ptr_t; 112 | 113 | /*! 114 | * \brief returns the string representation of the host 115 | * 116 | * error if the host is invalid 117 | */ 118 | result host() const noexcept { 119 | char buff[16]; 120 | const char *s = clinux::inet_ntop(AF_INET, &(base_type::sa_ptr()->sin_addr), 121 | buff, sizeof(buff)); 122 | if (s == nullptr) { 123 | return errno_ec(); 124 | } 125 | return {s}; 126 | } 127 | 128 | /*! 129 | * \brief sets the host 130 | * 131 | * error if the given string is invalid 132 | * 133 | * \param[in] host_s ipv4 string representation, like '127.0.0.1' 134 | */ 135 | result host(string host_s) noexcept { 136 | int ret = clinux::inet_pton(address_family, host_s.c_str(), 137 | &(base_type::sa_ptr()->sin_addr)); 138 | if (ret <= 0) 139 | return as_ec(EINVAL); 140 | return success(); 141 | } 142 | 143 | /*! 144 | * \brief gets the port 145 | */ 146 | unsigned short port() const noexcept { 147 | return clinux::ntohs(base_type::sa_ptr()->sin_port); 148 | } 149 | 150 | /*! 151 | * \brief sets the port 152 | */ 153 | void port(unsigned short p) noexcept { 154 | base_type::sa_ptr()->sin_port = clinux::htons(p); 155 | } 156 | 157 | /*! 158 | * \brief convert address to string 159 | * 160 | * format is 'host:port', like '127.0.0.1:8080' 161 | * 162 | * error if the address is invalid 163 | */ 164 | result to_string() const noexcept { 165 | ostringstream oss; 166 | OUTCOME_TRY(host_s, host()); 167 | oss << host_s << ":" << port(); 168 | return oss.str(); 169 | } 170 | 171 | /*! 172 | * \brief downcast from an address 173 | * 174 | * error if the address family does not match 175 | */ 176 | static result from_address(address addr) noexcept { 177 | if (addr.sa_ptr()->sa_family != address_family) 178 | return as_ec(EAFNOSUPPORT); 179 | return inet_address{addr}; 180 | } 181 | }; 182 | 183 | /*! 184 | * \brief denotes an IPV6 network address 185 | */ 186 | class inet6_address 187 | : public address_with_family { 188 | private: 189 | using base_type = address_with_family; 190 | 191 | public: 192 | using base_type::address_family; 193 | using base_type::const_sockaddr_ptr_t; 194 | using base_type::size; 195 | using base_type::size_type; 196 | using base_type::sockaddr_ptr_t; 197 | 198 | /*! 199 | * \brief returns the string representation of the host 200 | * 201 | * error if the host is invalid 202 | */ 203 | result host() const noexcept { 204 | char buff[64]; 205 | const char *s = clinux::inet_ntop( 206 | AF_INET6, &(base_type::sa_ptr()->sin6_addr), buff, sizeof(buff)); 207 | if (s == nullptr) { 208 | return errno_ec(); 209 | } 210 | return {s}; 211 | } 212 | 213 | /*! 214 | * \brief sets the host 215 | * 216 | * error if the given string is invalid 217 | * 218 | * \param[in] host_s ipv6 string representation, like '::1' 219 | */ 220 | result host(string host_s) noexcept { 221 | int ret = clinux::inet_pton(address_family, host_s.c_str(), 222 | &(base_type::sa_ptr()->sin6_addr)); 223 | if (ret <= 0) 224 | return as_ec(EINVAL); 225 | return success(); 226 | } 227 | 228 | /*! 229 | * \brief gets the port 230 | */ 231 | unsigned short port() const noexcept { 232 | return clinux::ntohs(base_type::sa_ptr()->sin6_port); 233 | } 234 | 235 | /*! 236 | * \brief sets the port 237 | */ 238 | void port(unsigned short p) noexcept { 239 | base_type::sa_ptr()->sin6_port = clinux::htons(p); 240 | } 241 | 242 | /*! 243 | * \brief convert address to string 244 | * 245 | * format is '[host]:port', like '[::1]:8080' 246 | * 247 | * error if the address is invalid 248 | */ 249 | result to_string() const noexcept { 250 | ostringstream oss; 251 | OUTCOME_TRY(host_s, host()); 252 | oss << "[" << host_s << "]:" << port(); 253 | return oss.str(); 254 | } 255 | 256 | /*! 257 | * \brief downcast from an address 258 | * 259 | * error if the address family does not match 260 | */ 261 | static result from_address(address addr) noexcept { 262 | if (addr.sa_ptr()->sa_family != address_family) 263 | return as_ec(EAFNOSUPPORT); 264 | return inet6_address{addr}; 265 | } 266 | }; 267 | 268 | /*! 269 | * \brief ADL enabled to_string visitor of \ref ::ark::net::address 270 | * 271 | * convert the address to its string representation 272 | * 273 | * error if the address family is unsupported, or the address is invalid 274 | */ 275 | result to_string(const address &addr) { 276 | if (addr.sa_family() == AF_INET) { 277 | OUTCOME_TRY(ret, inet_address::from_address(addr)); 278 | return ret.to_string(); 279 | } else if (addr.sa_family() == AF_INET6) { 280 | OUTCOME_TRY(ret, inet6_address::from_address(addr)); 281 | return ret.to_string(); 282 | } 283 | return as_ec(EAFNOSUPPORT); 284 | } 285 | 286 | /*! @} */ 287 | 288 | } // namespace net 289 | } // namespace ark 290 | -------------------------------------------------------------------------------- /include/ark/net/tcp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ark { 4 | namespace net { 5 | /*! 6 | * \brief implements TCP/IP related classes and functions 7 | */ 8 | namespace tcp {} 9 | } // namespace net 10 | } // namespace ark 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #ifndef ARK_NO_COROUTINES 20 | #include 21 | #endif 22 | -------------------------------------------------------------------------------- /include/ark/net/tcp/acceptor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | namespace net { 9 | 10 | /*! \addtogroup net 11 | * @{ 12 | */ 13 | 14 | namespace tcp { 15 | 16 | /*! 17 | * \brief special io object available to bond, listen, and accept \ref 18 | * ark::net::tcp::socket from 19 | */ 20 | class acceptor : public fd { 21 | protected: 22 | /*! 23 | * \brief constructs from int fildes 24 | * 25 | * \param[in] fd_int must be an fildes opened by socket(2) 26 | */ 27 | acceptor(int fd_int) : fd(fd_int) {} 28 | 29 | private: 30 | static result __create(async_context *ctx, 31 | bool use_ipv6 = false) noexcept { 32 | int ret = clinux::socket(use_ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); 33 | if (ret == -1) { 34 | return errno_ec(); 35 | } 36 | acceptor ret_fd(ret); 37 | ret_fd.set_async_context(ctx); 38 | return move(ret_fd); 39 | } 40 | 41 | public: 42 | /*! 43 | * \brief constructs an acceptor 44 | * 45 | * \param[in] use_ipv6 if set to true, associated bond and accept calls should 46 | * use \ref ark::net::inet6_address 47 | */ 48 | static result create(bool use_ipv6 = false) noexcept { 49 | return __create(nullptr, use_ipv6); 50 | } 51 | 52 | /*! 53 | * \brief constructs an acceptor 54 | * 55 | * \param[in] ctx bound to this \ref ark::async_context in addition, notice 56 | * that it would also be bound for accepted sockets \param[in] use_ipv6 if set 57 | * to true, associated bond and accept calls should use \ref 58 | * ark::net::inet6_address 59 | */ 60 | static result create(async_context &ctx, 61 | bool use_ipv6 = false) noexcept { 62 | return __create(&ctx, use_ipv6); 63 | } 64 | }; 65 | } // namespace tcp 66 | 67 | /*! @} */ 68 | 69 | } // namespace net 70 | } // namespace ark 71 | -------------------------------------------------------------------------------- /include/ark/net/tcp/async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ark { 12 | namespace net { 13 | 14 | /*! \addtogroup net 15 | * @{ 16 | */ 17 | 18 | namespace tcp { 19 | 20 | /*! 21 | * \brief contains apis that invokes a given \ref ::ark::callback on completion 22 | */ 23 | namespace async { 24 | 25 | /*! 26 | * \brief connect socket to the given endpoint 27 | * 28 | * returns instantly, cb is invoked on completion or error. 29 | */ 30 | inline void connect(socket &f, const address &endpoint, 31 | callback> &&cb) noexcept { 32 | auto ret = async_syscall::connect( 33 | f.context(), f.get(), endpoint.sa_ptr(), endpoint.sa_len(), 34 | [cb(forward>>(cb))](result ret) mutable { 35 | if (!ret) 36 | cb(ret.error()); 37 | cb(success()); 38 | }); 39 | if (ret.has_error()) 40 | cb(ret.as_failure()); 41 | } 42 | 43 | /*! \cond HIDDEN_CLASSES */ 44 | 45 | struct accept_with_address_impl { 46 | struct locals_t { 47 | acceptor &f_; 48 | address &endpoint_; 49 | 50 | clinux::socklen_t addrlen_buf; 51 | 52 | locals_t(acceptor &f, address &endpoint) 53 | : f_(f), endpoint_(endpoint), addrlen_buf{endpoint_.sa_len()} {} 54 | }; 55 | using ret_t = result; 56 | using op_t = async_op; 57 | 58 | static void run(op_t &op) noexcept { 59 | auto &ctx = op.ctx_; 60 | auto fd = op.locals_->f_.get(); 61 | auto sa_ptr = op.locals_->endpoint_.sa_ptr(); 62 | auto addr_ptr = addressof(op.locals_->addrlen_buf); 63 | auto ret = async_syscall::accept( 64 | ctx, fd, sa_ptr, addr_ptr, 0, 65 | op.yield_syscall(accept_with_address_impl::finish)); 66 | if (ret.has_error()) 67 | return op.complete(ret.as_failure()); 68 | } 69 | 70 | static void finish(op_t &op, result ret) noexcept { 71 | if (!ret) { 72 | op.complete(ret.error()); 73 | } 74 | op.complete(wrap_accepted_socket(&op.ctx_, static_cast(ret.value()))); 75 | } 76 | }; 77 | 78 | /*! \endcond */ 79 | 80 | /*! 81 | * \brief accept a socket connection from the given acceptor 82 | * 83 | * returns instantly, cb is invoked on completion or error. 84 | * 85 | * \param[out] endpoint the address of accepted socket, on success 86 | */ 87 | inline void accept(acceptor &srv, address &endpoint, 88 | callback> &&cb) noexcept { 89 | using impl_t = accept_with_address_impl; 90 | async_op(srv.context(), forward>>(cb), 91 | make_unique(srv, endpoint)) 92 | .run(); 93 | } 94 | 95 | /*! 96 | * \brief accept a socket connection from the given acceptor 97 | * 98 | * returns instantly, cb is invoked on completion or error. 99 | */ 100 | inline void accept(acceptor &srv, callback> &&cb) noexcept { 101 | auto ret = async_syscall::accept( 102 | srv.context(), srv.get(), NULL, NULL, 0, 103 | [&ctx(srv.context()), 104 | cb(forward>>(cb))](result ret) mutable { 105 | if (!ret) 106 | cb(ret.error()); 107 | cb(wrap_accepted_socket(&ctx, static_cast(ret.value()))); 108 | }); 109 | if (ret.has_error()) 110 | cb(ret.as_failure()); 111 | } 112 | 113 | } // namespace async 114 | } // namespace tcp 115 | 116 | /*! @} */ 117 | 118 | } // namespace net 119 | } // namespace ark 120 | -------------------------------------------------------------------------------- /include/ark/net/tcp/coro.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace ark { 12 | namespace net { 13 | 14 | /*! \addtogroup net 15 | * @{ 16 | */ 17 | 18 | namespace tcp { 19 | 20 | /*! 21 | * \brief contains apis that returns an Awaitable 22 | */ 23 | namespace coro { 24 | 25 | /*! \cond HIDDEN_CLASSES */ 26 | 27 | struct connect_awaitable : public awaitable_op> { 28 | socket &f_; 29 | const address &endpoint_; 30 | 31 | connect_awaitable(socket &f, const address &endpoint) noexcept 32 | : f_(f), endpoint_(endpoint) {} 33 | 34 | void invoke(callback> &&cb) noexcept override { 35 | async::connect(f_, endpoint_, forward>>(cb)); 36 | } 37 | }; 38 | 39 | /*! \endcond */ 40 | 41 | /*! 42 | * \brief connect socket to the given endpoint 43 | * 44 | * returns an Awaitable which yields an result when co_awaited. 45 | */ 46 | inline auto connect(socket &f, const address &endpoint) noexcept { 47 | return connect_awaitable(f, endpoint); 48 | } 49 | 50 | /*! \cond HIDDEN_CLASSES */ 51 | 52 | struct accept_with_ep_awaitable : public awaitable_op> { 53 | acceptor &srv_; 54 | address &endpoint_; 55 | 56 | accept_with_ep_awaitable(acceptor &srv, address &endpoint) noexcept 57 | : srv_(srv), endpoint_(endpoint) {} 58 | 59 | void invoke(callback> &&cb) noexcept override { 60 | async::accept(srv_, endpoint_, forward>>(cb)); 61 | } 62 | }; 63 | 64 | struct accept_awaitable : public awaitable_op> { 65 | acceptor &srv_; 66 | 67 | accept_awaitable(acceptor &srv) noexcept : srv_(srv) {} 68 | 69 | void invoke(callback> &&cb) noexcept override { 70 | async::accept(srv_, forward>>(cb)); 71 | } 72 | }; 73 | 74 | /*! \endcond */ 75 | 76 | /*! 77 | * \brief accept a socket connection from the given acceptor 78 | * 79 | * returns an Awaitable which yields an result when co_awaited. 80 | * 81 | * \param[out] endpoint the address of accepted socket, on success 82 | */ 83 | inline auto accept(acceptor &srv, address &endpoint) noexcept { 84 | return accept_with_ep_awaitable(srv, endpoint); 85 | } 86 | 87 | /*! 88 | * \brief accept a socket connection from the given acceptor 89 | * 90 | * returns an Awaitable which yields an result when co_awaited. 91 | */ 92 | inline auto accept(acceptor &srv) noexcept { return accept_awaitable(srv); } 93 | 94 | } // namespace coro 95 | } // namespace tcp 96 | 97 | /*! @} */ 98 | 99 | } // namespace net 100 | } // namespace ark 101 | -------------------------------------------------------------------------------- /include/ark/net/tcp/general.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ark { 10 | namespace net { 11 | 12 | /*! \addtogroup net 13 | * @{ 14 | */ 15 | 16 | namespace tcp { 17 | 18 | /*! 19 | * \brief bind acceptor to the given endpoint 20 | * 21 | * see bind(2) 22 | * 23 | * won't block 24 | */ 25 | inline result bind(acceptor &f, const address &endpoint) noexcept { 26 | int ret = clinux::bind(f.get(), endpoint.sa_ptr(), endpoint.sa_len()); 27 | if (ret == -1) { 28 | return errno_ec(); 29 | } 30 | return success(); 31 | } 32 | 33 | /*! 34 | * \brief make acceptor start to accept connections 35 | * 36 | * see listen(2) 37 | * 38 | * won't block 39 | */ 40 | inline result listen(acceptor &f, 41 | int backlog = numeric_limits::max()) noexcept { 42 | int ret = clinux::listen(f.get(), backlog); 43 | if (ret == -1) { 44 | return errno_ec(); 45 | } 46 | return success(); 47 | } 48 | 49 | } // namespace tcp 50 | 51 | /*! @} */ 52 | 53 | } // namespace net 54 | } // namespace ark 55 | -------------------------------------------------------------------------------- /include/ark/net/tcp/socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ark { 8 | namespace net { 9 | 10 | /*! \addtogroup net 11 | * @{ 12 | */ 13 | 14 | namespace tcp { 15 | 16 | /*! \cond SOCKET_WRAP_INTERNALS */ 17 | 18 | class socket; 19 | 20 | inline socket wrap_accepted_socket(async_context *ctx, int fd) noexcept; 21 | 22 | /*! \endcond */ 23 | 24 | /*! 25 | * \brief denotes a tcp socket 26 | * 27 | * will be available for io after a successful connect operation, sync or async, 28 | * or if the socket is retrieved by accepting from an \ref 29 | * ::ark::net::tcp::acceptor 30 | */ 31 | class socket : public fd { 32 | protected: 33 | /*! 34 | * \brief constructs from int fildes 35 | * 36 | * \param[in] fd_int must be an fildes opened by socket(2) 37 | */ 38 | socket(int fd_int) : fd(fd_int) {} 39 | 40 | private: 41 | static result __create(async_context *ctx, 42 | bool use_ipv6 = false) noexcept { 43 | int ret = clinux::socket(use_ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); 44 | if (ret == -1) { 45 | return errno_ec(); 46 | } 47 | socket ret_fd(ret); 48 | ret_fd.set_async_context(ctx); 49 | return move(ret_fd); 50 | } 51 | 52 | public: 53 | /*! \cond SOCKET_WRAP_INTERNALS */ 54 | friend inline socket wrap_accepted_socket(async_context *ctx, 55 | int fd) noexcept; 56 | /*! \endcond */ 57 | 58 | /*! 59 | * \brief constructs a socket available for connecting 60 | * 61 | * \param[in] use_ipv6 if set to true, associated connect should use \ref 62 | * ark::net::inet6_address 63 | */ 64 | static result create(bool use_ipv6 = false) noexcept { 65 | return __create(nullptr, use_ipv6); 66 | } 67 | 68 | /*! 69 | * \brief constructs a socket available for connecting 70 | * 71 | * \param[in] ctx bound to this \ref ark::async_context in addition, notice 72 | * that it would also be bound for accepted sockets \param[in] use_ipv6 if set 73 | * to true, associated connect should use \ref ark::net::inet6_address 74 | */ 75 | static result create(async_context &ctx, 76 | bool use_ipv6 = false) noexcept { 77 | return __create(&ctx, use_ipv6); 78 | } 79 | }; 80 | 81 | /*! \cond SOCKET_WRAP_INTERNALS */ 82 | 83 | inline socket wrap_accepted_socket(async_context *ctx, int fd) noexcept { 84 | socket ret(fd); 85 | ret.set_async_context(ctx); 86 | return move(ret); 87 | } 88 | 89 | /*! \endcond */ 90 | } // namespace tcp 91 | 92 | /*! @} */ 93 | 94 | } // namespace net 95 | } // namespace ark 96 | -------------------------------------------------------------------------------- /include/ark/net/tcp/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ark { 10 | namespace net { 11 | 12 | /*! \addtogroup net 13 | * @{ 14 | */ 15 | 16 | namespace tcp { 17 | 18 | /*! 19 | * \brief contains apis that blocks until completion 20 | */ 21 | namespace sync { 22 | 23 | /*! 24 | * \brief connect socket to the given endpoint 25 | * 26 | * blocks until the operation is complete 27 | */ 28 | inline result connect(socket &f, const address &endpoint) noexcept { 29 | int ret = clinux::connect(f.get(), endpoint.sa_ptr(), endpoint.sa_len()); 30 | if (ret == -1) { 31 | return errno_ec(); 32 | } 33 | return success(); 34 | } 35 | 36 | /*! 37 | * \brief accept a socket connection from the given acceptor 38 | * 39 | * blocks until the operation is complete 40 | */ 41 | inline result accept(acceptor &srv) noexcept { 42 | int ret = clinux::accept4(srv.get(), NULL, NULL, 0); 43 | if (ret == -1) { 44 | return errno_ec(); 45 | } 46 | return wrap_accepted_socket(nullptr, ret); 47 | } 48 | 49 | /*! 50 | * \brief accept a socket connection from the given acceptor 51 | * 52 | * blocks until the operation is complete 53 | * 54 | * \param[out] endpoint the address of accepted socket, on success 55 | */ 56 | inline result accept(acceptor &srv, address &endpoint) noexcept { 57 | clinux::socklen_t addrlen_buf = endpoint.sa_len(); 58 | int ret = 59 | clinux::accept4(srv.get(), endpoint.sa_ptr(), addressof(addrlen_buf), 0); 60 | if (ret == -1) { 61 | return errno_ec(); 62 | } 63 | return wrap_accepted_socket(nullptr, ret); 64 | } 65 | } // namespace sync 66 | } // namespace tcp 67 | 68 | /*! @} */ 69 | 70 | } // namespace net 71 | } // namespace ark 72 | -------------------------------------------------------------------------------- /scripts/generate_doc.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONDONTWRITEBYTECODE=1 4 | export CLANG_RESOURCE_DIR=$(clang -print-resource-dir) 5 | ./vendor/m.css/documentation/doxygen.py ./conf.py 6 | rm -rf ./docs 7 | mv ./doxy/html ./docs 8 | rm -rf ./doxy 9 | 10 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | add_subdirectory(${CMAKE_SOURCE_DIR}/vendor/googletest googletest) 4 | 5 | include(GoogleTest) 6 | 7 | set(TEST_SRCS 8 | test_net_address.cpp;test_general.cpp) 9 | 10 | foreach(test_src IN ITEMS ${TEST_SRCS}) 11 | get_filename_component(test_target ${test_src} NAME_WE) 12 | add_executable(${test_target} ${test_src}) 13 | target_link_libraries(${test_target} PUBLIC gtest gtest_main arkio) 14 | 15 | gtest_discover_tests(${test_target}) 16 | endforeach() 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/include/test_r.hpp: -------------------------------------------------------------------------------- 1 | 2 | #define TEST_R(_x_suite, _x_name) \ 3 | ark::result _x_suite##_##_x_name##_body(); \ 4 | \ 5 | TEST(_x_suite, _x_name) { \ 6 | auto ret = _x_suite##_##_x_name##_body(); \ 7 | ASSERT_FALSE(ret.has_error()) << "ark::result returning test returned error: " << ret.error().message(); \ 8 | } \ 9 | \ 10 | ark::result _x_suite##_##_x_name##_body() 11 | 12 | -------------------------------------------------------------------------------- /tests/test_general.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | using ark::success; 14 | 15 | TEST_R(general, event_fd) { 16 | using ark::buffer; 17 | using ark::event_fd; 18 | namespace sync = ark::sync; 19 | 20 | uint64_t data[1] = {1}; 21 | OUTCOME_TRY(fd, event_fd::create(0, 0)); 22 | OUTCOME_TRY(sync::write(fd, buffer(data))); 23 | uint64_t rd_buf[1]; 24 | OUTCOME_TRY(sync::read(fd, buffer(rd_buf))); 25 | return success(); 26 | } 27 | 28 | TEST_R(general, mem_fd) { 29 | using ark::buffer; 30 | using ark::mem_fd; 31 | namespace sync = ark::sync; 32 | 33 | std::string data = "hello"; 34 | OUTCOME_TRY(fd, mem_fd::create("test_mfd", 0)); 35 | OUTCOME_TRY(sync::write(fd, buffer(data))); 36 | fd.seek(0); 37 | std::array rd_buf; 38 | OUTCOME_TRY(sync::read(fd, buffer(rd_buf))); 39 | std::string_view got_s{rd_buf.data(), rd_buf.size()}; 40 | EXPECT_EQ(got_s, data); 41 | return success(); 42 | } 43 | 44 | TEST_R(general, pipe_fd) { 45 | using ark::buffer; 46 | using ark::pipe_fd; 47 | namespace sync = ark::sync; 48 | 49 | std::string data = "hello"; 50 | OUTCOME_TRY(ends, pipe_fd::create()); 51 | OUTCOME_TRY(sync::write(ends.second, buffer(data))); 52 | std::array rd_buf; 53 | OUTCOME_TRY(sync::read(ends.first, buffer(rd_buf))); 54 | std::string_view got_s{rd_buf.data(), rd_buf.size()}; 55 | EXPECT_EQ(got_s, data); 56 | return success(); 57 | } 58 | -------------------------------------------------------------------------------- /tests/test_net_address.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace ark; 7 | 8 | TEST_R(net_address, ipv4) { 9 | net::inet_address addr; 10 | OUTCOME_TRY(addr.host("127.0.0.1")); 11 | addr.port(8080); 12 | OUTCOME_TRY(got_host, addr.host()); 13 | EXPECT_EQ(got_host, "127.0.0.1"); 14 | EXPECT_EQ(addr.port(), 8080); 15 | 16 | OUTCOME_TRY(str, addr.to_string()); 17 | EXPECT_EQ(str, "127.0.0.1:8080"); 18 | 19 | return success(); 20 | } 21 | 22 | TEST_R(net_address, ipv6) { 23 | net::inet6_address addr; 24 | OUTCOME_TRY(addr.host("::1")); 25 | addr.port(8080); 26 | OUTCOME_TRY(got_host, addr.host()); 27 | EXPECT_EQ(got_host, "::1"); 28 | EXPECT_EQ(addr.port(), 8080); 29 | 30 | OUTCOME_TRY(str, addr.to_string()); 31 | EXPECT_EQ(str, "[::1]:8080"); 32 | return success(); 33 | } 34 | 35 | TEST_R(net_address, upcast_downcast) { 36 | net::inet_address addr; 37 | OUTCOME_TRY(addr.host("127.0.0.1")); 38 | addr.port(8080); 39 | 40 | net::address p = addr.to_address(); 41 | OUTCOME_TRY(addr2, net::inet_address::from_address(p)); 42 | 43 | OUTCOME_TRY(str1, addr.to_string()); 44 | OUTCOME_TRY(str2, addr2.to_string()); 45 | EXPECT_EQ(str1, str2); 46 | 47 | return success(); 48 | } 49 | --------------------------------------------------------------------------------