├── .clang-format ├── .clang-tidy ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── add_external.cmake ├── example ├── CMakeLists.txt ├── block_on_example.cpp ├── chain_example.cpp ├── channel_example.cpp ├── condvar_example.cpp ├── heavy_task.cpp ├── latch_example.cpp ├── mutex_example.cpp ├── rwlock_example.cpp ├── simple_server.cpp ├── tcp_example.cpp ├── udp_example.cpp └── wait_example.cpp ├── include └── coco │ ├── blocking_executor.hpp │ ├── defer.hpp │ ├── inl_executor.hpp │ ├── mt_executor.hpp │ ├── net.hpp │ ├── proactor.hpp │ ├── runtime.hpp │ ├── sync.hpp │ ├── sync │ ├── chain.hpp │ ├── channel.hpp │ ├── condvar.hpp │ ├── latch.hpp │ ├── mutex.hpp │ └── rwlock.hpp │ ├── sys │ ├── fd.hpp │ ├── file.hpp │ ├── file_awaiters.hpp │ ├── listener.hpp │ ├── socket.hpp │ ├── socket_addr.hpp │ ├── socket_awaiters.hpp │ ├── stream.hpp │ └── udp_socket.hpp │ ├── task.hpp │ ├── timer.hpp │ ├── uring.hpp │ ├── util │ ├── fixed_vec.hpp │ ├── heap.hpp │ ├── intr_ptr.hpp │ ├── lockfree_queue.hpp │ ├── panic.hpp │ ├── queue.hpp │ └── ring_buffer.hpp │ └── worker_job.hpp ├── src ├── inl_executor.cpp ├── mt_executor.cpp ├── sys │ └── socket_addr.cpp ├── timer.cpp └── uring.cpp └── test ├── CMakeLists.txt └── timer_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | # BasedOnStyle: LLVM 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveAssignments: 7 | Enabled: false 8 | AcrossEmptyLines: false 9 | AcrossComments: false 10 | AlignCompound: false 11 | PadOperators: true 12 | AlignConsecutiveBitFields: 13 | Enabled: false 14 | AcrossEmptyLines: false 15 | AcrossComments: false 16 | AlignCompound: false 17 | PadOperators: false 18 | AlignConsecutiveDeclarations: 19 | Enabled: false 20 | AcrossEmptyLines: false 21 | AcrossComments: false 22 | AlignCompound: false 23 | PadOperators: false 24 | AlignConsecutiveMacros: 25 | Enabled: false 26 | AcrossEmptyLines: false 27 | AcrossComments: false 28 | AlignCompound: false 29 | PadOperators: false 30 | AlignEscapedNewlines: Right 31 | AlignOperands: Align 32 | AlignTrailingComments: true 33 | AllowAllArgumentsOnNextLine: false 34 | AllowAllParametersOfDeclarationOnNextLine: true 35 | AllowShortEnumsOnASingleLine: true 36 | AllowShortBlocksOnASingleLine: Never 37 | AllowShortCaseLabelsOnASingleLine: false 38 | AllowShortFunctionsOnASingleLine: All 39 | AllowShortLambdasOnASingleLine: All 40 | AllowShortIfStatementsOnASingleLine: Never 41 | AllowShortLoopsOnASingleLine: false 42 | AlwaysBreakAfterDefinitionReturnType: None 43 | AlwaysBreakAfterReturnType: None 44 | AlwaysBreakBeforeMultilineStrings: false 45 | AlwaysBreakTemplateDeclarations: Yes 46 | AttributeMacros: 47 | - __capability 48 | BinPackArguments: true 49 | BinPackParameters: true 50 | BraceWrapping: 51 | AfterCaseLabel: false 52 | AfterClass: false 53 | AfterControlStatement: Never 54 | AfterEnum: false 55 | AfterFunction: true 56 | AfterNamespace: false 57 | AfterObjCDeclaration: false 58 | AfterStruct: false 59 | AfterUnion: false 60 | AfterExternBlock: false 61 | BeforeCatch: false 62 | BeforeElse: false 63 | BeforeLambdaBody: false 64 | BeforeWhile: false 65 | IndentBraces: false 66 | SplitEmptyFunction: true 67 | SplitEmptyRecord: true 68 | SplitEmptyNamespace: true 69 | BreakBeforeBinaryOperators: None 70 | BreakBeforeConceptDeclarations: Always 71 | BreakBeforeBraces: Custom 72 | BreakBeforeInheritanceComma: false 73 | BreakInheritanceList: BeforeColon 74 | BreakBeforeTernaryOperators: true 75 | BreakConstructorInitializersBeforeComma: false 76 | BreakConstructorInitializers: BeforeColon 77 | BreakAfterJavaFieldAnnotations: false 78 | BreakStringLiterals: true 79 | ColumnLimit: 120 80 | CommentPragmas: "^ IWYU pragma:" 81 | CompactNamespaces: false 82 | ConstructorInitializerIndentWidth: 4 83 | ContinuationIndentWidth: 4 84 | Cpp11BracedListStyle: true 85 | DeriveLineEnding: true 86 | DerivePointerAlignment: false 87 | DisableFormat: false 88 | EmptyLineAfterAccessModifier: Never 89 | EmptyLineBeforeAccessModifier: LogicalBlock 90 | ExperimentalAutoDetectBinPacking: false 91 | PackConstructorInitializers: BinPack 92 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 93 | AllowAllConstructorInitializersOnNextLine: true 94 | FixNamespaceComments: true 95 | ForEachMacros: 96 | - foreach 97 | - Q_FOREACH 98 | - BOOST_FOREACH 99 | IfMacros: 100 | - KJ_IF_MAYBE 101 | IncludeBlocks: Preserve 102 | IncludeCategories: 103 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 104 | Priority: 2 105 | SortPriority: 0 106 | CaseSensitive: false 107 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 108 | Priority: 3 109 | SortPriority: 0 110 | CaseSensitive: false 111 | - Regex: ".*" 112 | Priority: 1 113 | SortPriority: 0 114 | CaseSensitive: false 115 | IncludeIsMainRegex: "(Test)?$" 116 | IncludeIsMainSourceRegex: "" 117 | IndentAccessModifiers: false 118 | IndentCaseLabels: false 119 | IndentCaseBlocks: false 120 | IndentGotoLabels: true 121 | IndentPPDirectives: BeforeHash 122 | IndentExternBlock: AfterExternBlock 123 | IndentRequiresClause: true 124 | IndentWidth: 2 125 | IndentWrappedFunctionNames: false 126 | InsertBraces: true 127 | InsertTrailingCommas: None 128 | JavaScriptQuotes: Leave 129 | JavaScriptWrapImports: true 130 | KeepEmptyLinesAtTheStartOfBlocks: true 131 | LambdaBodyIndentation: Signature 132 | MacroBlockBegin: "" 133 | MacroBlockEnd: "" 134 | MaxEmptyLinesToKeep: 1 135 | NamespaceIndentation: None 136 | ObjCBinPackProtocolList: Auto 137 | ObjCBlockIndentWidth: 2 138 | ObjCBreakBeforeNestedBlockParam: true 139 | ObjCSpaceAfterProperty: false 140 | ObjCSpaceBeforeProtocolList: true 141 | PenaltyBreakAssignment: 2 142 | PenaltyBreakBeforeFirstCallParameter: 19 143 | PenaltyBreakComment: 300 144 | PenaltyBreakFirstLessLess: 120 145 | PenaltyBreakOpenParenthesis: 0 146 | PenaltyBreakString: 1000 147 | PenaltyBreakTemplateDeclaration: 10 148 | PenaltyExcessCharacter: 1000000 149 | PenaltyReturnTypeOnItsOwnLine: 60 150 | PenaltyIndentedWhitespace: 0 151 | PointerAlignment: Left 152 | QualifierAlignment: Right 153 | PPIndentWidth: -1 154 | ReferenceAlignment: Pointer 155 | ReflowComments: true 156 | RemoveBracesLLVM: false 157 | RequiresClausePosition: OwnLine 158 | SeparateDefinitionBlocks: Leave 159 | ShortNamespaceLines: 1 160 | SortIncludes: CaseSensitive 161 | SortJavaStaticImport: Before 162 | SortUsingDeclarations: true 163 | SpaceAfterCStyleCast: false 164 | SpaceAfterLogicalNot: false 165 | SpaceAfterTemplateKeyword: true 166 | SpaceBeforeAssignmentOperators: true 167 | SpaceBeforeCaseColon: false 168 | SpaceBeforeCpp11BracedList: false 169 | SpaceBeforeCtorInitializerColon: true 170 | SpaceBeforeInheritanceColon: true 171 | SpaceBeforeParens: ControlStatements 172 | SpaceBeforeParensOptions: 173 | AfterControlStatements: true 174 | AfterForeachMacros: true 175 | AfterFunctionDefinitionName: false 176 | AfterFunctionDeclarationName: false 177 | AfterIfMacros: true 178 | AfterOverloadedOperator: false 179 | AfterRequiresInClause: false 180 | AfterRequiresInExpression: false 181 | BeforeNonEmptyParentheses: false 182 | SpaceAroundPointerQualifiers: Default 183 | SpaceBeforeRangeBasedForLoopColon: true 184 | SpaceInEmptyBlock: false 185 | SpaceInEmptyParentheses: false 186 | SpacesBeforeTrailingComments: 1 187 | SpacesInAngles: Never 188 | SpacesInConditionalStatement: false 189 | SpacesInContainerLiterals: true 190 | SpacesInCStyleCastParentheses: false 191 | SpacesInLineCommentPrefix: 192 | Minimum: 1 193 | Maximum: -1 194 | SpacesInParentheses: false 195 | SpacesInSquareBrackets: false 196 | SpaceBeforeSquareBrackets: false 197 | BitFieldColonSpacing: Both 198 | Standard: Latest 199 | StatementAttributeLikeMacros: 200 | - Q_EMIT 201 | StatementMacros: 202 | - Q_UNUSED 203 | - QT_REQUIRE_VERSION 204 | TabWidth: 8 205 | UseCRLF: false 206 | UseTab: Never 207 | WhitespaceSensitiveMacros: 208 | - STRINGIZE 209 | - PP_STRINGIZE 210 | - BOOST_PP_STRINGIZE 211 | - NS_SWIFT_NAME 212 | - CF_SWIFT_NAME 213 | --- 214 | 215 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "clang-diagnostic-*,clang-analyzer-*,bugprone-*,misc-*,performance-*,portability-*,modernize-*,-bugprone-easily-swappable-parameters" 3 | WarningsAsErrors: "" 4 | HeaderFilterRegex: "" 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: .clang-format 7 | User: leaving 8 | --- -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /draft 3 | 4 | .vscode 5 | .cache -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(Coco) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | find_library(LIBURING 7 | NAMES liburing.a 8 | REQUIRED 9 | HINTS "/usr/lib" 10 | ) 11 | 12 | if(NOT LIBURING) 13 | message(FATAL_ERROR "liburing not found") 14 | else() 15 | message(STATUS "liburing: ${LIBURING} found") 16 | endif() 17 | 18 | include(cmake/add_external.cmake) 19 | 20 | # address santitlizer 21 | option(COCO_BUILD_WITH_ASAN "Build with address sanitizer" ON) 22 | 23 | if(COCO_BUILD_WITH_ASAN) 24 | message(STATUS "Build with address sanitizer") 25 | add_compile_options(-fsanitize=address -fno-omit-frame-pointer) 26 | add_link_options(-fsanitize=address -fno-omit-frame-pointer) 27 | endif() 28 | 29 | # target coco 30 | add_library(Coco STATIC 31 | src/uring.cpp 32 | src/mt_executor.cpp 33 | src/inl_executor.cpp 34 | src/timer.cpp 35 | src/sys/socket_addr.cpp 36 | ) 37 | set_target_properties(Coco PROPERTIES CXX_STANDARD 20) 38 | target_include_directories(Coco PUBLIC include) 39 | target_link_libraries(Coco PUBLIC ${LIBURING}) 40 | target_precompile_headers(Coco 41 | PUBLIC 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ) 58 | 59 | option(COCO_BUILD_EXAMPLE "Build example" OFF) 60 | option(COCO_BUILD_TEST "Build test" OFF) 61 | 62 | if(COCO_BUILD_EXAMPLE) 63 | message(STATUS "Build Coco example") 64 | add_subdirectory(example) 65 | endif() 66 | 67 | if(COCO_BUILD_TEST) 68 | message(STATUS "Build Coco test") 69 | add_subdirectory(test) 70 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [WIP] Coco 2 | A simple C++ 20 coroutine library, using liburing. 3 | 4 | ## Features 5 | - [x] `Channel`, `Mutex`, `CondVar`, `RwLock`, `Latch` 6 | - [x] `TcpStream`, `TcpListener`, `UdpSocket` 7 | - [x] `MultiThreadExecutor`, `InlineExecutor` 8 | - [x] `waitAll`, `sleepFor`, `sleepUntil` 9 | - [x] **File IO** 10 | - [x] Support IPv6 and IPv4 11 | 12 | 13 | ## Example 14 | Simple Tcp server and client 15 | ```cpp 16 | static coco::Runtime rt(coco::MT, 4); 17 | 18 | auto server() -> coco::Task 19 | { 20 | using namespace coco::sys; 21 | ::puts("server start"); 22 | auto [listener, errc] = TcpListener::bind(SocketAddr(SocketAddrV4::localhost(2333))); 23 | 24 | if (errc != std::errc{0}) { 25 | print("bind error", errc); 26 | co_return 1; 27 | } 28 | 29 | auto [stream, errc2] = co_await listener.accept(); 30 | 31 | if (errc2 != std::errc{0}) { 32 | print("accept error", errc2); 33 | co_return 1; 34 | } 35 | ::puts("server accepted"); 36 | 37 | std::array buf{}; 38 | while (true) { 39 | auto [n, errc2] = co_await stream.recv(std::as_writable_bytes(std::span(buf))); 40 | 41 | if (errc2 != std::errc(0)) { 42 | print("recv error", errc2); 43 | co_return 1; 44 | } 45 | 46 | if (n == 0) { 47 | break; 48 | } 49 | buf[n] = '\0'; 50 | ::printf("server get: %s\n", buf.data()); 51 | } 52 | ::puts("server end"); 53 | co_return 0; 54 | } 55 | 56 | auto client() -> coco::Task<> 57 | { 58 | using namespace coco::sys; 59 | ::puts("client start"); 60 | auto [client, errc] = co_await TcpStream::connect(SocketAddr(SocketAddrV4::localhost(2333))); 61 | if (errc != std::errc(0)) { 62 | print("connect error", errc); 63 | co_return; 64 | } 65 | ::puts("client connected"); 66 | for (int i = 0; i < 10; i++) { 67 | auto [n, errc2] = co_await client.send(std::as_bytes(std::span("hi server"))); 68 | if (errc2 != std::errc(0)) { 69 | print("send error", errc2); 70 | co_return; 71 | } 72 | if (n == 0) { 73 | break; 74 | } 75 | co_await rt.sleepFor(std::chrono::seconds(1)); 76 | } 77 | ::puts("client end"); 78 | co_return; 79 | } 80 | 81 | auto main() -> int 82 | { 83 | rt.block([]() -> coco::Task<> { 84 | ::puts("tcp example"); 85 | auto s = rt.spawn(server()); 86 | auto c = rt.spawn(client()); 87 | 88 | co_await c.join(); 89 | co_await s.join(); 90 | ::puts("everything done"); 91 | co_return; 92 | }()); 93 | } 94 | ``` 95 | ## Requirements 96 | - latest liburing built and installed -------------------------------------------------------------------------------- /cmake/add_external.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | macro(add_external target git_repo git_tag) 4 | FetchContent_Declare( 5 | ${target} 6 | GIT_REPOSITORY "${git_repo}" 7 | GIT_TAG ${git_tag} 8 | ) 9 | FetchContent_MakeAvailable(${target}) 10 | endmacro() -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(wait_example wait_example.cpp) 2 | target_link_libraries(wait_example Coco) 3 | set_target_properties(wait_example PROPERTIES CXX_STANDARD 20) 4 | 5 | add_executable(tcp_example tcp_example.cpp) 6 | target_link_libraries(tcp_example Coco) 7 | set_target_properties(tcp_example PROPERTIES CXX_STANDARD 20) 8 | 9 | add_executable(mutex_example mutex_example.cpp) 10 | target_link_libraries(mutex_example Coco) 11 | set_target_properties(mutex_example PROPERTIES CXX_STANDARD 20) 12 | 13 | add_executable(channel_example channel_example.cpp) 14 | target_link_libraries(channel_example Coco) 15 | set_target_properties(channel_example PROPERTIES CXX_STANDARD 20) 16 | 17 | add_executable(condvar_example condvar_example.cpp) 18 | target_link_libraries(condvar_example Coco) 19 | set_target_properties(condvar_example PROPERTIES CXX_STANDARD 20) 20 | 21 | add_executable(rwlock_example rwlock_example.cpp) 22 | target_link_libraries(rwlock_example Coco) 23 | set_target_properties(rwlock_example PROPERTIES CXX_STANDARD 20) 24 | 25 | add_executable(latch_example latch_example.cpp) 26 | target_link_libraries(latch_example Coco) 27 | set_target_properties(latch_example PROPERTIES CXX_STANDARD 20) 28 | 29 | add_executable(udp_example udp_example.cpp) 30 | target_link_libraries(udp_example Coco) 31 | set_target_properties(udp_example PROPERTIES CXX_STANDARD 20) 32 | 33 | add_executable(block_on_example block_on_example.cpp) 34 | target_link_libraries(block_on_example Coco) 35 | set_target_properties(block_on_example PROPERTIES CXX_STANDARD 20) 36 | 37 | add_executable(simple_server simple_server.cpp) 38 | target_link_libraries(simple_server Coco) 39 | set_target_properties(simple_server PROPERTIES CXX_STANDARD 20) 40 | 41 | add_executable(heavy_task heavy_task.cpp) 42 | target_link_libraries(heavy_task Coco) 43 | set_target_properties(heavy_task PROPERTIES CXX_STANDARD 20) 44 | 45 | add_executable(chain_example chain_example.cpp) 46 | target_link_libraries(chain_example Coco) 47 | set_target_properties(chain_example PROPERTIES CXX_STANDARD 20) 48 | 49 | # add a target run all example 50 | add_custom_target(run_example 51 | COMMAND wait_example 52 | COMMAND tcp_example 53 | COMMAND mutex_example 54 | COMMAND channel_example 55 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 56 | ) -------------------------------------------------------------------------------- /example/block_on_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std::literals; 4 | 5 | auto main() -> int 6 | { 7 | static auto rt = coco::Runtime(coco::INL, 4); 8 | rt.block([&]() -> coco::Task<> { 9 | auto t1 = rt.spawn([]() -> coco::Task<> { 10 | for (int i = 0; i < 10; i++) { 11 | std::printf("t1: %d\n", i); 12 | co_await rt.sleepFor(0.8s); 13 | } 14 | }()); 15 | try { 16 | auto i = co_await rt.blockOn([]() { 17 | std::this_thread::sleep_for(4s); // simulate a long running task 18 | throw std::runtime_error("throw error"); 19 | return 1; 20 | }); 21 | } catch (std::runtime_error& e) { 22 | std::printf("error: %s\n", e.what()); 23 | } 24 | co_await t1.join(); 25 | co_return; 26 | }()); 27 | } -------------------------------------------------------------------------------- /example/chain_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static auto rt = coco::Runtime(coco::MT, 4); 5 | 6 | auto main() -> int 7 | { 8 | rt.block([]() -> coco::Task<> { 9 | auto chain = coco::sync::Chain(); 10 | std::uint64_t cnt{0}; 11 | chain.withCtx(&cnt) 12 | .chain([](void* ctx) -> coco::Task<> { 13 | ::puts("job 1"); 14 | auto cnt = static_cast(ctx); 15 | for (int i = 0; i < 100'000; i++) { 16 | *cnt += 1; 17 | } 18 | co_return; 19 | }) 20 | .chain([](void* ctx) -> coco::Task<> { 21 | ::puts("job 2"); 22 | auto cnt = static_cast(ctx); 23 | for (int i = 0; i < 100'000; i++) { 24 | *cnt += 1; 25 | } 26 | co_return; 27 | }) 28 | .chain([](void* ctx) -> coco::Task<> { 29 | ::puts("job 3"); 30 | auto cnt = static_cast(ctx); 31 | for (int i = 0; i < 100'000; i++) { 32 | *cnt += 1; 33 | } 34 | co_return; 35 | }); 36 | co_await rt.wait(std::move(chain)); 37 | assert(cnt == 300'000); 38 | }()); 39 | } 40 | -------------------------------------------------------------------------------- /example/channel_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static coco::Runtime rt(coco::MT, 4); 5 | 6 | auto main() -> int 7 | { 8 | rt.block([]() -> coco::Task<> { 9 | using ChanType = coco::sync::Channel; 10 | auto chan = ChanType(); 11 | auto reader = rt.spawn([](ChanType& chan) -> coco::Task<> { 12 | ::puts("reader"); 13 | std::size_t sum = 0; 14 | int cnt = 0; 15 | for (int i = 0; i < 100'000; i++) { 16 | auto val = co_await chan.read(); 17 | sum += val.value(); 18 | } 19 | ::printf("reader done: %zu\n", sum); 20 | co_return; 21 | }(chan)); 22 | 23 | auto writer = rt.spawn([](ChanType& chan) -> coco::Task<> { 24 | ::puts("writer"); 25 | for (int i = 0; i < 100'000; ++i) { 26 | co_await chan.write(i); 27 | } 28 | ::puts("writer done"); 29 | co_return; 30 | }(chan)); 31 | 32 | co_await reader.join(); 33 | chan.close(); 34 | co_await writer.join(); 35 | co_return; 36 | }()); 37 | } -------------------------------------------------------------------------------- /example/condvar_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto worker(coco::sync::CondVar& cv, int v) -> coco::Task 5 | { 6 | ::printf("worker %d wait\n", v); 7 | co_await cv.wait(); 8 | ::printf("worker %d wake up\n", v); 9 | co_return; 10 | } 11 | 12 | auto main() -> int 13 | { 14 | static auto rt = coco::Runtime(coco::MT, 4); 15 | rt.block([]() -> coco::Task<> { 16 | auto cv = coco::sync::CondVar(); 17 | auto workers = std::vector>>(); 18 | workers.reserve(10); 19 | for (auto i = 0; i < 10; ++i) { 20 | workers.push_back(rt.spawn(worker(cv, i))); 21 | } 22 | co_await rt.sleepFor(std::chrono::seconds(3)); 23 | ::printf("notify all\n"); 24 | cv.notifyAll(); 25 | co_await rt.waitAll(std::span(workers)); 26 | }()); 27 | } -------------------------------------------------------------------------------- /example/heavy_task.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // TODO: not efficient in this case 6 | static auto rt = coco::Runtime(coco::MT, 16); 7 | constexpr auto kTaskCount = 100'000'000; 8 | 9 | static auto gRandomGen = std::mt19937(std::random_device{}()); 10 | static auto gIterRange = std::uniform_int_distribution(0, 100); 11 | 12 | auto heavy_task(coco::sync::Latch& latch) -> coco::Task<> 13 | { 14 | auto iter = gIterRange(gRandomGen); 15 | for (int i = 0; i < iter; i++) { 16 | // do some calculation 17 | std::this_thread::yield(); 18 | } 19 | latch.countDown(); 20 | co_return; 21 | } 22 | 23 | auto main() -> int 24 | { 25 | rt.block([]() -> coco::Task<> { 26 | auto latch = coco::sync::Latch(kTaskCount); 27 | for (int i = 0; i < kTaskCount; i++) { 28 | rt.spawnDetach(heavy_task(latch)); 29 | } 30 | co_await latch.wait(); 31 | co_return; 32 | }()); 33 | } -------------------------------------------------------------------------------- /example/latch_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto main() -> int 5 | { 6 | // sleep sort 7 | static auto rt = coco::Runtime(coco::MT, 4); 8 | rt.block([]() -> coco::Task<> { 9 | std::vector vec{400, 200, 300, 100, 500}; 10 | std::vector result{}; 11 | auto latch = coco::sync::Latch(vec.size()); 12 | auto ths = std::vector>>{}; 13 | for (auto i = 0; i < vec.size(); ++i) { 14 | auto task = [](coco::sync::Latch& latch, int time, int i, std::vector& result) -> coco::Task<> { 15 | co_await rt.sleepFor(std::chrono::milliseconds(time)); 16 | result.push_back(time); 17 | ::printf("Task %d done\n", i); 18 | latch.countDown(); 19 | }; 20 | ths.push_back(rt.spawn(task(latch, vec[i], i, result))); 21 | } 22 | co_await latch.wait(); 23 | ::printf("All tasks done, result: "); 24 | for (auto i : result) { 25 | ::printf("%d ", i); 26 | } 27 | ::putchar('\n'); 28 | co_await rt.waitAll(std::span(ths)); 29 | }()); 30 | } -------------------------------------------------------------------------------- /example/mutex_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static coco::Runtime rt(coco::MT, 4); 6 | 7 | std::size_t counter = 0; 8 | 9 | auto incTask(coco::sync::Mutex& mt) -> coco::Task<> 10 | { 11 | for (int i = 0; i < 100'000; i++) { 12 | co_await mt.lock(); 13 | defer { mt.unlock(); }; 14 | counter += 1; 15 | } 16 | co_return; 17 | } 18 | 19 | auto tryLock() -> coco::Task<> 20 | { 21 | coco::sync::Mutex mt; 22 | auto r = co_await mt.tryLock(); 23 | assert(r); 24 | mt.unlock(); 25 | 26 | auto t1 = rt.spawn([](coco::sync::Mutex& mt) -> coco::Task<> { 27 | co_await mt.lock(); 28 | defer { mt.unlock(); }; 29 | 30 | co_await rt.sleepFor(1s); 31 | }(mt)); 32 | co_await rt.sleepFor(20ms); 33 | r = co_await mt.tryLock(); 34 | assert(!r); 35 | co_await t1.join(); 36 | }; 37 | 38 | auto main() -> int 39 | { 40 | rt.block(tryLock()); 41 | 42 | rt.block([]() -> coco::Task<> { 43 | auto now = std::chrono::steady_clock::now(); 44 | auto mt = coco::sync::Mutex(); 45 | auto ths = std::vector>>(100); 46 | for (int i = 0; i < ths.size(); i++) { 47 | ths[i] = rt.spawn(incTask(mt)); 48 | } 49 | co_await rt.waitAll(std::span(ths)); 50 | ::printf("counter = %zu\n", counter); 51 | ::printf("time = %zu\n", 52 | std::chrono::duration_cast(std::chrono::steady_clock::now() - now).count()); 53 | co_return; 54 | }()); 55 | } -------------------------------------------------------------------------------- /example/rwlock_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::vector data; 5 | static auto rt = coco::Runtime(coco::MT, 4); 6 | 7 | auto reader(coco::sync::RwLock& lock, int i) -> coco::Task<> 8 | { 9 | co_await rt.sleepFor(std::chrono::seconds(3)); 10 | co_await lock.lockRead(); 11 | ::printf("data size: %zu\n", data.size()); 12 | lock.unlockRead(); 13 | } 14 | 15 | auto writer(coco::sync::RwLock& lock) -> coco::Task<> 16 | { 17 | co_await lock.lockWrite(); 18 | ::puts("write data"); 19 | data.push_back(1); 20 | lock.unlockWrite(); 21 | } 22 | 23 | auto main() -> int 24 | { 25 | rt.block([]() -> coco::Task<> { 26 | coco::sync::RwLock lock; 27 | constexpr int rc = 5; 28 | constexpr int wc = 5; 29 | auto readers = std::vector>>(); 30 | auto writers = std::vector>>(); 31 | 32 | for (int i = 0; i < rc; i++) { 33 | readers.push_back(rt.spawn(reader(lock, i))); 34 | } 35 | for (int i = 0; i < wc; i++) { 36 | writers.push_back(rt.spawn(writer(lock))); 37 | } 38 | 39 | co_await rt.waitAll(std::span(readers)); 40 | co_await rt.waitAll(std::span(writers)); 41 | }()); 42 | } -------------------------------------------------------------------------------- /example/simple_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | using namespace std::literals; 7 | 8 | auto print(std::string_view msg, std::errc errc) -> void 9 | { 10 | if (errc == std::errc{0}) { 11 | ::printf("%s: success\n", msg.data()); 12 | } else { 13 | ::printf("%s: %s\n", msg.data(), std::generic_category().message(static_cast(errc)).c_str()); 14 | } 15 | } 16 | 17 | auto main() -> int 18 | { 19 | static auto rt = coco::Runtime(coco::MT, 4); 20 | rt.block([]() -> coco::Task<> { 21 | using namespace coco::sys; 22 | ::puts("server start"); 23 | auto addr = SocketAddr(SocketAddrV4::loopback(2333)); 24 | auto [listener, errc] = TcpListener::bind(addr); 25 | 26 | if (errc != std::errc{0}) { 27 | print("bind error", errc); 28 | co_return; 29 | } 30 | 31 | static auto latch = coco::sync::Latch(10000000); 32 | for (int i = 0; i < 10000000; i++) { 33 | auto [stream, errc0] = co_await listener.accept(); 34 | 35 | if (errc0 != std::errc{0}) { 36 | print("accept error", errc0); 37 | co_return; 38 | } 39 | 40 | rt.spawnDetach([](TcpStream stream, int i) -> coco::Task<> { 41 | std::array buf{}; 42 | auto [n, errc2] = co_await stream.recv(std::as_writable_bytes(std::span(buf))); 43 | 44 | if (errc2 != std::errc(0)) { 45 | print("recv error", errc2); 46 | latch.countDown(); 47 | co_return; 48 | } else { 49 | if (i % 1000 == 0) { 50 | ::printf("recv: %lu, id: %d, notify: %u\n", n, i, 0); 51 | } 52 | } 53 | 54 | std::string str; 55 | str.reserve(1024 + 128); 56 | const auto http200 = "HTTP/1.1 200 OK\r\nContent-Length: 1024\r\nConnection: close\r\n\r\n"sv; 57 | str.append(http200); 58 | str.append(1024, 'a'); 59 | str.append("\n"); 60 | auto [n2, err3] = co_await stream.send(std::as_bytes(std::span(str))); 61 | if (errc2 != std::errc(0)) { 62 | print("recv error", errc2); 63 | latch.countDown(); 64 | co_return; 65 | } 66 | auto e = co_await stream.close(); 67 | if (e != std::errc{0}) { 68 | print("close error", e); 69 | latch.countDown(); 70 | co_return; 71 | } 72 | latch.countDown(); 73 | }(std::move(stream), i)); 74 | } 75 | co_await latch.wait(); 76 | ::puts("server end"); 77 | co_return; 78 | }()); 79 | } -------------------------------------------------------------------------------- /example/tcp_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std::literals; 4 | 5 | auto print(std::string_view msg, std::errc errc) -> void 6 | { 7 | if (errc == std::errc{0}) { 8 | ::printf("%s: success\n", msg.data()); 9 | } else { 10 | ::printf("%s: %s\n", msg.data(), std::generic_category().message(static_cast(errc)).c_str()); 11 | } 12 | } 13 | 14 | static coco::Runtime rt(coco::INL, 4); 15 | 16 | auto server() -> coco::Task<> 17 | { 18 | using namespace coco::sys; 19 | ::puts("server start"); 20 | auto addr = SocketAddr(SocketAddrV6::loopback(2333)); 21 | auto [listener, errc] = TcpListener::bind(addr); 22 | 23 | if (errc != std::errc{0}) { 24 | print("bind error", errc); 25 | co_return; 26 | } 27 | 28 | auto [stream, errc2] = co_await listener.accept(); 29 | 30 | if (errc2 != std::errc{0}) { 31 | print("accept error", errc2); 32 | co_return; 33 | } 34 | ::puts("server accepted"); 35 | 36 | std::array buf{}; 37 | while (true) { 38 | auto [n, errc2] = co_await stream.recv(std::as_writable_bytes(std::span(buf))); 39 | 40 | if (errc2 != std::errc(0)) { 41 | print("recv error", errc2); 42 | co_return; 43 | } 44 | 45 | if (n == 0) { 46 | break; 47 | } 48 | buf[n] = '\0'; 49 | ::printf("server get: %s\n", buf.data()); 50 | } 51 | ::puts("server end"); 52 | co_return; 53 | } 54 | 55 | auto client() -> coco::Task<> 56 | { 57 | using namespace coco::sys; 58 | ::puts("client start"); 59 | auto addr = SocketAddr(SocketAddrV6::loopback(2333)); 60 | auto [client, errc] = co_await TcpStream::connect(addr); 61 | if (errc != std::errc(0)) { 62 | print("connect error", errc); 63 | co_return; 64 | } 65 | ::puts("client connected"); 66 | for (int i = 0; i < 10; i++) { 67 | auto [n, errc2] = co_await client.send(std::as_bytes(std::span("hi server"))); 68 | if (errc2 != std::errc(0)) { 69 | print("send error", errc2); 70 | co_return; 71 | } 72 | if (n == 0) { 73 | break; 74 | } 75 | co_await rt.sleepFor(std::chrono::seconds(1)); 76 | } 77 | ::puts("client end"); 78 | co_return; 79 | } 80 | 81 | auto main() -> int 82 | { 83 | rt.block([]() -> coco::Task<> { 84 | ::puts("tcp example"); 85 | auto s = rt.spawn(server()); 86 | auto c = rt.spawn(client()); 87 | 88 | co_await c.join(); 89 | co_await s.join(); 90 | ::puts("everything done"); 91 | co_return; 92 | }()); 93 | } 94 | 95 | auto connect() -> coco::Task<> 96 | { 97 | using namespace coco::sys; 98 | ::puts("client start"); 99 | auto [client, errc] = co_await TcpStream::connect(SocketAddr(SocketAddrV4::loopback(2333)), 1s); 100 | if (errc != std::errc(0)) { 101 | print("connect error", errc); 102 | co_return; 103 | } 104 | ::puts("client connected"); 105 | auto buf = std::array{}; 106 | auto [n, err] = co_await client.recv(std::as_writable_bytes(std::span(buf)), 5s); 107 | if (err != std::errc(0)) { 108 | print("recv error", err); 109 | co_return; 110 | } 111 | } 112 | 113 | auto main1() -> int 114 | { 115 | rt.block([]() -> coco::Task<> { 116 | ::puts("tcp example"); 117 | auto c = rt.spawn(connect()); 118 | 119 | co_await c.join(); 120 | ::puts("everything done"); 121 | co_return; 122 | }()); 123 | return 0; 124 | } -------------------------------------------------------------------------------- /example/udp_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std::literals; 4 | 5 | auto print(std::string_view msg, std::errc errc) -> void 6 | { 7 | if (errc == std::errc{0}) { 8 | ::printf("%s: success\n", msg.data()); 9 | } else { 10 | ::printf("%s: %s\n", msg.data(), std::generic_category().message(static_cast(errc)).c_str()); 11 | } 12 | } 13 | 14 | static coco::Runtime rt(coco::MT, 4); 15 | 16 | auto server(coco::sys::SocketAddr addr) -> coco::Task<> 17 | { 18 | using namespace coco::sys; 19 | ::puts("server start"); 20 | 21 | auto [listener, errc] = UdpSocket::bind(addr); 22 | if (errc != std::errc{0}) { 23 | print("bind error", errc); 24 | co_return; 25 | } else { 26 | ::puts("server bind success"); 27 | } 28 | 29 | std::array buf{}; 30 | for (int i = 0; i < 10; i++) { 31 | auto [n, errc] = co_await listener.recvfrom(std::as_writable_bytes(std::span(buf)), addr); 32 | 33 | if (errc != std::errc(0)) { 34 | print("recv error", errc); 35 | co_return; 36 | } 37 | 38 | buf[n] = '\0'; 39 | ::printf("server get: %s\n", buf.data()); 40 | } 41 | co_return; 42 | } 43 | 44 | auto client(coco::sys::SocketAddr addr) -> coco::Task<> 45 | { 46 | using namespace coco::sys; 47 | ::puts("client start"); 48 | auto [client, errc] = UdpSocket::create(addr); 49 | if (errc != std::errc(0)) { 50 | print("create error", errc); 51 | co_return; 52 | } 53 | 54 | for (int i = 0; i < 10; i++) { 55 | auto [n, errc] = co_await client.sendto(std::as_bytes(std::span("hello world")), addr); 56 | if (errc != std::errc(0)) { 57 | print("send error", errc); 58 | co_return; 59 | } 60 | } 61 | } 62 | 63 | auto main() -> int 64 | { 65 | rt.block([]() -> coco::Task<> { 66 | using namespace coco::sys; 67 | ::puts("udp example"); 68 | auto addr = SocketAddr(SocketAddrV4::loopback(2333)); 69 | auto st = rt.spawn(server(addr)); 70 | auto ct = rt.spawn(client(addr)); 71 | 72 | co_await st.join(); 73 | co_await ct.join(); 74 | ::puts("everything done"); 75 | co_return; 76 | }()); 77 | } -------------------------------------------------------------------------------- /example/wait_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static coco::Runtime rt(coco::MT, 4); 5 | 6 | auto taskA() -> coco::Task<> 7 | { 8 | ::puts("taskA"); 9 | co_return; 10 | } 11 | 12 | auto taskB() -> coco::Task<> 13 | { 14 | co_await rt.sleepFor(1s); 15 | ::puts("taskB"); 16 | co_return; 17 | } 18 | 19 | auto taskC() -> coco::Task<> 20 | { 21 | co_await rt.sleepFor(2s); 22 | ::puts("taskC"); 23 | co_return; 24 | } 25 | 26 | auto main() -> int 27 | { 28 | rt.block([]() -> coco::Task<> { 29 | auto handle = co_await coco::ThisTask(); 30 | ::puts("===================="); 31 | 32 | ::puts("every thing done"); 33 | co_return; 34 | }()); 35 | } -------------------------------------------------------------------------------- /include/coco/blocking_executor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/task.hpp" 4 | #include "coco/util/panic.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace coco { 10 | class BlockingThreadPool { 11 | public: 12 | BlockingThreadPool(std::size_t threadLimit) : mIdleCount(0), mThreadCount(0), mThreadLimits(threadLimit) {} 13 | ~BlockingThreadPool() {} 14 | auto enqueue(WorkerJob* task) -> void 15 | { 16 | auto lk = std::unique_lock(mQueueMt); 17 | mQueue.pushBack(task); 18 | mQueueSize += 1; 19 | mQueueCv.notify_one(); 20 | growPool(); 21 | } 22 | 23 | private: 24 | auto loop() -> void 25 | { 26 | using namespace std::chrono_literals; 27 | auto lk = std::unique_lock(mQueueMt); 28 | while (true) { 29 | mIdleCount -= 1; 30 | while (!mQueue.empty()) { 31 | growPool(); 32 | auto task = mQueue.popFront(); 33 | lk.unlock(); 34 | task->run(task, kWorkerArgNull); 35 | lk.lock(); 36 | } 37 | mIdleCount += 1; 38 | auto r = mQueueCv.wait_for(lk, 500ms); 39 | if (r == std::cv_status::timeout && mQueue.empty()) { 40 | mIdleCount -= 1; 41 | mThreadCount -= 1; 42 | break; 43 | } 44 | } 45 | } 46 | 47 | auto growPool() -> void 48 | { 49 | assert(!mQueueMt.try_lock()); 50 | while (mQueueSize > mIdleCount * 5 && mThreadCount < mThreadLimits) { 51 | mThreadCount += 1; 52 | mIdleCount += 1; 53 | mQueueCv.notify_all(); 54 | std::thread([this]() { loop(); }).detach(); 55 | } 56 | } 57 | 58 | private: 59 | std::mutex mQueueMt; 60 | std::condition_variable mQueueCv; 61 | util::Queue<&WorkerJob::next> mQueue; 62 | std::size_t mQueueSize; 63 | 64 | std::size_t mIdleCount; 65 | std::size_t mThreadCount; 66 | std::size_t mThreadLimits; 67 | }; 68 | 69 | class BlockingExecutor { 70 | public: 71 | BlockingExecutor(std::size_t threadLimit) : mPool(threadLimit) {} 72 | ~BlockingExecutor() = default; 73 | auto execute(WorkerJob* handle) noexcept -> void { mPool.enqueue(handle); } 74 | BlockingThreadPool mPool; 75 | }; 76 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/defer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | class Defer { 5 | public: 6 | Defer(F f) noexcept : mFn(std::move(f)), mInvoke(true) {} 7 | Defer(Defer&& other) noexcept : mFn(std::move(other.mFn)), mInvoke(other.mInvoke) { other.mInvoke = false; } 8 | Defer(Defer const&) = delete; 9 | Defer& operator=(Defer const&) = delete; 10 | ~Defer() noexcept 11 | { 12 | if (mInvoke) { 13 | mFn(); 14 | } 15 | } 16 | 17 | private: 18 | F mFn; 19 | bool mInvoke; 20 | }; 21 | 22 | template 23 | inline Defer deferFn(F const& f) noexcept 24 | { 25 | return Defer(f); 26 | } 27 | 28 | template 29 | inline Defer deferFn(F&& f) noexcept 30 | { 31 | return Defer(std::forward(f)); 32 | } 33 | 34 | #define concat1(a, b) a##b 35 | #define concat2(a, b) concat1(a, b) 36 | #define _deferObject concat2(_finally_object_, __COUNTER__) 37 | 38 | #define defer Defer _deferObject = [&]() 39 | #define defer2(func) Defer _deferObject = deferFn(func) 40 | -------------------------------------------------------------------------------- /include/coco/inl_executor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/proactor.hpp" 4 | #include "coco/task.hpp" 5 | #include "coco/worker_job.hpp" 6 | 7 | #include 8 | 9 | namespace coco { 10 | class InlExecutor : public Executor { 11 | public: 12 | InlExecutor() : mState(State::Waiting) { mProactor = &Proactor::get(); } 13 | virtual ~InlExecutor() noexcept { forceStop(); }; 14 | 15 | auto execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void override; 16 | auto execute(WorkerJob* handle, ExeOpt opt) noexcept -> void override; 17 | auto runMain(Task<> task) -> void override; 18 | 19 | auto forceStop() -> void; 20 | auto loop() -> void; 21 | auto processTasks() -> void; 22 | 23 | private: 24 | enum class State { 25 | Waiting, 26 | Executing, 27 | Stop, 28 | }; 29 | 30 | Proactor* mProactor = nullptr; 31 | WorkerJobQueue mTaskQueue; 32 | State mState; 33 | std::atomic mMainTaskState = JobState::Ready; 34 | }; 35 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/mt_executor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/proactor.hpp" 4 | #include "coco/task.hpp" 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | namespace coco { 11 | class Worker { 12 | public: 13 | Worker() noexcept = default; 14 | Worker(Worker const&) = delete; 15 | Worker(Worker&&) = delete; 16 | auto operator=(Worker const&) -> Worker& = delete; 17 | auto operator=(Worker&&) -> Worker& = delete; 18 | 19 | template 20 | [[nodiscard]] auto enqueue(T task, ExeOpt opt) noexcept -> bool 21 | { 22 | auto state = mState.load(std::memory_order_relaxed); 23 | if (state == State::Stop) [[unlikely]] { 24 | return false; 25 | } else { 26 | pushTask(std::move(task), opt); 27 | return true; 28 | } 29 | } 30 | 31 | template 32 | [[nodiscard]] auto tryEnqeue(T task, ExeOpt opt) noexcept -> bool 33 | { 34 | auto state = mState.load(std::memory_order_relaxed); 35 | if (state == State::Stop) [[unlikely]] { 36 | return false; 37 | } else { 38 | return tryPushTask(std::move(task), opt); 39 | } 40 | } 41 | 42 | auto forceStop() -> void; 43 | auto start(std::latch& latch) -> void; 44 | auto loop() -> void; 45 | auto notify() -> void; 46 | 47 | private: 48 | auto processTasks() -> void; 49 | 50 | auto pushTask(WorkerJob* job, ExeOpt opt) -> void; 51 | auto tryPushTask(WorkerJob* job, ExeOpt opt) -> bool; 52 | 53 | auto pushTask(WorkerJobQueue jobs, ExeOpt opt) -> void; 54 | auto tryPushTask(WorkerJobQueue jobs, ExeOpt opt) -> bool; 55 | 56 | private: 57 | enum class State { 58 | Waiting, 59 | Executing, 60 | Stop, 61 | }; 62 | 63 | coco::Proactor* mProactor = nullptr; 64 | std::mutex mQueueMt; 65 | util::Queue<&WorkerJob::next> mTaskQueue; 66 | std::atomic mState; 67 | }; 68 | 69 | class MtExecutor : public Executor { 70 | public: 71 | MtExecutor(std::size_t threadCount); 72 | ~MtExecutor() noexcept override 73 | { 74 | requestStop(); 75 | join(); 76 | } 77 | auto requestStop() noexcept -> void; 78 | auto join() noexcept -> void; 79 | auto execute(WorkerJob* job, ExeOpt opt) noexcept -> void override; 80 | auto execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void override; 81 | auto runMain(Task<> task) -> void override; 82 | 83 | private: 84 | template 85 | auto balanceEnqueue(T task, ExeOpt opt) noexcept -> void 86 | requires std::is_same_v || std::is_base_of_v> 87 | { 88 | auto nextIdx = opt.mOpt == ExeOpt::Balance ? mNextWorker.fetch_add(1, std::memory_order_relaxed) 89 | : mNextWorker.load(std::memory_order_relaxed); 90 | auto startIdx = nextIdx % mThreadCount; 91 | for (std::uint32_t i = 0; i < mThreadCount; i++) { 92 | auto const idx = (startIdx + i) < mThreadCount ? (startIdx + i) : (startIdx + i - mThreadCount); 93 | if (mWorkers[idx]->tryEnqeue(std::move(task), opt)) { 94 | return; 95 | } 96 | } 97 | auto r = mWorkers[startIdx]->enqueue(std::move(task), opt); 98 | assert(r); 99 | } 100 | 101 | std::uint32_t const mThreadCount; 102 | std::atomic_uint32_t mNextWorker = 0; 103 | std::vector mThreads; 104 | std::vector> mWorkers; 105 | std::atomic_uint32_t mSyncNextWorker = 0; 106 | }; 107 | } // namespace coco 108 | -------------------------------------------------------------------------------- /include/coco/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/sys/stream.hpp" 3 | #include "coco/sys/listener.hpp" 4 | #include "coco/sys/udp_socket.hpp" -------------------------------------------------------------------------------- /include/coco/proactor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/timer.hpp" 4 | #include "coco/uring.hpp" 5 | #include "coco/util/fixed_vec.hpp" 6 | #include "coco/worker_job.hpp" 7 | 8 | namespace coco { 9 | struct CancelItem { 10 | enum class Kind { IoFd, TimeoutToken } mKind; 11 | union { 12 | int mFd; 13 | Token mToken; 14 | }; 15 | static auto cancelIo(int fd) -> CancelItem { return {Kind::IoFd, fd}; } 16 | static auto cancelTimeout(Token token) -> CancelItem { return {.mKind = Kind::TimeoutToken, .mToken = token}; } 17 | }; 18 | class Proactor { 19 | public: 20 | static auto get() noexcept -> Proactor& 21 | { 22 | static thread_local auto instance = std::make_shared(); 23 | return *instance; 24 | } 25 | 26 | Proactor() = default; 27 | ~Proactor() = default; 28 | 29 | auto attachExecutor(Executor* executor, std::uint32_t tid) noexcept -> void 30 | { 31 | mExecutor = executor; 32 | mTid = tid; 33 | } 34 | auto getExecutor() const noexcept -> Executor* { return mExecutor; } 35 | auto execute(WorkerJobQueue&& queue, ExeOpt opt) noexcept -> void 36 | { 37 | if (opt.mOpt == ExeOpt::PreferInOne) [[unlikely]] { 38 | opt.mTid = mTid; 39 | } 40 | mExecutor->execute(std::move(queue), 0, opt); 41 | notify(); 42 | } 43 | auto execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void 44 | { 45 | if (opt.mOpt == ExeOpt::PreferInOne) [[unlikely]] { 46 | opt.mTid = mTid; 47 | } 48 | mExecutor->execute(std::move(queue), count, opt); 49 | notify(); 50 | } 51 | auto execute(WorkerJob* job, ExeOpt opt) noexcept -> void 52 | { 53 | if (opt.mOpt == ExeOpt::PreferInOne) [[unlikely]] { 54 | opt.mTid = mTid; 55 | } 56 | mExecutor->execute(job, opt); 57 | notify(); 58 | } 59 | auto addTimer(Instant time, WorkerJob* job) noexcept -> void { mTimerManager.addTimer(time, job); } 60 | auto deleteTimer(void* jobId) noexcept -> void { mTimerManager.deleteTimer(jobId); } 61 | auto processTimers() { return mTimerManager.processTimers(); } 62 | 63 | auto notify() -> void 64 | { 65 | bool expected = false; 66 | if (mNotifyBlocked.compare_exchange_strong(expected, true)) { 67 | mUring.notify(); 68 | } 69 | } 70 | auto prepRecv(Token token, int fd, std::span buf, int flag = 0) -> void 71 | { 72 | addPendingSet((WorkerJob*)token); 73 | mUring.prepRecv(token, fd, buf, flag); 74 | } 75 | auto prepSend(Token token, int fd, std::span buf, int flag = 0) -> void 76 | { 77 | addPendingSet((WorkerJob*)token); 78 | mUring.prepSend(token, fd, buf, flag); 79 | } 80 | auto prepAccept(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags = 0) -> void 81 | { 82 | addPendingSet((WorkerJob*)token); 83 | mUring.prepAccept(token, fd, addr, addrlen, flags); 84 | } 85 | auto prepAcceptMt(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags = 0) -> void 86 | { 87 | addPendingSet((WorkerJob*)token); 88 | mUring.prepAcceptMt(token, fd, addr, addrlen, flags); 89 | } 90 | auto prepConnect(Token token, int fd, sockaddr* addr, socklen_t addrlen) -> void 91 | { 92 | addPendingSet((WorkerJob*)token); 93 | mUring.prepConnect(token, fd, addr, addrlen); 94 | } 95 | template 96 | auto prepTimeout(Token token, std::chrono::duration duration) -> void 97 | { 98 | addPendingSet((WorkerJob*)token); 99 | mUring.prepAddTimeout(token, duration); 100 | } 101 | template 102 | auto prepUpdateTimeout(Token token, std::chrono::duration duration) -> void 103 | { 104 | addPendingSet((WorkerJob*)token); 105 | mUring.prepUpdateTimeout(token, duration); 106 | } 107 | auto prepRemoveTimeout(Token token) -> void { mUring.prepRemoveTimeout(token); } 108 | auto prepRecvMsg(Token token, int fd, msghdr* msg, unsigned flag = 0) -> void 109 | { 110 | addPendingSet((WorkerJob*)token); 111 | mUring.prepRecvMsg(token, fd, msg, flag); 112 | } 113 | auto prepSendMsg(Token token, int fd, msghdr* msg, unsigned flag = 0) -> void 114 | { 115 | addPendingSet((WorkerJob*)token); 116 | mUring.prepSendMsg(token, fd, msg, flag); 117 | } 118 | auto prepRead(Token token, int fd, std::span buf, off_t offset) -> void 119 | { 120 | addPendingSet((WorkerJob*)token); 121 | mUring.prepRead(token, fd, buf, offset); 122 | } 123 | auto prepWrite(Token token, int fd, std::span buf, off_t offset) -> void 124 | { 125 | addPendingSet((WorkerJob*)token); 126 | mUring.prepWrite(token, fd, buf, offset); 127 | } 128 | auto prepCancel(int fd) -> void { mUring.prepCancel(fd); } 129 | auto prepCancel(Token token) -> void { mUring.prepCancel(token); } 130 | auto prepClose(Token token, int fd) -> void { mUring.prepClose(token, fd); } 131 | 132 | auto addCancel(CancelItem cancel) -> void 133 | { 134 | std::lock_guard lock(mCancelMt); 135 | mCancels.push_back(cancel); 136 | notify(); 137 | } 138 | 139 | auto wait() -> void 140 | { 141 | processCancel(); 142 | auto [jobs, count] = mTimerManager.processTimers(); 143 | while (auto job = jobs.popFront()) { 144 | runJob(job, {.ptr = nullptr}); 145 | } 146 | if (mNotifyBlocked.load(std::memory_order_acquire)) { // unblocked path 147 | submit(); 148 | processIoTasks(); 149 | mNotifyBlocked.store(false, std::memory_order_release); 150 | } else { 151 | auto future = mTimerManager.nextInstant(); 152 | auto duration = future - std::chrono::steady_clock::now(); 153 | mNotifyBlocked.store(false, std::memory_order_relaxed); 154 | std::atomic_thread_fence(std::memory_order_acq_rel); 155 | submitWait(duration); 156 | processIoTasks(); 157 | std::atomic_thread_fence(std::memory_order_acq_rel); 158 | mNotifyBlocked.store(true, std::memory_order_relaxed); 159 | } 160 | return; 161 | } 162 | 163 | auto delPendingSet(CancelItem item) -> void 164 | { 165 | std::lock_guard lock(mPendingSet); 166 | switch (item.mKind) { 167 | case CancelItem::Kind::IoFd: 168 | break; 169 | case CancelItem::Kind::TimeoutToken: 170 | mPendingJobs.erase((WorkerJob*)item.mToken); 171 | break; 172 | } 173 | doCancel(item); 174 | } 175 | 176 | private: 177 | template 178 | auto submitWait(std::chrono::duration duration) -> void 179 | { 180 | io_uring_cqe* cqe = nullptr; 181 | auto e = mUring.submitWait(cqe, duration); 182 | if (e == std::errc::stream_timeout) { 183 | // timeout 184 | } else if (e != std::errc(0)) { 185 | // error occured 186 | } else if (cqe != nullptr) { 187 | if (cqe->flags & IORING_CQE_F_MORE) { 188 | processMore(cqe); 189 | } else { 190 | addIoJob(cqe); 191 | } 192 | mUring.seen(cqe); 193 | } 194 | } 195 | 196 | auto submit() -> void 197 | { 198 | auto e = mUring.submit(); 199 | if (e != std::errc(0)) { 200 | assert(false); // error occured 201 | } 202 | std::uint32_t count = 0; 203 | std::uint32_t head = 0; 204 | ::io_uring_cqe* cqe = nullptr; 205 | io_uring_for_each_cqe(mUring.uring(), head, cqe) 206 | { 207 | if (cqe->flags & IORING_CQE_F_MORE) { 208 | processMore(cqe); 209 | } else { 210 | addIoJob(cqe); 211 | } 212 | count++; 213 | } 214 | mUring.advance(count); 215 | } 216 | 217 | auto processIoTasks() -> void 218 | { 219 | for (IoTask const& task : mIoTaskBuffer) { 220 | runJob(task.job, {.i32 = task.res}); 221 | } 222 | mIoTaskBuffer.clear(); 223 | } 224 | 225 | auto processCancel() -> void 226 | { 227 | std::lock_guard lock(mCancelMt); 228 | if (!mCancels.empty()) [[unlikely]] { 229 | while (!mCancels.empty()) { 230 | auto cancel = mCancels.back(); 231 | mCancels.pop_back(); 232 | doCancel(cancel); 233 | } 234 | auto r = mUring.submit(); 235 | assert(r == std::errc(0)); 236 | } 237 | } 238 | 239 | auto addPendingSet(WorkerJob* job) -> void 240 | { 241 | std::lock_guard lock(mPendingSet); 242 | mPendingJobs.insert(job); 243 | } 244 | 245 | auto processMore(::io_uring_cqe* cqe) -> void 246 | { 247 | if (cqe->user_data == 0) { 248 | 249 | } else { 250 | runJob((WorkerJob*)cqe->user_data, {.i32 = cqe->res}); 251 | } 252 | } 253 | 254 | auto addIoJob(::io_uring_cqe* cqe) noexcept -> void 255 | { 256 | auto job = (WorkerJob*)cqe->user_data; 257 | if (job != nullptr) { 258 | auto n = 0; 259 | { 260 | std::lock_guard lock(mPendingSet); 261 | n = mPendingJobs.erase(job); 262 | } 263 | if (n == 1) { 264 | auto r = mIoTaskBuffer.push_back({job, cqe->res}); 265 | if (r == false) { // task buffer full 266 | runJob(job, {.i32 = cqe->res}); 267 | } 268 | } 269 | } 270 | } 271 | 272 | auto doCancel(CancelItem item) noexcept -> void 273 | { 274 | switch (item.mKind) { 275 | case CancelItem::Kind::IoFd: 276 | mUring.prepCancel(item.mFd); 277 | break; 278 | case CancelItem::Kind::TimeoutToken: 279 | mUring.prepRemoveTimeout(item.mToken); 280 | break; 281 | } 282 | } 283 | 284 | struct IoTask { 285 | WorkerJob* job; 286 | int res; 287 | }; 288 | util::FixedVec mIoTaskBuffer; 289 | 290 | Executor* mExecutor; 291 | TimerManager mTimerManager{64}; 292 | IoUring mUring{}; 293 | 294 | std::mutex mPendingSet; 295 | std::unordered_set mPendingJobs; 296 | 297 | std::mutex mCancelMt; 298 | std::vector mCancels; 299 | std::atomic_bool mNotifyBlocked{false}; 300 | std::uint32_t mTid = -1; 301 | }; 302 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/runtime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/blocking_executor.hpp" 4 | #include "coco/inl_executor.hpp" 5 | #include "coco/mt_executor.hpp" 6 | #include "coco/sync/chain.hpp" 7 | 8 | namespace coco { 9 | enum class RuntimeKind { 10 | Inline, 11 | Multi, 12 | }; 13 | constexpr inline RuntimeKind MT = RuntimeKind::Multi; 14 | constexpr inline RuntimeKind INL = RuntimeKind::Inline; 15 | class Runtime { 16 | public: 17 | constexpr Runtime(RuntimeKind type, std::size_t threadNum = 4) : mBlockingThreadsMax(500), mBlocking(nullptr) 18 | { 19 | if (type == RuntimeKind::Inline) { 20 | mExecutor = std::make_shared(); 21 | } else if (type == RuntimeKind::Multi) { 22 | mExecutor = std::make_shared(threadNum); 23 | } 24 | } 25 | 26 | struct [[nodiscard]] JoinAwaiter { 27 | JoinAwaiter(std::atomic* done) : mDone(done) {} 28 | auto await_ready() const noexcept -> bool { return false; } 29 | template 30 | auto await_suspend(std::coroutine_handle handle) noexcept -> bool 31 | { 32 | auto job = handle.promise().getThisJob(); 33 | WorkerJob* expected = &detail::kEmptyJob; 34 | if (mDone->compare_exchange_strong(expected, job)) { 35 | return true; 36 | } else { 37 | return false; 38 | } 39 | } 40 | auto await_resume() const noexcept -> void {} 41 | 42 | std::atomic* mDone; 43 | }; 44 | template 45 | struct JoinHandle { 46 | JoinHandle() noexcept = default; 47 | JoinHandle(TaskTy&& task) noexcept : mTask(std::forward(task)) 48 | { 49 | mTask.promise().setNextJob(&detail::kEmptyJob); 50 | mDone = &mTask.promise().getNextJob(); 51 | Proactor::get().execute(mTask.promise().getThisJob(), ExeOpt::balance()); 52 | } 53 | JoinHandle(JoinHandle&& other) noexcept 54 | : mDone(std::exchange(other.mDone, nullptr)), mTask(std::move(other.mTask)){}; 55 | auto operator=(JoinHandle&& other) noexcept -> JoinHandle& = default; 56 | ~JoinHandle() noexcept 57 | { 58 | if (mDone != nullptr && mTask.handle() != nullptr && mDone->load() == &detail::kEmptyJob) { 59 | assert(false && "looks like you forget to call join()"); 60 | } 61 | } 62 | [[nodiscard]] auto join() { return JoinAwaiter(mDone); } 63 | 64 | auto result() -> decltype(auto) { return mTask.promise().result(); } 65 | 66 | std::atomic* mDone; 67 | TaskTy mTask; 68 | }; 69 | 70 | template 71 | [[nodiscard]] constexpr auto spawn(TaskTy&& task) -> JoinHandle 72 | { 73 | return JoinHandle(std::move(task)); 74 | } 75 | 76 | template 77 | [[nodiscard]] auto waitAll(std::span> handles) -> Task<> 78 | { 79 | for (auto& handle : handles) { 80 | co_await handle.join(); 81 | } 82 | co_return; 83 | } 84 | 85 | template 86 | [[nodiscard]] auto waitAll(JoinHandles&&... handles) -> Task<> 87 | { 88 | if constexpr (sizeof...(handles) == 0) { 89 | co_return; 90 | } else { 91 | (co_await handles.join(), ...); 92 | } 93 | co_return; 94 | } 95 | 96 | template 97 | [[nodiscard]] auto waitAll(TasksTy&&... tasks) -> Task<> 98 | { 99 | auto tasksTuple = std::make_tuple(std::forward(tasks)...); 100 | constexpr auto taskCount = std::tuple_size_v; 101 | auto joinHandles = std::vector>>(); 102 | joinHandles.reserve(taskCount); 103 | std::apply([&](auto&&... task) { (joinHandles.push_back(spawn(std::move(task))), ...); }, tasksTuple); 104 | co_return co_await waitAll(joinHandles); 105 | } 106 | 107 | auto block(Task<> task) -> void { mExecutor->runMain(std::move(task)); } 108 | 109 | struct [[nodiscard]] SleepAwaiter { 110 | SleepAwaiter(Instant instant) : mInstant(instant) {} 111 | 112 | auto await_ready() const noexcept -> bool { return false; } 113 | template 114 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 115 | { 116 | auto job = handle.promise().getThisJob(); 117 | mPromise = &handle.promise(); 118 | Proactor::get().addTimer(mInstant, job); 119 | } 120 | auto await_resume() const noexcept -> void {} 121 | 122 | private: 123 | PromiseBase* mPromise; 124 | Instant mInstant; 125 | }; 126 | template 127 | auto sleepFor(std::chrono::duration duration) -> Task<> 128 | { 129 | if (duration.count() == 0) { 130 | co_return; 131 | } 132 | auto now = std::chrono::steady_clock::now(); 133 | co_await SleepAwaiter(now + std::chrono::duration_cast(duration)); 134 | } 135 | auto sleepUntil(Instant time) -> Task<> 136 | { 137 | auto now = std::chrono::steady_clock::now(); 138 | if (time <= now) { 139 | co_return; 140 | } 141 | co_await SleepAwaiter(time); 142 | } 143 | 144 | template 145 | struct BlockOnJob : WorkerJob { 146 | using result_type = std::invoke_result_t; 147 | BlockOnJob(Runtime* rt, PromiseBase* next, FnTy&& fn) 148 | : WorkerJob(run, nullptr), mRuntime(rt), mNextPromise(next), mFn(std::forward(fn)) 149 | { 150 | } 151 | 152 | static auto run(WorkerJob* job, WorkerArg /* arg */) noexcept -> void 153 | { 154 | auto self = static_cast(job); 155 | try { 156 | self->mResult = self->mFn(); 157 | } catch (...) { 158 | self->mNextPromise->setExeception(std::current_exception()); 159 | } 160 | self->mRuntime->mExecutor.get()->execute(self->mNextPromise->getThisJob(), ExeOpt::balance()); 161 | } 162 | result_type mResult; 163 | FnTy mFn; 164 | Runtime* mRuntime; 165 | PromiseBase* mNextPromise; 166 | }; 167 | 168 | template 169 | struct [[nodiscard]] BlockOnAwaiter { 170 | using result_type = std::invoke_result_t; 171 | BlockOnAwaiter(Runtime* rt, Fn&& fn) : mJob(rt, nullptr, std::forward(fn)) {} 172 | 173 | auto await_ready() const noexcept -> bool { return false; } 174 | template 175 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 176 | { 177 | mJob.mNextPromise = &handle.promise(); 178 | mJob.mRuntime->mBlocking.get()->execute(&mJob); 179 | } 180 | auto await_resume() const -> result_type 181 | { 182 | if (mJob.mNextPromise->hasException()) { 183 | std::rethrow_exception(mJob.mNextPromise->currentException()); 184 | } 185 | return std::move(mJob.mResult); 186 | } 187 | 188 | BlockOnJob mJob; 189 | }; 190 | 191 | // FIXME !! this function is not exception safe, i guess 192 | template 193 | [[nodiscard]] auto blockOn(Fn&& fn, Args... args) 194 | { 195 | if (mBlocking == nullptr) { 196 | std::call_once(mBlockingOnceFlag, [&]() { mBlocking = std::make_shared(mBlockingThreadsMax); }); 197 | } 198 | return BlockOnAwaiter{this, [&]() -> decltype(auto) { return fn(args...); }}; 199 | } 200 | 201 | auto spawnDetach(Task<> task) -> void 202 | { 203 | task.promise().setDetach(); 204 | mExecutor.get()->execute(task.promise().getThisJob(), ExeOpt::balance()); 205 | [[maybe_unused]] auto dummy = task.take(); 206 | } 207 | 208 | struct [[nodiscard]] waitChainAwaiter { 209 | waitChainAwaiter(Runtime& runtime, sync::Chain&& chain) noexcept : mRuntime(runtime), mChain(std::move(chain)) {} 210 | 211 | auto await_ready() const noexcept -> bool { return false; } 212 | template 213 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 214 | { 215 | mChain.chain(handle.promise().getThisJob()); 216 | mRuntime.execute(mChain.takeQueue(), ExeOpt::balance()); 217 | } 218 | auto await_resume() const noexcept -> void {} 219 | Runtime& mRuntime; 220 | sync::Chain mChain; 221 | }; 222 | 223 | auto wait(sync::Chain&& chain) -> decltype(auto) { return waitChainAwaiter(*this, std::move(chain)); } 224 | 225 | private: 226 | auto execute(WorkerJob* job, ExeOpt opt) noexcept -> void { mExecutor.get()->execute(job, opt); } 227 | auto execute(WorkerJobQueue queue, ExeOpt opt) noexcept -> void 228 | { 229 | mExecutor.get()->execute(std::move(queue), 0, opt); 230 | } 231 | std::shared_ptr mExecutor; 232 | std::shared_ptr mBlocking; 233 | std::once_flag mBlockingOnceFlag; 234 | std::uint32_t mBlockingThreadsMax; 235 | }; 236 | template 237 | using JoinHandle = Runtime::JoinHandle; 238 | }; // namespace coco -------------------------------------------------------------------------------- /include/coco/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/sync/mutex.hpp" 3 | #include "coco/sync/channel.hpp" 4 | #include "coco/sync/rwlock.hpp" 5 | #include "coco/sync/condvar.hpp" 6 | #include "coco/sync/latch.hpp" -------------------------------------------------------------------------------- /include/coco/sync/chain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/task.hpp" 3 | // guarantee all the tasks in the chain will be executed in order and in the same thread 4 | namespace coco { 5 | class Runtime; 6 | } 7 | namespace coco::sync { 8 | class Chain { 9 | public: 10 | Chain() noexcept = default; 11 | Chain(void* ctx) noexcept : mCtx(ctx){}; 12 | Chain(Chain const&) = delete; 13 | auto operator=(Chain const&) -> Chain& = delete; 14 | Chain(Chain&&) = default; 15 | auto operator=(Chain&&) -> Chain& = default; 16 | ~Chain() noexcept { assert(mQueue.empty() && "Chain must be empty"); } 17 | auto withCtx(void* ctx) noexcept -> Chain& 18 | { 19 | mCtx = ctx; 20 | return *this; 21 | } 22 | auto chain(Task<> taskFn(void* ctx)) noexcept -> Chain& 23 | { 24 | auto task = taskFn(mCtx); 25 | task.promise().setDetach(); 26 | mQueue.pushBack(task.promise().getThisJob()); 27 | [[maybe_unused]] auto t = task.take(); 28 | return *this; 29 | } 30 | 31 | private: 32 | friend class ::coco::Runtime; 33 | auto chain(WorkerJob* job) noexcept -> void { mQueue.pushBack(job); } 34 | auto takeQueue() noexcept -> WorkerJobQueue { return std::move(mQueue); } 35 | 36 | void* mCtx = nullptr; 37 | WorkerJobQueue mQueue; 38 | }; 39 | } // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sync/channel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace coco::sync { 7 | template 8 | class Channel; 9 | namespace detail { 10 | template 11 | struct ChannelReadAwaiter { 12 | auto await_ready() noexcept -> bool { return false; } 13 | template 14 | auto await_suspend(std::coroutine_handle handle) noexcept; 15 | auto await_resume() noexcept -> std::optional; 16 | 17 | std::optional mVal; 18 | Channel& mChannel; 19 | }; 20 | template 21 | struct ChannelWriteAwaiter { 22 | auto await_ready() noexcept -> bool { return false; } 23 | template 24 | auto await_suspend(std::coroutine_handle handle) noexcept; 25 | auto await_resume() noexcept -> bool; 26 | 27 | T const* mVal; 28 | Channel& mChannel; 29 | }; 30 | }; // namespace detail 31 | 32 | template 33 | class Channel { 34 | public: 35 | Channel() : mBuffer() {} 36 | 37 | auto read() noexcept { return detail::ChannelReadAwaiter{std::nullopt, *this}; } 38 | auto write(T&& in) noexcept { return detail::ChannelWriteAwaiter{&in, *this}; } 39 | auto write(T const& in) noexcept { return detail::ChannelWriteAwaiter{&in, *this}; } 40 | 41 | auto empty() noexcept -> bool { return mBuffer.empty(); } 42 | auto full() noexcept -> bool { return mBuffer.full(); } 43 | auto close() noexcept 44 | { 45 | std::scoped_lock lk(mMt); 46 | mClosed = true; 47 | Proactor::get().execute(std::move(mReaders), ExeOpt::balance()); 48 | Proactor::get().execute(std::move(mWriter), ExeOpt::balance()); 49 | } 50 | 51 | private: 52 | friend struct detail::ChannelReadAwaiter; 53 | friend struct detail::ChannelWriteAwaiter; 54 | 55 | std::mutex mMt; 56 | WorkerJobQueue mReaders; 57 | WorkerJobQueue mWriter; 58 | RingBuffer mBuffer; 59 | bool mClosed = false; 60 | }; 61 | 62 | namespace detail { 63 | template 64 | template 65 | auto ChannelReadAwaiter::await_suspend(std::coroutine_handle handle) noexcept 66 | { 67 | std::scoped_lock lk(mChannel.mMt); 68 | if (mChannel.mClosed) { 69 | return false; 70 | } 71 | if (mChannel.mBuffer.empty()) { 72 | mChannel.mReaders.pushBack(handle.promise().getThisJob()); 73 | WorkerJobQueue tmp; 74 | for (int i = 0; i < mChannel.mBuffer.size() && !mChannel.mWriter.empty(); i++) { 75 | tmp.pushBack(mChannel.mWriter.popFront()); 76 | } 77 | Proactor::get().execute(std::move(tmp), ExeOpt::prefInOne()); 78 | return true; 79 | } else { 80 | mVal = mChannel.mBuffer.pop(); 81 | assert(mVal.has_value()); 82 | WorkerJobQueue tmp; 83 | for (int i = 0; i < mChannel.mBuffer.size() && !mChannel.mWriter.empty(); i++) { 84 | tmp.pushBack(mChannel.mWriter.popFront()); 85 | } 86 | Proactor::get().execute(std::move(tmp), ExeOpt::prefInOne()); 87 | return false; 88 | } 89 | } 90 | 91 | template 92 | auto ChannelReadAwaiter::await_resume() noexcept -> std::optional 93 | { 94 | std::scoped_lock lk(mChannel.mMt); 95 | if (mChannel.mClosed) { 96 | return std::nullopt; 97 | } 98 | if (!mVal.has_value()) { 99 | mVal = mChannel.mBuffer.pop(); 100 | assert(mVal.has_value()); 101 | } 102 | return std::move(mVal); 103 | } 104 | 105 | // ChannelWriteAwaiter 106 | 107 | template 108 | template 109 | auto ChannelWriteAwaiter::await_suspend(std::coroutine_handle handle) noexcept 110 | { 111 | std::scoped_lock lk(mChannel.mMt); 112 | if (mChannel.mClosed) { 113 | return false; 114 | } 115 | if (mChannel.mBuffer.full()) { 116 | mChannel.mWriter.pushBack(handle.promise().getThisJob()); 117 | WorkerJobQueue tmp; 118 | for (int i = 0; i < mChannel.mBuffer.size() && !mChannel.mReaders.empty(); i++) { 119 | tmp.pushBack(mChannel.mReaders.popFront()); 120 | } 121 | Proactor::get().execute(std::move(tmp), ExeOpt::prefInOne()); 122 | return true; 123 | } else { 124 | mChannel.mBuffer.push(std::move(*mVal)); 125 | mVal = nullptr; 126 | WorkerJobQueue tmp; 127 | for (int i = 0; i < mChannel.mBuffer.size() && !mChannel.mReaders.empty(); i++) { 128 | tmp.pushBack(mChannel.mReaders.popFront()); 129 | } 130 | Proactor::get().execute(std::move(tmp), ExeOpt::prefInOne()); 131 | return false; 132 | } 133 | } 134 | 135 | template 136 | auto ChannelWriteAwaiter::await_resume() noexcept -> bool 137 | { 138 | std::scoped_lock lk(mChannel.mMt); 139 | if (mChannel.mClosed) { 140 | return false; 141 | } 142 | if (mVal != nullptr) { 143 | mChannel.mBuffer.push(std::move(*mVal)); 144 | mVal = nullptr; 145 | } 146 | return true; 147 | } 148 | } // namespace detail 149 | }; // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sync/condvar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/sync/mutex.hpp" 3 | #include 4 | 5 | namespace coco::sync { 6 | class CondVar; 7 | 8 | namespace detail { 9 | struct [[nodiscard]] CondVarWaitAwaiter { 10 | constexpr auto await_ready() const noexcept -> bool { return false; } 11 | template 12 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 13 | constexpr auto await_resume() const noexcept -> void {} 14 | 15 | CondVar& mCv; 16 | }; 17 | }; // namespace detail 18 | 19 | class CondVar { 20 | public: 21 | auto wait() { return detail::CondVarWaitAwaiter{*this}; } 22 | auto notifyOne() -> void 23 | { 24 | WorkerJob* job = nullptr; 25 | mQueueMt.lock(); 26 | if (!mWaitQueue.empty()) { 27 | job = mWaitQueue.popFront(); 28 | } 29 | mQueueMt.unlock(); 30 | if (job) { 31 | Proactor::get().execute(job, ExeOpt::prefInOne()); 32 | } 33 | } 34 | auto notifyAll() -> void 35 | { 36 | mQueueMt.lock(); 37 | while (!mWaitQueue.empty()) { 38 | auto job = mWaitQueue.popFront(); 39 | mQueueMt.unlock(); 40 | Proactor::get().execute(job, ExeOpt::balance()); 41 | mQueueMt.lock(); 42 | } 43 | } 44 | 45 | private: 46 | friend struct detail::CondVarWaitAwaiter; 47 | 48 | coco::WorkerJobQueue mWaitQueue; 49 | std::mutex mQueueMt; 50 | }; 51 | 52 | namespace detail { 53 | template 54 | auto CondVarWaitAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 55 | { 56 | mCv.mQueueMt.lock(); 57 | mCv.mWaitQueue.pushBack(awaiting.promise().getThisJob()); 58 | mCv.mQueueMt.unlock(); 59 | return true; 60 | } 61 | } // namespace detail 62 | } // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sync/latch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/task.hpp" 3 | #include "coco/util/panic.hpp" 4 | 5 | namespace coco::sync { 6 | class Latch; 7 | namespace detail { 8 | struct [[nodiscard]] LatchWaitAwaiter { 9 | LatchWaitAwaiter(Latch& latch) : mLatch(latch) {} 10 | 11 | auto await_ready() const noexcept -> bool; 12 | template 13 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 14 | constexpr auto await_resume() const noexcept -> void {} 15 | 16 | Latch& mLatch; 17 | }; 18 | 19 | struct [[nodiscard]] LatchArriveAndWaitAwaiter { 20 | LatchArriveAndWaitAwaiter(Latch& latch) : mLatch(latch) {} 21 | 22 | auto await_ready() const noexcept -> bool; 23 | template 24 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 25 | constexpr auto await_resume() const noexcept -> void {} 26 | 27 | Latch& mLatch; 28 | }; 29 | } // namespace detail 30 | 31 | class Latch { 32 | public: 33 | Latch(std::size_t count) : mCount(count) {} 34 | auto countDown() -> void 35 | { 36 | if (mCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { 37 | wakeAll(); 38 | } 39 | } 40 | auto wait() { return detail::LatchWaitAwaiter(*this); } 41 | auto arriveAndWait() { return detail::LatchArriveAndWaitAwaiter(*this); } 42 | 43 | private: 44 | auto wakeAll() -> void 45 | { 46 | auto queue = mWaiting.popAll(); 47 | while (auto task = queue.popFront()) { 48 | Proactor::get().execute(task, ExeOpt::balance()); 49 | } 50 | } 51 | 52 | friend struct detail::LatchWaitAwaiter; 53 | friend struct detail::LatchArriveAndWaitAwaiter; 54 | 55 | util::AtomicQueue<&WorkerJob::next> mWaiting; 56 | std::atomic_size_t mCount; 57 | }; 58 | 59 | namespace detail { 60 | inline auto LatchWaitAwaiter::await_ready() const noexcept -> bool 61 | { 62 | if (mLatch.mCount.load(std::memory_order_acquire) == 0) { 63 | mLatch.wakeAll(); 64 | return true; 65 | } else { 66 | return false; 67 | }; 68 | } 69 | 70 | template 71 | auto LatchWaitAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 72 | { 73 | mLatch.mWaiting.pushFront(awaiting.promise().getThisJob()); 74 | return true; 75 | } 76 | 77 | inline auto LatchArriveAndWaitAwaiter::await_ready() const noexcept -> bool 78 | { 79 | if (mLatch.mCount.fetch_sub(1, std::memory_order_acquire) == 1) { 80 | mLatch.wakeAll(); 81 | return true; 82 | } else { 83 | return false; 84 | } 85 | } 86 | 87 | template 88 | inline auto LatchArriveAndWaitAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 89 | { 90 | mLatch.mWaiting.pushFront(awaiting.promise().getThisJob()); 91 | return true; 92 | } 93 | 94 | } // namespace detail 95 | } // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sync/mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/task.hpp" 4 | 5 | // TODO: not efficient 6 | namespace coco::sync { 7 | class Mutex; 8 | namespace detail { 9 | struct [[nodiscard]] MutexLockAwaiter { 10 | MutexLockAwaiter(Mutex& mt) : mMt(mt) {} 11 | auto await_ready() const noexcept -> bool; 12 | template 13 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> void; 14 | auto await_resume() const noexcept -> void {} 15 | 16 | Mutex& mMt; 17 | }; 18 | 19 | struct [[nodiscard]] MutexTryLockAwaiter { 20 | MutexTryLockAwaiter(Mutex& mt) : mMt(mt) {} 21 | auto await_ready() const noexcept -> bool; 22 | template 23 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 24 | auto await_resume() const noexcept -> bool; 25 | 26 | Mutex& mMt; 27 | bool mSuccess = false; 28 | }; 29 | }; // namespace detail 30 | 31 | class Mutex { 32 | public: 33 | Mutex() = default; 34 | ~Mutex() = default; 35 | 36 | auto lock() -> detail::MutexLockAwaiter { return detail::MutexLockAwaiter(*this); } 37 | auto tryLock() -> detail::MutexTryLockAwaiter { return detail::MutexTryLockAwaiter(*this); } 38 | auto unlock() -> void 39 | { 40 | mQueueMt.lock(); 41 | if (mWaitQueue.empty()) { 42 | mQueueMt.unlock(); 43 | mHold.store(false, std::memory_order_relaxed); 44 | std::atomic_thread_fence(std::memory_order_acq_rel); 45 | } else { 46 | auto expected = true; 47 | auto job = mWaitQueue.popFront(); 48 | mQueueMt.unlock(); 49 | assert(mHold.load(std::memory_order_relaxed) == true); 50 | mHold.store(true, std::memory_order_relaxed); 51 | std::atomic_thread_fence(std::memory_order_acq_rel); 52 | Proactor::get().execute(job, ExeOpt::prefInOne()); 53 | } 54 | } 55 | 56 | private: 57 | friend struct detail::MutexLockAwaiter; 58 | friend struct detail::MutexTryLockAwaiter; 59 | 60 | coco::WorkerJobQueue mWaitQueue; 61 | std::mutex mQueueMt; 62 | std::atomic_bool mHold = false; 63 | }; 64 | 65 | namespace detail { 66 | inline auto MutexLockAwaiter::await_ready() const noexcept -> bool 67 | { 68 | auto expected = false; 69 | if (mMt.mHold.compare_exchange_strong(expected, true)) { 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | template 77 | auto MutexLockAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> void 78 | { 79 | std::scoped_lock lk(mMt.mQueueMt); 80 | auto job = awaiting.promise().getThisJob(); 81 | mMt.mWaitQueue.pushBack(job); 82 | } 83 | 84 | inline auto MutexTryLockAwaiter::await_ready() const noexcept -> bool { return false; } 85 | template 86 | auto MutexTryLockAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 87 | { 88 | auto expected = false; 89 | if (mMt.mHold.compare_exchange_strong(expected, true)) { 90 | mSuccess = true; 91 | return false; 92 | } else { 93 | mSuccess = false; 94 | return false; 95 | }; 96 | } 97 | inline auto MutexTryLockAwaiter::await_resume() const noexcept -> bool { return mSuccess; } 98 | }; // namespace detail 99 | } // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sync/rwlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/task.hpp" 4 | #include "coco/util/panic.hpp" 5 | 6 | namespace coco::sync { 7 | class RwLock; 8 | namespace detail { 9 | struct RwLockReadAwaiter { 10 | RwLockReadAwaiter(RwLock& lock) : mLock(lock) {} 11 | 12 | auto await_ready() const noexcept -> bool; 13 | template 14 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 15 | auto await_resume() const noexcept -> void {} 16 | 17 | RwLock& mLock; 18 | }; 19 | 20 | struct RwLockWriteAwaiter { 21 | RwLockWriteAwaiter(RwLock& lock) : mLock(lock) {} 22 | 23 | auto await_ready() const noexcept -> bool; 24 | template 25 | auto await_suspend(std::coroutine_handle awaiting) noexcept -> bool; 26 | auto await_resume() const noexcept -> void {} 27 | 28 | RwLock& mLock; 29 | }; 30 | } // namespace detail 31 | 32 | class RwLock { 33 | public: 34 | RwLock() = default; 35 | ~RwLock() = default; 36 | 37 | auto lockRead() { return detail::RwLockReadAwaiter(*this); } 38 | auto unlockRead() 39 | { 40 | auto writers = popWriter(); 41 | if (writers != nullptr) { 42 | mState.store(LockState::Write, std::memory_order_relaxed); 43 | std::atomic_thread_fence(std::memory_order_acq_rel); 44 | Proactor::get().execute(writers, ExeOpt::prefInOne()); 45 | } else { 46 | auto readers = popReaders(); 47 | if (!readers.empty()) { 48 | Proactor::get().execute(std::move(readers), ExeOpt::prefInOne()); 49 | } else { 50 | mState.store(LockState::Free, std::memory_order_relaxed); 51 | std::atomic_thread_fence(std::memory_order_acq_rel); 52 | } 53 | } 54 | } 55 | 56 | auto lockWrite() { return detail::RwLockWriteAwaiter(*this); } 57 | auto unlockWrite() 58 | { 59 | auto writer = popWriter(); 60 | if (writer != nullptr) { 61 | Proactor::get().execute(writer, ExeOpt::prefInOne()); 62 | } else { 63 | auto readers = popReaders(); 64 | if (!readers.empty()) { 65 | mState.store(LockState::Read, std::memory_order_relaxed); 66 | std::atomic_thread_fence(std::memory_order_acq_rel); 67 | Proactor::get().execute(std::move(readers), ExeOpt::prefInOne()); 68 | } else { 69 | mState.store(LockState::Free, std::memory_order_relaxed); 70 | std::atomic_thread_fence(std::memory_order_acq_rel); 71 | } 72 | } 73 | } 74 | 75 | private: 76 | friend struct detail::RwLockReadAwaiter; 77 | friend struct detail::RwLockWriteAwaiter; 78 | 79 | auto unlock() -> void {} 80 | 81 | auto popWriter() -> WorkerJob* 82 | { 83 | std::scoped_lock lk(mMt); 84 | return mWriters.popFront(); 85 | } 86 | 87 | auto popReaders() -> WorkerJobQueue 88 | { 89 | std::scoped_lock lk(mMt); 90 | return std::exchange(mReaders, {}); 91 | } 92 | 93 | enum class LockState { 94 | Free, 95 | Read, 96 | Write, 97 | }; 98 | 99 | std::mutex mMt; 100 | WorkerJobQueue mWriters; 101 | WorkerJobQueue mReaders; 102 | std::atomic mState = LockState::Free; 103 | }; 104 | 105 | namespace detail { 106 | inline auto RwLockReadAwaiter::await_ready() const noexcept -> bool 107 | { 108 | auto expected = RwLock::LockState::Free; 109 | if (mLock.mState.compare_exchange_strong(expected, RwLock::LockState::Read)) { 110 | return true; 111 | } else if (expected == RwLock::LockState::Read) { 112 | return true; 113 | } else { 114 | return false; 115 | } 116 | } 117 | 118 | template 119 | inline auto RwLockReadAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 120 | { 121 | std::scoped_lock lk(mLock.mMt); 122 | mLock.mReaders.pushBack(awaiting.promise().getThisJob()); 123 | return true; 124 | } 125 | 126 | inline auto RwLockWriteAwaiter::await_ready() const noexcept -> bool 127 | { 128 | auto expected = RwLock::LockState::Free; 129 | if (mLock.mState.compare_exchange_strong(expected, RwLock::LockState::Write)) { 130 | return true; 131 | } else { 132 | return false; 133 | } 134 | } 135 | 136 | template 137 | inline auto RwLockWriteAwaiter::await_suspend(std::coroutine_handle awaiting) noexcept -> bool 138 | { 139 | std::scoped_lock lk(mLock.mMt); 140 | mLock.mWriters.pushBack(awaiting.promise().getThisJob()); 141 | return true; 142 | } 143 | 144 | } // namespace detail 145 | } // namespace coco::sync -------------------------------------------------------------------------------- /include/coco/sys/fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace coco::sys { 4 | inline auto lastErrc() -> std::errc { return std::errc(errno); } 5 | 6 | class Fd { 7 | public: 8 | Fd() noexcept : mFd(-1) {} 9 | Fd(int fd) noexcept : mFd(fd) {} 10 | Fd(Fd&& other) noexcept : mFd(std::exchange(other.mFd, -1)) {} 11 | auto operator=(Fd&& other) noexcept -> Fd& 12 | { 13 | mFd = std::exchange(other.mFd, -1); 14 | return *this; 15 | } 16 | 17 | auto close() noexcept -> std::errc 18 | { 19 | auto err = std::errc{0}; 20 | if (mFd >= 0) { 21 | if (::close(mFd) == -1) { 22 | err = lastErrc(); 23 | } 24 | mFd = -1; 25 | } 26 | return err; 27 | } 28 | auto fd() const noexcept -> int { return mFd; } 29 | auto valid() const noexcept -> bool { return mFd >= 0; } 30 | 31 | protected: 32 | int mFd; 33 | }; 34 | } // namespace coco::sys -------------------------------------------------------------------------------- /include/coco/sys/file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/sys/fd.hpp" 4 | #include "coco/sys/file_awaiters.hpp" 5 | 6 | namespace coco::sys { 7 | class File : public Fd { 8 | public: 9 | File() noexcept = default; 10 | File(int fd) noexcept : Fd(fd) {} 11 | File(File&& file) noexcept = default; 12 | auto operator=(File&& file) noexcept -> File& = default; 13 | ~File() noexcept { close(); } 14 | 15 | static auto open(char const* path, int flags, mode_t mode = 0) noexcept -> std::pair 16 | { 17 | auto fd = ::open(path, flags, mode); 18 | if (fd < 0) { 19 | return {File(), lastErrc()}; 20 | } else { 21 | return {File(fd), std::errc(0)}; 22 | } 23 | } 24 | auto read(std::span buf, off_t offset) noexcept -> decltype(auto) 25 | { 26 | return detail::ReadAwaiter(mFd, buf, offset); 27 | } 28 | auto write(std::span buf, off_t offset) noexcept -> decltype(auto) 29 | { 30 | return detail::WriteAwaiter(mFd, buf, offset); 31 | } 32 | }; 33 | } // namespace coco::sys 34 | -------------------------------------------------------------------------------- /include/coco/sys/file_awaiters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/proactor.hpp" 4 | #include "coco/sys/socket_awaiters.hpp" // for IoJob 5 | #include 6 | 7 | namespace coco::sys::detail { 8 | struct [[nodiscard]] FileAwaiter { 9 | FileAwaiter(int fd) noexcept : mFd(fd) {} 10 | auto await_ready() const noexcept -> bool { return false; } 11 | int mFd; 12 | }; 13 | 14 | struct [[nodiscard]] ReadAwaiter : FileAwaiter { 15 | ReadAwaiter(int fd, std::span buf, off_t offset) noexcept 16 | : FileAwaiter(fd), mIoJob(nullptr), mBuf(buf), mOffset(offset) 17 | { 18 | } 19 | 20 | template 21 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 22 | { 23 | auto job = &handle.promise(); 24 | mIoJob.mPending = job; 25 | Proactor::get().prepRead(&mIoJob, mFd, mBuf, mOffset); 26 | } 27 | auto await_resume() noexcept -> std::pair 28 | { 29 | if (mIoJob.mResult < 0) { 30 | return {0, std::errc(-mIoJob.mResult)}; 31 | } else { 32 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 33 | } 34 | } 35 | 36 | IoJob mIoJob; 37 | off_t mOffset; 38 | std::span mBuf; 39 | }; 40 | 41 | struct [[nodiscard]] WriteAwaiter : FileAwaiter { 42 | WriteAwaiter(int fd, std::span buf, off_t offset) noexcept 43 | : FileAwaiter(fd), mIoJob(nullptr), mBuf(buf), mOffset(offset) 44 | { 45 | } 46 | 47 | template 48 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 49 | { 50 | auto job = &handle.promise(); 51 | mIoJob.mPending = job; 52 | Proactor::get().prepWrite(&mIoJob, mFd, mBuf, mOffset); 53 | } 54 | auto await_resume() noexcept -> std::pair 55 | { 56 | if (mIoJob.mResult < 0) { 57 | return {0, std::errc(-mIoJob.mResult)}; 58 | } else { 59 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 60 | } 61 | } 62 | 63 | IoJob mIoJob; 64 | off_t mOffset; 65 | std::span mBuf; 66 | }; 67 | } // namespace coco::sys::detail -------------------------------------------------------------------------------- /include/coco/sys/listener.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "socket.hpp" 4 | #include "stream.hpp" 5 | 6 | namespace coco::sys { 7 | class TcpListener : private Socket { 8 | public: 9 | TcpListener() noexcept = default; 10 | TcpListener(TcpListener&& listener) noexcept = default; 11 | 12 | static auto bind(SocketAddr const& addr) -> std::pair 13 | { 14 | auto [socket, errc] = Socket::create(addr, Socket::Stream); 15 | if (errc != std::errc{0}) { 16 | return {TcpListener(), errc}; 17 | } 18 | int opt = 1; 19 | errc = socket.setopt(SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 20 | if (errc != std::errc{0}) { 21 | return {TcpListener(), errc}; 22 | } 23 | errc = socket.bind(addr); 24 | if (errc != std::errc{0}) { 25 | return {TcpListener(), errc}; 26 | } 27 | errc = socket.listen(128); 28 | if (errc != std::errc{0}) { 29 | return {TcpListener(), errc}; 30 | } 31 | return {TcpListener(std::move(socket)), std::errc{0}}; 32 | } 33 | 34 | auto accept() noexcept -> Task> 35 | { 36 | auto [socket, errc] = co_await Socket::accept(); 37 | if (errc != std::errc{0}) { 38 | co_return {TcpStream(), errc}; 39 | } 40 | co_return {TcpStream::from(std::move(socket)), std::errc{0}}; 41 | } 42 | template 43 | auto accept(std::chrono::duration timeout) noexcept -> Task> 44 | { 45 | auto [socket, errc] = co_await Socket::accept(timeout); 46 | if (errc != std::errc{0}) { 47 | co_return {TcpStream(), errc}; 48 | } 49 | co_return {TcpStream::from(std::move(socket)), std::errc{0}}; 50 | } 51 | 52 | auto recv(std::span buf) noexcept -> decltype(auto) { return Socket::recv(buf); } 53 | auto send(std::span buf) noexcept -> decltype(auto) { return Socket::send(buf); } 54 | auto sendTimeout(std::span buf, std::chrono::milliseconds timeout) noexcept -> decltype(auto) 55 | { 56 | return Socket::send(buf, timeout); 57 | } 58 | auto recvTimeout(std::span buf, std::chrono::milliseconds timeout) noexcept -> decltype(auto) 59 | { 60 | return Socket::recv(buf, timeout); 61 | } 62 | auto close() noexcept -> decltype(auto) { return Socket::close(); } 63 | 64 | private: 65 | TcpListener(Socket&& socket) noexcept : Socket(std::move(socket)) {} 66 | }; 67 | } // namespace coco::sys -------------------------------------------------------------------------------- /include/coco/sys/socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "coco/sys/fd.hpp" 8 | #include "coco/sys/socket_addr.hpp" 9 | #include "coco/sys/socket_awaiters.hpp" 10 | 11 | namespace coco::sys { 12 | 13 | class Socket : public Fd { 14 | public: 15 | Socket() noexcept = default; 16 | Socket(int fd) noexcept : Fd(fd) {} 17 | Socket(Socket&& socket) noexcept = default; 18 | auto operator=(Socket&& socket) noexcept -> Socket& = default; 19 | ~Socket() noexcept 20 | { 21 | if (mFd != -1) [[unlikely]] { 22 | auto r = Fd::close(); 23 | } 24 | } 25 | 26 | enum Kind { Stream, Datagram }; 27 | static auto create(SocketAddr const& addr, Kind kind) noexcept -> std::pair 28 | { 29 | int type = kind == Stream ? SOCK_STREAM : SOCK_DGRAM; 30 | if (addr.isIpv4()) { 31 | return create(AF_INET, type); 32 | } else if (addr.isIpv6()) { 33 | return create(AF_INET6, type); 34 | } else [[unlikely]] { 35 | return {-1, std::errc::invalid_argument}; 36 | } 37 | } 38 | static auto create(int domain, int type, int protocol = 0) noexcept -> std::pair 39 | { 40 | auto fd = ::socket(domain, type, protocol); 41 | if (fd == -1) { 42 | return {-1, lastErrc()}; 43 | } 44 | return {fd, std::errc{0}}; 45 | } 46 | 47 | auto bind(SocketAddr const& addr) noexcept -> std::errc 48 | { 49 | if (addr.isIpv4()) { 50 | sockaddr_in addr4; 51 | addr.setSys(addr4); 52 | return bind(reinterpret_cast(&addr4), sizeof(addr4)); 53 | } else if (addr.isIpv6()) { 54 | sockaddr_in6 addr6; 55 | addr.setSys(addr6); 56 | return bind(reinterpret_cast(&addr6), sizeof(addr6)); 57 | } else [[unlikely]] { 58 | return std::errc::invalid_argument; 59 | } 60 | } 61 | auto bind(sockaddr const* addr, socklen_t len) noexcept -> std::errc 62 | { 63 | if (::bind(mFd, addr, len) == -1) { 64 | return lastErrc(); 65 | } 66 | return std::errc{0}; 67 | } 68 | 69 | auto listen(int backlog) noexcept -> std::errc 70 | { 71 | if (::listen(mFd, backlog) == -1) { 72 | return lastErrc(); 73 | } 74 | return std::errc{0}; 75 | } 76 | 77 | auto recv(std::span buf, int flags = 0) noexcept -> decltype(auto) 78 | { 79 | return detail::RecvAwaiter(mFd, buf); 80 | } 81 | auto recv(std::span buf, Duration duration) noexcept -> decltype(auto) 82 | { 83 | return detail::RecvTimeoutAwaiter(mFd, buf, duration); 84 | } 85 | auto send(std::span buf, int flags = 0) noexcept -> decltype(auto) 86 | { 87 | return detail::SendAwaiter(mFd, buf); 88 | } 89 | auto send(std::span buf, Duration duration) noexcept -> decltype(auto) 90 | { 91 | return detail::SendTimeoutAwaiter(mFd, buf, duration); 92 | } 93 | auto sendTo(std::span buf, SocketAddr const& addr, int flags = 0) noexcept -> decltype(auto) 94 | { 95 | return detail::SendToAwaiter(mFd, buf, addr); 96 | } 97 | auto recvFrom(std::span buf, SocketAddr& addr, int flags = 0) noexcept -> decltype(auto) 98 | { 99 | return detail::RecvFromAwaiter(mFd, buf, addr); 100 | } 101 | auto connect(SocketAddr addr, Duration duration) noexcept -> decltype(auto) 102 | { 103 | return detail::ConnectTimeoutAwaiter(mFd, addr, duration); 104 | } 105 | auto accept(Duration duration) noexcept -> decltype(auto) { return detail::AcceptTimeoutAwaiter(mFd, duration); } 106 | auto accept(int flags = 0) noexcept -> decltype(auto) { return detail::AcceptAwaiter(mFd); } 107 | auto addAcceptMultishot(WorkerJob* job, int flags = 0) 108 | { 109 | return Proactor::get().prepAcceptMt(job, mFd, nullptr, nullptr, flags); 110 | } 111 | auto connect(SocketAddr addr) noexcept -> decltype(auto) { return detail::ConnectAwaiter(mFd, addr); } 112 | auto close() noexcept -> decltype(auto) { return detail::CloseAwaiter(mFd); } 113 | auto setopt(int level, int optname, void const* optval, socklen_t optlen) noexcept -> std::errc 114 | { 115 | if (::setsockopt(mFd, level, optname, optval, optlen) == -1) { 116 | return lastErrc(); 117 | } 118 | return std::errc{0}; 119 | } 120 | 121 | auto getopt(int level, int optname, void* optval, socklen_t* optlen) noexcept -> std::errc 122 | { 123 | if (::getsockopt(mFd, level, optname, optval, optlen) == -1) { 124 | return lastErrc(); 125 | } 126 | return std::errc{0}; 127 | } 128 | }; 129 | } // namespace coco::sys -------------------------------------------------------------------------------- /include/coco/sys/socket_addr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coco/util/panic.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace coco::sys { 8 | struct Ipv4Addr { 9 | constexpr Ipv4Addr() noexcept = default; 10 | constexpr Ipv4Addr(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) noexcept : mAddr{a, b, c, d} {} 11 | Ipv4Addr(std::uint32_t addr) noexcept : mAddr{*reinterpret_cast(&addr)} {} 12 | operator std::uint32_t() const noexcept { return *reinterpret_cast(mAddr); } 13 | 14 | constexpr auto isUnspecified() const noexcept -> bool { return *this == Ipv4Addr{}; } 15 | constexpr auto isLoopback() const noexcept -> bool { return mAddr[0] == 127; } 16 | 17 | constexpr static auto unspecified() noexcept -> Ipv4Addr { return Ipv4Addr{0, 0, 0, 0}; } 18 | constexpr static auto loopback() noexcept -> Ipv4Addr { return Ipv4Addr{127, 0, 0, 1}; } 19 | constexpr static auto broadcast() noexcept -> Ipv4Addr { return Ipv4Addr{255, 255, 255, 255}; } 20 | 21 | auto to_string() const -> std::string; 22 | auto operator==(Ipv4Addr const& other) const noexcept -> bool = default; 23 | 24 | std::uint8_t mAddr[4]; 25 | }; 26 | 27 | struct Ipv6Addr { 28 | constexpr Ipv6Addr() noexcept = default; 29 | constexpr Ipv6Addr(std::uint16_t a, std::uint16_t b, std::uint16_t c, std::uint16_t d, std::uint16_t e, 30 | std::uint16_t f, std::uint16_t g, std::uint16_t h) noexcept 31 | { 32 | // to big endian 33 | mAddr[0] = a >> 8; 34 | mAddr[1] = a & 0xFF; 35 | mAddr[2] = b >> 8; 36 | mAddr[3] = b & 0xFF; 37 | mAddr[4] = c >> 8; 38 | mAddr[5] = c & 0xFF; 39 | mAddr[6] = d >> 8; 40 | mAddr[7] = d & 0xFF; 41 | mAddr[8] = e >> 8; 42 | mAddr[9] = e & 0xFF; 43 | mAddr[10] = f >> 8; 44 | mAddr[11] = f & 0xFF; 45 | mAddr[12] = g >> 8; 46 | mAddr[13] = g & 0xFF; 47 | mAddr[14] = h >> 8; 48 | mAddr[15] = h & 0xFF; 49 | } 50 | 51 | constexpr auto isUnspecified() const noexcept -> bool { return *this == Ipv6Addr::unspecified(); } 52 | constexpr auto isLoopback() const noexcept -> bool { return *this == Ipv6Addr::loopback(); } 53 | // TODO: isMulticast 54 | constexpr static auto unspecified() noexcept -> Ipv6Addr { return Ipv6Addr{0, 0, 0, 0, 0, 0, 0, 0}; } 55 | constexpr static auto loopback() noexcept -> Ipv6Addr { return Ipv6Addr{0, 0, 0, 0, 0, 0, 0, 1}; } 56 | 57 | constexpr auto toSegments(std::span dst) const noexcept -> void 58 | { 59 | for (std::size_t i = 0; i < dst.size(); i++) { 60 | dst[i] = mAddr[i * 2] << 8 | mAddr[i * 2 + 1]; 61 | } 62 | } 63 | 64 | auto to_string() const -> std::string; 65 | auto operator==(Ipv6Addr const& other) const noexcept -> bool = default; 66 | 67 | std::uint8_t mAddr[16]; 68 | }; 69 | 70 | struct SocketAddrV4 { 71 | constexpr SocketAddrV4() noexcept = default; 72 | constexpr SocketAddrV4(Ipv4Addr addr, std::uint16_t port) noexcept : mAddr{addr}, mPort{port} {} 73 | constexpr auto ip() noexcept -> Ipv4Addr& { return mAddr; } 74 | constexpr auto port() noexcept -> std::uint16_t { return mPort; } 75 | 76 | constexpr static auto unspecified(std::uint16_t port) noexcept -> SocketAddrV4 77 | { 78 | return {Ipv4Addr::unspecified(), port}; 79 | } 80 | constexpr static auto loopback(std::uint16_t port) noexcept -> SocketAddrV4 { return {Ipv4Addr::loopback(), port}; } 81 | constexpr static auto broadcast(std::uint16_t port) noexcept -> SocketAddrV4 { return {Ipv4Addr::broadcast(), port}; } 82 | 83 | auto to_string() const -> std::string; 84 | auto operator==(SocketAddrV4 const& other) const noexcept -> bool = default; 85 | 86 | Ipv4Addr mAddr; 87 | std::uint16_t mPort; 88 | }; 89 | 90 | struct SocketAddrV6 { 91 | constexpr SocketAddrV6() noexcept = default; 92 | constexpr SocketAddrV6(Ipv6Addr addr, std::uint16_t port, std::uint32_t flowInfo, std::uint32_t scopeId) noexcept 93 | : mAddr{addr}, mPort{port}, mFlowInfo{flowInfo}, mScopeId{scopeId} 94 | { 95 | } 96 | constexpr auto ip() noexcept -> Ipv6Addr& { return mAddr; } 97 | constexpr auto port() noexcept -> std::uint16_t { return mPort; } 98 | constexpr auto flowInfo() noexcept -> std::uint32_t { return mFlowInfo; } 99 | constexpr auto scopeId() noexcept -> std::uint32_t { return mScopeId; } 100 | 101 | constexpr static auto loopback(std::uint16_t port, std::uint32_t flowInfo = 0, std::uint32_t scopeId = 0) noexcept 102 | -> SocketAddrV6 103 | { 104 | return {Ipv6Addr::loopback(), port, flowInfo, scopeId}; 105 | } 106 | constexpr static auto unspecified(std::uint16_t port, std::uint32_t flowInfo = 0, std::uint32_t scopeId = 0) noexcept 107 | -> SocketAddrV6 108 | { 109 | return {Ipv6Addr::unspecified(), port, flowInfo, scopeId}; 110 | } 111 | 112 | auto to_string() const -> std::string; 113 | auto operator==(SocketAddrV6 const& other) const noexcept -> bool = default; 114 | 115 | Ipv6Addr mAddr; 116 | std::uint16_t mPort; 117 | std::uint32_t mFlowInfo; 118 | std::uint32_t mScopeId; 119 | }; 120 | 121 | struct SocketAddr { 122 | SocketAddr(SocketAddrV4 addr) noexcept : mV4{addr}, mKind(V4) {} 123 | SocketAddr(SocketAddrV6 addr) noexcept : mV6{addr}, mKind(V6) {} 124 | 125 | auto domain() const noexcept -> int { return mKind == V4 ? AF_INET : AF_INET6; } 126 | auto port() const noexcept -> std::uint16_t { return mKind == V4 ? mV4.mPort : mV6.mPort; } 127 | auto setPort(std::uint16_t port) noexcept -> void 128 | { 129 | if (mKind == V4) { 130 | mV4.mPort = port; 131 | } else { 132 | mV6.mPort = port; 133 | } 134 | } 135 | auto isIpv4() const noexcept -> bool { return mKind == V4; } 136 | auto isIpv6() const noexcept -> bool { return mKind == V6; } 137 | auto setIp(Ipv4Addr const& addr) noexcept -> void 138 | { 139 | assert(mKind == V4); 140 | mV4.mAddr = addr; 141 | } 142 | auto setIp(Ipv6Addr const& addr) noexcept -> void 143 | { 144 | assert(mKind == V6); 145 | mV6.mAddr = addr; 146 | } 147 | 148 | auto setSys(sockaddr_in& out) const -> void 149 | { 150 | out.sin_family = AF_INET; 151 | out.sin_port = htons(port()); 152 | out.sin_addr.s_addr = mV4.mAddr; 153 | } 154 | auto setSys(sockaddr_in6& out) const -> void 155 | { 156 | out.sin6_family = AF_INET6; 157 | out.sin6_port = htons(port()); 158 | out.sin6_flowinfo = mV6.mFlowInfo; 159 | out.sin6_scope_id = mV6.mScopeId; 160 | std::memcpy(&out.sin6_addr, mV6.mAddr.mAddr, sizeof(out.sin6_addr)); 161 | } 162 | auto to_string() const -> std::string; 163 | 164 | private: 165 | friend struct std::formatter; 166 | 167 | union { 168 | SocketAddrV4 mV4; 169 | SocketAddrV6 mV6; 170 | }; 171 | enum { V4, V6 } mKind; 172 | }; 173 | } // namespace coco::sys 174 | 175 | template <> 176 | struct std::formatter { 177 | constexpr auto parse(auto& ctx) { return ctx.begin(); } 178 | 179 | template 180 | auto format(coco::sys::Ipv4Addr const& addr, FormatContext& ctx) const 181 | { 182 | return std::format_to(ctx.out(), "{}.{}.{}.{}", addr.mAddr[0], addr.mAddr[1], addr.mAddr[2], addr.mAddr[3]); 183 | } 184 | }; 185 | 186 | template <> 187 | struct std::formatter { 188 | constexpr auto parse(auto& ctx) { return ctx.begin(); } 189 | template 190 | auto format(coco::sys::Ipv6Addr const& addr, FormatContext& ctx) const 191 | { 192 | if (addr.isUnspecified()) { 193 | return std::format_to(ctx.out(), "::"); 194 | } else if (addr.isLoopback()) { 195 | return std::format_to(ctx.out(), "::1"); 196 | } else { 197 | std::array segments; 198 | addr.toSegments(segments); 199 | struct Span { 200 | std::uint32_t start; 201 | std::uint32_t len; 202 | }; 203 | 204 | Span longest{0, 0}; 205 | Span current{0, 0}; 206 | for (std::size_t i = 0; i < segments.size(); i++) { 207 | if (segments[i] == 0) { 208 | if (current.len == 0) { 209 | current.start = i; 210 | } 211 | current.len++; 212 | 213 | if (current.len > longest.len) { 214 | longest = current; 215 | } 216 | } else { 217 | current = {}; 218 | } 219 | } 220 | Span zeros = longest; 221 | auto out = ctx.out(); 222 | if (zeros.len > 1) { 223 | for (std::size_t i = 0; i < zeros.start; i++) { 224 | if (i == 0) { 225 | out = std::format_to(ctx.out(), "{:x}", segments[i]); 226 | } else { 227 | out = std::format_to(ctx.out(), ":{:x}", segments[i]); 228 | } 229 | } 230 | std::format_to(ctx.out(), "::"); 231 | for (std::size_t i = zeros.start + zeros.len; i < segments.size(); i++) { 232 | if (i == zeros.start + zeros.len) { 233 | out = std::format_to(ctx.out(), "{:x}", segments[i]); 234 | } else { 235 | out = std::format_to(ctx.out(), ":{:x}", segments[i]); 236 | } 237 | } 238 | } else { 239 | for (std::size_t i = 0; i < segments.size(); i++) { 240 | if (i == 0) { 241 | out = std::format_to(ctx.out(), "{:x}", segments[i]); 242 | } else { 243 | out = std::format_to(ctx.out(), ":{:x}", segments[i]); 244 | } 245 | } 246 | } 247 | return out; 248 | } 249 | } 250 | }; 251 | 252 | template <> 253 | struct std::formatter { 254 | constexpr auto parse(auto& ctx) { return ctx.begin(); } 255 | auto format(coco::sys::SocketAddrV4 const& addr, auto& ctx) const 256 | { 257 | return std::format_to(ctx.out(), "{}:{}", addr.mAddr, addr.mPort); 258 | } 259 | }; 260 | 261 | template <> 262 | struct std::formatter { 263 | constexpr auto parse(auto& ctx) { return ctx.begin(); } 264 | auto format(coco::sys::SocketAddrV6 const& addr, auto& ctx) const 265 | { 266 | if (addr.mFlowInfo == 0) { 267 | return std::format_to(ctx.out(), "[{}]:{}", addr.mAddr, addr.mPort); 268 | } else { 269 | return std::format_to(ctx.out(), "[{}%{}]:{}", addr.mAddr, addr.mScopeId, addr.mPort); 270 | } 271 | } 272 | }; 273 | 274 | template <> 275 | struct std::formatter { 276 | constexpr auto parse(auto& ctx) { return ctx.begin(); } 277 | auto format(coco::sys::SocketAddr const& addr, auto& ctx) const 278 | { 279 | if (addr.isIpv4()) { 280 | return std::format_to(ctx.out(), "{}", addr.mV4); 281 | } else { 282 | return std::format_to(ctx.out(), "{}", addr.mV6); 283 | } 284 | } 285 | }; -------------------------------------------------------------------------------- /include/coco/sys/socket_awaiters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/proactor.hpp" 4 | #include "coco/sys/socket_addr.hpp" 5 | #include "coco/task.hpp" 6 | 7 | #include 8 | 9 | namespace coco::sys::detail { 10 | template 11 | static auto convertTime(std::chrono::duration duration, ::timeval& out) noexcept -> void 12 | { 13 | auto sec = std::chrono::duration_cast(duration); 14 | auto nsec = std::chrono::duration_cast(duration - sec); 15 | out.tv_sec = sec.count(); 16 | out.tv_usec = nsec.count(); 17 | } 18 | 19 | struct [[nodiscard]] IoJob : WorkerJob { 20 | IoJob(PromiseBase* pending) : IoJob(pending, {}){}; 21 | IoJob(PromiseBase* pending, ExeOpt opt) : WorkerJob(&IoJob::run, nullptr), mPending(pending), mOpt(opt) {} 22 | static auto run(WorkerJob* job, WorkerArg args) noexcept -> void 23 | { 24 | auto self = static_cast(job); 25 | self->mResult = args.i32; 26 | auto* selfJob = self->mPending->getThisJob(); 27 | if (self->mOpt.mPri == ExeOpt::High) [[unlikely]] { 28 | Proactor::get().execute(selfJob, self->mOpt); 29 | } else { 30 | selfJob->run(selfJob, args); 31 | } 32 | } 33 | int mResult; 34 | ExeOpt mOpt; 35 | PromiseBase* mPending; 36 | }; 37 | 38 | struct SocketAwaiter { 39 | SocketAwaiter(int fd) noexcept : mFd(fd) {} 40 | auto await_ready() const noexcept -> bool { return false; } 41 | int mFd; 42 | }; 43 | 44 | struct [[nodiscard]] CloseAwaiter : SocketAwaiter { 45 | CloseAwaiter(int fd) noexcept : SocketAwaiter(fd), mIoJob(nullptr) {} 46 | template 47 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 48 | { 49 | auto promise = &handle.promise(); 50 | mIoJob.mPending = promise; 51 | Proactor::get().prepClose(&mIoJob, mFd); 52 | } 53 | auto await_resume() noexcept -> std::errc 54 | { 55 | if (mIoJob.mResult < 0) { 56 | return std::errc(-mIoJob.mResult); 57 | } else { 58 | return std::errc(0); 59 | } 60 | } 61 | IoJob mIoJob; 62 | }; 63 | 64 | struct [[nodiscard]] RecvAwaiter : SocketAwaiter { 65 | RecvAwaiter(int fd, std::span buf) noexcept : SocketAwaiter(fd), mIoJob(nullptr), mBuf(buf) {} 66 | 67 | template 68 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 69 | { 70 | auto promise = &handle.promise(); 71 | mIoJob.mPending = promise; 72 | 73 | Proactor::get().prepRecv(&mIoJob, mFd, mBuf); 74 | } 75 | auto await_resume() noexcept -> std::pair 76 | { 77 | if (mIoJob.mResult < 0) { 78 | return {0, std::errc(-mIoJob.mResult)}; 79 | } else { 80 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 81 | } 82 | } 83 | 84 | IoJob mIoJob; 85 | std::span mBuf; 86 | }; 87 | 88 | struct [[nodiscard]] SendAwaiter : SocketAwaiter { 89 | SendAwaiter(int fd, std::span buf) noexcept : SocketAwaiter(fd), mIoJob(nullptr), mBuf(buf) {} 90 | template 91 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 92 | { 93 | auto promise = &handle.promise(); 94 | mIoJob.mPending = promise; 95 | Proactor::get().prepSend(&mIoJob, mFd, mBuf); 96 | } 97 | auto await_resume() noexcept -> std::pair 98 | { 99 | if (mIoJob.mResult < 0) { 100 | return {0, std::errc(-mIoJob.mResult)}; 101 | } else { 102 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 103 | } 104 | } 105 | 106 | IoJob mIoJob; 107 | std::span mBuf; 108 | }; 109 | 110 | struct [[nodiscard]] SendMsgAwaiter : SocketAwaiter { 111 | SendMsgAwaiter(int fd, void* name, socklen_t namelen, ::iovec* iov, std::size_t iovlen) noexcept 112 | : SocketAwaiter(fd), mIoJob(nullptr), mMsg{name, namelen, iov, iovlen, nullptr, 0, 0} 113 | { 114 | } 115 | template 116 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 117 | { 118 | auto promise = &handle.promise(); 119 | mIoJob.mPending = promise; 120 | 121 | Proactor::get().prepSendMsg(&mIoJob, mFd, &mMsg); 122 | } 123 | auto await_resume() noexcept -> std::pair 124 | { 125 | if (mIoJob.mResult < 0) { 126 | return {0, std::errc(-mIoJob.mResult)}; 127 | } else { 128 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 129 | } 130 | } 131 | 132 | IoJob mIoJob; 133 | ::msghdr mMsg; 134 | }; 135 | 136 | struct [[nodiscard]] RecvMsgAwaiter : SocketAwaiter { 137 | RecvMsgAwaiter(int fd, void* name, socklen_t namelen, ::iovec* iov, std::size_t iovlen) noexcept 138 | : SocketAwaiter(fd), mIoJob(nullptr), mMsg{name, namelen, iov, iovlen, nullptr, 0, 0} 139 | { 140 | } 141 | template 142 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 143 | { 144 | auto promise = &handle.promise(); 145 | mIoJob.mPending = promise; 146 | 147 | Proactor::get().prepRecvMsg(&mIoJob, mFd, &mMsg); 148 | } 149 | auto await_resume() noexcept -> std::pair 150 | { 151 | if (mIoJob.mResult < 0) { 152 | return {0, std::errc(-mIoJob.mResult)}; 153 | } else { 154 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 155 | } 156 | } 157 | 158 | IoJob mIoJob; 159 | ::msghdr mMsg; 160 | }; 161 | 162 | // TODO: using sendto/recvfrom 163 | inline auto SendToAwaiter(int fd, std::span buf, SocketAddr addr) noexcept 164 | -> Task> 165 | { 166 | ::iovec iov = {(void*)buf.data(), buf.size()}; 167 | if (addr.isIpv6()) { 168 | sockaddr_in6 v6; 169 | addr.setSys(v6); 170 | co_return co_await SendMsgAwaiter(fd, (void*)&v6, (socklen_t)sizeof(v6), &iov, 1ul); 171 | } else if (addr.isIpv4()) { 172 | sockaddr_in v4; 173 | addr.setSys(v4); 174 | co_return co_await SendMsgAwaiter(fd, (void*)&v4, (socklen_t)sizeof(v4), &iov, 1ul); 175 | } else { 176 | co_return {0, std::errc::invalid_argument}; 177 | } 178 | } 179 | inline auto RecvFromAwaiter(int fd, std::span buf, SocketAddr addr) noexcept 180 | -> Task> 181 | { 182 | ::iovec iov = {(void*)buf.data(), buf.size()}; 183 | if (addr.isIpv6()) { 184 | sockaddr_in6 v6; 185 | socklen_t len = sizeof(v6); 186 | co_return co_await RecvMsgAwaiter(fd, (void*)&v6, len, &iov, 1ul); 187 | } else if (addr.isIpv4()) { 188 | sockaddr_in v4; 189 | socklen_t len = sizeof(v4); 190 | co_return co_await RecvMsgAwaiter(fd, (void*)&v4, len, &iov, 1ul); 191 | } else { 192 | co_return {0, std::errc::invalid_argument}; 193 | } 194 | } 195 | 196 | struct [[nodiscard]] ConnectAwaiter : SocketAwaiter { 197 | ConnectAwaiter(int fd, SocketAddr addr) noexcept : SocketAwaiter(fd), mIoJob(nullptr), mAddr(addr) {} 198 | 199 | template 200 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 201 | { 202 | auto job = &handle.promise(); 203 | mIoJob.mPending = job; 204 | if (mAddr.isIpv6()) { 205 | sockaddr_in6 v6; 206 | mAddr.setSys(v6); 207 | Proactor::get().prepConnect(&mIoJob, mFd, (sockaddr*)&v6, sizeof(v6)); 208 | } else if (mAddr.isIpv4()) { 209 | sockaddr_in v4; 210 | mAddr.setSys(v4); 211 | Proactor::get().prepConnect(&mIoJob, mFd, (sockaddr*)&v4, sizeof(v4)); 212 | } 213 | } 214 | auto await_resume() noexcept -> std::errc 215 | { 216 | if (mIoJob.mResult < 0) { 217 | return std::errc(-mIoJob.mResult); 218 | } else { 219 | return std::errc(0); 220 | } 221 | } 222 | IoJob mIoJob; 223 | SocketAddr mAddr; 224 | }; 225 | 226 | struct [[nodiscard]] AcceptAwaiter : SocketAwaiter { 227 | AcceptAwaiter(int fd) noexcept : SocketAwaiter(fd), mIoJob(nullptr) {} 228 | 229 | template 230 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 231 | { 232 | auto promise = &handle.promise(); 233 | mIoJob.mPending = promise; 234 | mIoJob.mOpt = ExeOpt::prefInOne(ExeOpt::High); 235 | Proactor::get().prepAccept(&mIoJob, mFd, nullptr, nullptr); 236 | } 237 | 238 | auto await_resume() noexcept -> std::pair 239 | { 240 | if (mIoJob.mResult < 0) { 241 | return {0, std::errc(-mIoJob.mResult)}; 242 | } else { 243 | return {mIoJob.mResult, std::errc(0)}; 244 | } 245 | } 246 | 247 | IoJob mIoJob; 248 | }; 249 | 250 | struct [[nodiscard]] RecvTimeoutAwaiter : RecvAwaiter { 251 | RecvTimeoutAwaiter(int fd, std::span buf, Duration timeout) noexcept 252 | : RecvAwaiter(fd, buf), mTimeout(timeout) 253 | { 254 | } 255 | template 256 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 257 | { 258 | auto promise = &handle.promise(); 259 | mIoJob.mPending = promise; 260 | 261 | mProactor = &Proactor::get(); 262 | mProactor->prepRecv(&mIoJob, mFd, mBuf); 263 | mProactor->prepTimeout(&mIoJob, mTimeout); 264 | } 265 | auto await_resume() noexcept -> std::pair 266 | { 267 | if (mIoJob.mResult < 0) { 268 | if (mIoJob.mResult == -ETIME) { 269 | mProactor->delPendingSet(CancelItem::cancelIo(mFd)); 270 | return {0, std::errc::timed_out}; 271 | } else { 272 | return {0, std::errc(-mIoJob.mResult)}; 273 | } 274 | } else { 275 | mProactor->delPendingSet(CancelItem::cancelTimeout(&mIoJob)); 276 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 277 | } 278 | } 279 | 280 | Proactor* mProactor; 281 | Duration mTimeout; 282 | }; 283 | 284 | struct [[nodiscard]] SendTimeoutAwaiter : SendAwaiter { 285 | SendTimeoutAwaiter(int fd, std::span buf, Duration timeout) noexcept 286 | : SendAwaiter(fd, buf), mTimeout(timeout) 287 | { 288 | } 289 | template 290 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 291 | { 292 | auto promise = &handle.promise(); 293 | mIoJob.mPending = promise; 294 | 295 | mProactor = &Proactor::get(); 296 | mProactor->prepSend(&mIoJob, mFd, mBuf); 297 | mProactor->prepTimeout(&mIoJob, mTimeout); 298 | } 299 | auto await_resume() noexcept -> std::pair 300 | { 301 | if (mIoJob.mResult < 0) { 302 | if (mIoJob.mResult == -ETIME) { 303 | mProactor->delPendingSet(CancelItem::cancelIo(mFd)); 304 | return {0, std::errc::timed_out}; 305 | } else { 306 | return {0, std::errc(-mIoJob.mResult)}; 307 | } 308 | } else { 309 | mProactor->delPendingSet(CancelItem::cancelTimeout(&mIoJob)); 310 | return {std::size_t(mIoJob.mResult), std::errc(0)}; 311 | } 312 | } 313 | 314 | Proactor* mProactor; 315 | Duration mTimeout; 316 | }; 317 | 318 | struct [[nodiscard]] AcceptTimeoutAwaiter : AcceptAwaiter { 319 | AcceptTimeoutAwaiter(int fd, Duration timeout) noexcept : AcceptAwaiter(fd), mTimeout(timeout) {} 320 | 321 | template 322 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 323 | { 324 | auto promise = &handle.promise(); 325 | mIoJob.mPending = promise; 326 | 327 | mProactor = &Proactor::get(); 328 | mProactor->prepAccept(&mIoJob, mFd, nullptr, nullptr); 329 | mProactor->prepTimeout(&mIoJob, mTimeout); 330 | } 331 | 332 | auto await_resume() noexcept -> std::pair 333 | { 334 | if (mIoJob.mResult < 0) { 335 | if (mIoJob.mResult == -ETIME) { 336 | mProactor->delPendingSet(CancelItem::cancelIo(mFd)); 337 | return {0, std::errc::timed_out}; 338 | } else { 339 | return {0, std::errc(-mIoJob.mResult)}; 340 | } 341 | } else { 342 | mProactor->delPendingSet(CancelItem::cancelTimeout(&mIoJob)); 343 | return {mIoJob.mResult, std::errc(0)}; 344 | } 345 | } 346 | 347 | Proactor* mProactor; 348 | Duration mTimeout; 349 | }; 350 | 351 | struct [[nodiscard]] ConnectTimeoutAwaiter : ConnectAwaiter { 352 | ConnectTimeoutAwaiter(int fd, SocketAddr addr, Duration timeout) noexcept 353 | : ConnectAwaiter(fd, addr), mTimeout(timeout) 354 | { 355 | } 356 | 357 | template 358 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 359 | { 360 | auto job = &handle.promise(); 361 | mIoJob.mPending = job; 362 | 363 | mProactor = &Proactor::get(); 364 | if (mAddr.isIpv4()) { 365 | sockaddr_in v4; 366 | mAddr.setSys(v4); 367 | mProactor->prepConnect(&mIoJob, mFd, (sockaddr*)&v4, sizeof(v4)); 368 | } else if (mAddr.isIpv6()) { 369 | sockaddr_in6 v6; 370 | mAddr.setSys(v6); 371 | mProactor->prepConnect(&mIoJob, mFd, (sockaddr*)&v6, sizeof(v6)); 372 | } 373 | mProactor->prepTimeout(&mIoJob, mTimeout); 374 | } 375 | 376 | auto await_resume() noexcept -> std::errc 377 | { 378 | if (mIoJob.mResult < 0) { 379 | if (mIoJob.mResult == -ETIME) { 380 | mProactor->delPendingSet(CancelItem::cancelIo(mFd)); 381 | return std::errc::timed_out; 382 | } else { 383 | return std::errc(-mIoJob.mResult); 384 | } 385 | } else { 386 | mProactor->delPendingSet(CancelItem::cancelTimeout(&mIoJob)); 387 | return std::errc(0); 388 | } 389 | } 390 | 391 | Proactor* mProactor; 392 | Duration mTimeout; 393 | }; 394 | }; // namespace coco::sys::detail -------------------------------------------------------------------------------- /include/coco/sys/stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/sys/socket.hpp" 4 | #include "coco/task.hpp" 5 | 6 | namespace coco::sys { 7 | class TcpStream : private Socket { 8 | public: 9 | TcpStream() noexcept = default; 10 | TcpStream(TcpStream&& stream) noexcept = default; 11 | auto operator=(TcpStream&& stream) noexcept -> TcpStream& = default; 12 | 13 | static auto connect(SocketAddr const& addr) -> coco::Task> 14 | { 15 | auto [socket, errc] = Socket::create(addr, Socket::Stream); 16 | if (errc != std::errc{0}) { 17 | co_return {TcpStream(), errc}; 18 | } 19 | errc = co_await socket.connect(addr); 20 | if (errc != std::errc{0}) { 21 | co_return {TcpStream(), errc}; 22 | } 23 | co_return {TcpStream(std::move(socket)), std::errc{0}}; 24 | } 25 | template 26 | static auto connect(SocketAddr const& addr, std::chrono::duration timeout) 27 | -> coco::Task> 28 | { 29 | auto [socket, errc] = Socket::create(addr, Socket::Stream); 30 | if (errc != std::errc{0}) { 31 | co_return {TcpStream(), errc}; 32 | } 33 | errc = co_await socket.connect(addr, timeout); 34 | if (errc != std::errc{0}) { 35 | co_return {TcpStream(), errc}; 36 | } 37 | co_return {TcpStream(std::move(socket)), std::errc{0}}; 38 | } 39 | static auto from(Socket&& socket) noexcept -> TcpStream { return TcpStream(std::move(socket)); } 40 | auto recv(std::span buf) noexcept -> decltype(auto) { return Socket::recv(buf); } 41 | auto send(std::span buf) noexcept -> decltype(auto) { return Socket::send(buf); } 42 | auto send(std::span buf, std::chrono::milliseconds timeout) noexcept -> decltype(auto) 43 | { 44 | return Socket::send(buf, timeout); 45 | } 46 | auto recv(std::span buf, std::chrono::milliseconds timeout) noexcept -> decltype(auto) 47 | { 48 | return Socket::recv(buf, timeout); 49 | } 50 | auto close() noexcept -> decltype(auto) { return Socket::close(); } 51 | 52 | private: 53 | TcpStream(Socket&& socket) noexcept : Socket(std::move(socket)) {} 54 | }; 55 | } // namespace coco::sys -------------------------------------------------------------------------------- /include/coco/sys/udp_socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "socket.hpp" 3 | namespace coco::sys { 4 | class UdpSocket : Socket { 5 | public: 6 | UdpSocket() noexcept = default; 7 | UdpSocket(UdpSocket&& socket) noexcept = default; 8 | auto operator=(UdpSocket&& socket) noexcept -> UdpSocket& = default; 9 | 10 | static auto bind(SocketAddr const& addr) -> std::pair 11 | { 12 | auto [socket, errc] = Socket::create(addr, Socket::Datagram); 13 | if (errc != std::errc{0}) { 14 | return {UdpSocket(), errc}; 15 | } 16 | int opt = 1; 17 | socket.setopt(SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 18 | errc = socket.bind(addr); 19 | if (errc != std::errc{0}) { 20 | return {UdpSocket(), errc}; 21 | } 22 | return {UdpSocket(std::move(socket)), std::errc{0}}; 23 | } 24 | static auto create(SocketAddr const& addr) noexcept -> std::pair 25 | { 26 | auto [socket, errc] = Socket::create(addr, Socket::Datagram); 27 | if (errc != std::errc{0}) { 28 | return {UdpSocket(), errc}; 29 | } else { 30 | return {UdpSocket(std::move(socket)), std::errc{0}}; 31 | } 32 | } 33 | auto recvfrom(std::span buf, SocketAddr& addr) noexcept -> decltype(auto) 34 | { 35 | return Socket::recvFrom(buf, addr); 36 | } 37 | auto sendto(std::span buf, SocketAddr const& addr) noexcept -> decltype(auto) 38 | { 39 | return Socket::sendTo(buf, addr); 40 | } 41 | 42 | private: 43 | UdpSocket(Socket&& socket) noexcept : Socket(std::move(socket)) {} 44 | }; 45 | } // namespace coco::sys -------------------------------------------------------------------------------- /include/coco/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/proactor.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace coco { 12 | struct PromiseBase { 13 | struct CoroJob : WorkerJob { 14 | CoroJob(PromiseBase* promise, WorkerJob::WorkerFn run) noexcept : promise(promise), WorkerJob(run, nullptr) {} 15 | static auto run(WorkerJob* job, WorkerArg) noexcept -> void 16 | { 17 | auto coroJob = static_cast(job); 18 | coroJob->promise->mThisHandle.resume(); 19 | } 20 | PromiseBase* promise; 21 | }; 22 | 23 | struct FinalAwaiter { 24 | auto await_ready() const noexcept -> bool { return false; } 25 | template 26 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 27 | { 28 | assert(handle.done() && "handle should done here"); 29 | auto& promise = handle.promise(); 30 | 31 | auto next = promise.mNextJob.exchange(nullptr); 32 | if (next == nullptr) { 33 | if (promise.getState() != nullptr) [[unlikely]] { 34 | promise.getState()->store(JobState::Final, std::memory_order_release); 35 | promise.getState()->notify_one(); 36 | } 37 | } else if (next == &detail::kDetachJob) { 38 | if (promise.getState() != nullptr) [[unlikely]] { 39 | promise.getState()->store(JobState::Final, std::memory_order_release); 40 | promise.getState()->notify_one(); 41 | } 42 | promise.mThisHandle.destroy(); 43 | } else if (next != &detail::kEmptyJob) { 44 | Proactor::get().execute(next, ExeOpt::prefInOne()); 45 | } 46 | } 47 | auto await_resume() noexcept -> void {} 48 | }; 49 | 50 | auto initial_suspend() noexcept -> std::suspend_always { return {}; } 51 | auto final_suspend() noexcept -> FinalAwaiter { return {}; } 52 | auto unhandled_exception() noexcept -> void { mExceptionPtr = std::current_exception(); } 53 | 54 | auto setCoHandle(std::coroutine_handle<> handle) noexcept -> void { mThisHandle = handle; } 55 | auto getThisJob() noexcept -> WorkerJob* { return &mThisJob; } 56 | 57 | auto getState() noexcept -> std::atomic* { return mThisJob.state; } 58 | auto setState(std::atomic* state) noexcept -> void { mThisJob.state = state; } 59 | auto setDetach() noexcept -> void 60 | { 61 | auto ptr = &detail::kDetachJob; 62 | mNextJob.store(ptr); 63 | } 64 | 65 | auto setNextJob(WorkerJob* next) noexcept -> void { mNextJob = next; } 66 | auto getNextJob() noexcept -> std::atomic& { return mNextJob; } 67 | 68 | auto setExeception(std::exception_ptr exceptionPtr) noexcept -> void { mExceptionPtr = exceptionPtr; } 69 | auto hasException() const noexcept -> bool { return mExceptionPtr != nullptr; } 70 | auto currentException() const noexcept -> std::exception_ptr { return mExceptionPtr; } 71 | 72 | CoroJob mThisJob{this, &CoroJob::run}; 73 | std::coroutine_handle<> mThisHandle; 74 | std::atomic mNextJob{nullptr}; 75 | std::exception_ptr mExceptionPtr; 76 | }; 77 | 78 | template 79 | struct Promise final : PromiseBase { 80 | auto get_return_object() noexcept -> Task; 81 | auto return_value(T value) noexcept -> void { std::construct_at(std::addressof(mRetVal), std::move(value)); } 82 | auto result() const& -> T const& 83 | { 84 | if (mExceptionPtr) { 85 | std::rethrow_exception(mExceptionPtr); 86 | } 87 | return mRetVal; 88 | } 89 | auto result() && -> T&& 90 | { 91 | if (mExceptionPtr) { 92 | std::rethrow_exception(mExceptionPtr); 93 | } 94 | return std::move(mRetVal); 95 | } 96 | 97 | T mRetVal; 98 | }; 99 | 100 | template <> 101 | struct Promise : PromiseBase { 102 | auto get_return_object() noexcept -> Task; 103 | auto return_void() noexcept -> void {} 104 | auto result() const -> void 105 | { 106 | if (mExceptionPtr) { 107 | std::rethrow_exception(mExceptionPtr); 108 | } 109 | return; 110 | } 111 | }; 112 | 113 | template 114 | constexpr bool IsTask = false; 115 | 116 | template 117 | constexpr bool IsTask> = true; 118 | 119 | template 120 | concept TaskConcept = IsTask; 121 | 122 | template 123 | class Task { 124 | public: 125 | using promise_type = Promise; 126 | using coroutine_handle_type = std::coroutine_handle; 127 | using value_type = T; 128 | 129 | Task() noexcept = default; 130 | explicit Task(coroutine_handle_type handle) noexcept : mHandle(handle) 131 | { 132 | assert(mHandle != nullptr); 133 | mHandle.promise().setCoHandle(mHandle); 134 | } 135 | Task(Task&& other) noexcept : mHandle(std::exchange(other.mHandle, nullptr)) {} 136 | auto operator=(Task&& other) -> Task& 137 | { 138 | assert(mHandle == nullptr); 139 | mHandle = std::exchange(other.mHandle, nullptr); 140 | return *this; 141 | }; 142 | constexpr ~Task() noexcept { destroy(); } 143 | 144 | auto operator==(Task const& other) -> bool { return mHandle == other.mHandle; } 145 | auto done() const noexcept -> bool { return mHandle.done(); } 146 | auto handle() const noexcept -> coroutine_handle_type { return mHandle; } 147 | auto resume() const -> bool 148 | { 149 | if (mHandle != nullptr && !mHandle.done()) { 150 | mHandle.resume(); 151 | return true; 152 | } 153 | return false; 154 | } 155 | auto promise() & -> promise_type& { return mHandle.promise(); } 156 | auto promise() const& -> promise_type const& { return mHandle.promise(); } 157 | auto promise() && -> promise_type&& { return std::move(mHandle.promise()); } 158 | 159 | auto take() noexcept -> coroutine_handle_type { return std::exchange(mHandle, nullptr); } 160 | 161 | struct AwaiterBase { 162 | auto await_ready() const noexcept -> bool { return false; } 163 | template 164 | auto await_suspend(std::coroutine_handle handle) noexcept -> void 165 | { 166 | mHandle.promise().setNextJob(handle.promise().getThisJob()); 167 | mHandle.promise().setState(handle.promise().getState()); 168 | Proactor::get().execute(mHandle.promise().getThisJob(), ExeOpt::prefInOne()); 169 | } 170 | coroutine_handle_type mHandle; 171 | }; 172 | 173 | auto operator co_await() const& noexcept 174 | { 175 | struct Awaiter : AwaiterBase { 176 | auto await_resume() -> decltype(auto) { return this->mHandle.promise().result(); } 177 | }; 178 | return Awaiter{mHandle}; 179 | } 180 | 181 | auto operator co_await() && noexcept 182 | { 183 | struct Awaiter : AwaiterBase { 184 | auto await_resume() -> decltype(auto) { return std::move(this->mHandle.promise()).result(); } 185 | }; 186 | return Awaiter{mHandle}; 187 | } 188 | 189 | private: 190 | auto destroy() -> void 191 | { 192 | if (auto handle = std::exchange(mHandle, nullptr); handle != nullptr) { 193 | handle.destroy(); 194 | } 195 | } 196 | coroutine_handle_type mHandle{nullptr}; 197 | }; 198 | 199 | template 200 | inline auto Promise::get_return_object() noexcept -> Task 201 | { 202 | return Task{std::coroutine_handle::from_promise(*this)}; 203 | } 204 | inline auto Promise::get_return_object() noexcept -> Task 205 | { 206 | return Task{std::coroutine_handle::from_promise(*this)}; 207 | } 208 | 209 | struct [[nodiscard]] ThisTask { 210 | constexpr auto await_ready() const noexcept -> bool { return false; } 211 | template 212 | constexpr auto await_suspend(std::coroutine_handle handle) noexcept 213 | { 214 | mCoHandle = handle; 215 | return false; 216 | } 217 | constexpr auto await_resume() const noexcept -> std::coroutine_handle<> { return {mCoHandle}; } 218 | std::coroutine_handle<> mCoHandle; 219 | }; 220 | 221 | struct [[nodiscard]] Yield { 222 | constexpr auto await_ready() const noexcept -> bool { return false; } 223 | template 224 | constexpr auto await_suspend(std::coroutine_handle handle) noexcept 225 | { 226 | // suspend then reshcedule 227 | Proactor::get().execute(handle.promise().getThisJob()); 228 | } 229 | constexpr auto await_resume() const noexcept -> void {} 230 | }; 231 | } // namespace coco 232 | -------------------------------------------------------------------------------- /include/coco/timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/util/heap.hpp" 4 | #include "coco/util/lockfree_queue.hpp" 5 | #include "coco/worker_job.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace coco { 12 | using Instant = std::chrono::steady_clock::time_point; 13 | using Duration = std::chrono::steady_clock::duration; 14 | 15 | enum class TimerOpKind : std::uint8_t { 16 | Add, 17 | Delete, 18 | }; 19 | 20 | struct TimerOp { 21 | Instant instant; 22 | union { 23 | WorkerJob* job; 24 | void* jobId; 25 | }; 26 | TimerOpKind kind; 27 | }; 28 | 29 | struct TimerItem { 30 | Instant instant; 31 | WorkerJob* job; 32 | }; 33 | 34 | inline auto operator<(TimerItem const& lhs, TimerItem const& rhs) noexcept -> bool { return lhs.instant < rhs.instant; } 35 | 36 | class TimerManager { 37 | public: 38 | TimerManager() noexcept = default; 39 | TimerManager(std::size_t capcacity) : mTimers(capcacity), mPendingJobs() {} 40 | ~TimerManager() = default; 41 | 42 | // MT-Safe 43 | auto addTimer(Instant time, WorkerJob* job) noexcept -> void; 44 | // MT-Safe 45 | auto deleteTimer(void* id) noexcept -> void; 46 | auto nextInstant() const noexcept -> Instant; 47 | auto processTimers() -> std::pair; 48 | 49 | private: 50 | std::mutex mPendingJobsMt; 51 | std::queue mPendingJobs; 52 | std::unordered_set mDeleted; 53 | util::Heap mTimers; 54 | }; 55 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/uring.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #if !IO_URING_CHECK_VERSION(2, 4) 12 | #error "current liburing version is not supported" 13 | #endif 14 | 15 | inline std::atomic_uint32_t gNotifyTicks; 16 | 17 | namespace coco { 18 | class Worker; 19 | class WorkerJob; 20 | class MtExecutor; 21 | 22 | constexpr std::uint32_t kIoUringQueueSize = 2048; 23 | using Token = void*; 24 | 25 | template 26 | static auto convertTime(std::chrono::duration duration, struct __kernel_timespec& out) noexcept -> void 27 | { 28 | auto sec = std::chrono::duration_cast(duration); 29 | auto nsec = std::chrono::duration_cast(duration - sec); 30 | out.tv_sec = sec.count(); 31 | out.tv_nsec = nsec.count(); 32 | } 33 | 34 | class IoUring { 35 | public: 36 | IoUring(); 37 | ~IoUring(); 38 | 39 | IoUring(IoUring&& other) = delete; 40 | auto operator=(IoUring&& other) -> IoUring& = delete; 41 | 42 | auto prepRecv(Token token, int fd, std::span buf, int flag = 0) noexcept -> void; 43 | auto prepSend(Token token, int fd, std::span buf, int flag = 0) noexcept -> void; 44 | auto prepRecvMsg(Token token, int fd, ::msghdr* msg, unsigned flag = 0) noexcept -> void; 45 | auto prepSendMsg(Token token, int fd, ::msghdr* msg, unsigned flag = 0) noexcept -> void; 46 | auto prepAccept(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags = 0) noexcept -> void; 47 | auto prepAcceptMt(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags = 0) noexcept -> void; 48 | auto prepConnect(Token token, int fd, sockaddr* addr, socklen_t addrlen) noexcept -> void; 49 | 50 | auto prepRead(Token token, int fd, std::span buf, off_t offset) noexcept -> void; 51 | auto prepWrite(Token token, int fd, std::span buf, off_t offset) noexcept -> void; 52 | template 53 | auto prepAddTimeout(Token token, std::chrono::duration timeout) noexcept -> void 54 | { 55 | auto timeoutSpec = __kernel_timespec{}; 56 | convertTime(timeout, timeoutSpec); 57 | auto sqe = fetchSqe(); 58 | ::io_uring_prep_timeout(sqe, &timeoutSpec, 0, 0); 59 | ::io_uring_sqe_set_data(sqe, token); 60 | } 61 | template 62 | auto prepUpdateTimeout(Token token, std::chrono::duration timeout) noexcept -> void 63 | { 64 | auto timeoutSpec = __kernel_timespec{}; 65 | convertTime(timeout, timeoutSpec); 66 | auto sqe = fetchSqe(); 67 | ::io_uring_prep_timeout(sqe, &timeoutSpec, 1, 0); 68 | ::io_uring_sqe_set_data(sqe, token); 69 | } 70 | auto prepRemoveTimeout(Token token) noexcept -> void 71 | { 72 | auto sqe = fetchSqe(); 73 | ::io_uring_prep_timeout_remove(sqe, (std::uint64_t)token, 0); 74 | ::io_uring_sqe_set_data(sqe, token); 75 | } 76 | auto prepCancel(int fd) noexcept -> void; 77 | auto prepCancel(Token token) noexcept -> void; 78 | auto prepClose(Token token, int fd) noexcept -> void; 79 | 80 | auto seen(io_uring_cqe* cqe) noexcept -> void; 81 | auto advance(std::uint32_t n) noexcept -> void; 82 | auto submitWait(int waitn) noexcept -> std::errc; 83 | auto submit() noexcept -> std::errc; 84 | 85 | template 86 | auto submitWait(io_uring_cqe*& cqe, std::chrono::duration duration) noexcept -> std::errc 87 | { 88 | auto timeout = __kernel_timespec{}; 89 | convertTime(duration, timeout); 90 | auto r = ::io_uring_submit_and_wait_timeout(&mUring, &cqe, 1, &timeout, 0); 91 | return r < 0 ? std::errc(-r) : std::errc(0); 92 | } 93 | template 94 | auto submitWait(std::span cqes, std::chrono::duration duration) noexcept -> std::errc 95 | { 96 | auto timeout = __kernel_timespec{}; 97 | convertTime(duration, timeout); 98 | auto r = ::io_uring_submit_and_wait_timeout(&mUring, cqes.data(), cqes.size(), &timeout, 0); 99 | return r < 0 ? std::errc(-r) : std::errc(0); 100 | } 101 | 102 | // TODO: I can't find a method to notify a uring without a real fd :(. 103 | auto notify() noexcept -> void; 104 | auto uring() -> ::io_uring* { return &mUring; } 105 | 106 | private: 107 | auto fetchSqe() -> io_uring_sqe*; 108 | 109 | private: 110 | int mEventFd; 111 | ::io_uring mUring; 112 | }; 113 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/util/fixed_vec.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace coco::util { 6 | template 7 | class FixedVec { 8 | public: 9 | constexpr FixedVec() noexcept(noexcept(std::declval())) = default; 10 | constexpr auto begin() noexcept { return mData.begin(); } 11 | constexpr auto end() noexcept { return mData.begin() + mSize; } 12 | constexpr auto data() noexcept { return mData.data(); } 13 | constexpr auto push_back(T const& item) -> bool 14 | { 15 | if (mSize == N) [[unlikely]] { 16 | return false; 17 | } 18 | mData[mSize++] = item; 19 | return true; 20 | } 21 | constexpr auto push_back(T&& item) -> bool 22 | { 23 | if (mSize == N) [[unlikely]] { 24 | return false; 25 | } 26 | mData[mSize++] = std::move(item); 27 | return true; 28 | } 29 | template 30 | constexpr auto emplace_back(Args&&... args) -> bool 31 | { 32 | if (mSize == N) [[unlikely]] { 33 | return false; 34 | } 35 | std::construct_at(mData.data() + mSize, std::forward(args)...); 36 | mSize += 1; 37 | return true; 38 | } 39 | constexpr auto pop_back() -> bool 40 | { 41 | if (mSize == 0) { 42 | return false; 43 | } 44 | mSize -= 1; 45 | std::destroy_at(mData.begin() + mSize); 46 | return true; 47 | } 48 | constexpr auto size() noexcept -> std::size_t { return mSize; } 49 | constexpr auto clear() -> void 50 | { 51 | std::destroy_n(mData.data(), mSize); 52 | mSize = 0; 53 | } 54 | constexpr auto operator[](std::size_t i) noexcept -> T& { return mData[i]; } 55 | constexpr auto operator[](std::size_t i) const noexcept -> T const& { return mData[i]; } 56 | 57 | private: 58 | std::size_t mSize = 0; 59 | std::array mData; 60 | }; 61 | } // namespace coco::util -------------------------------------------------------------------------------- /include/coco/util/heap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace coco::util { 6 | template > 7 | class Heap { 8 | public: 9 | Heap() = default; 10 | Heap(std::size_t capacity) { mData.reserve(capacity); } 11 | ~Heap() = default; 12 | 13 | auto size() const -> std::size_t { return mData.size(); } 14 | auto empty() const -> bool { return mData.empty(); } 15 | auto insert(T&& val) -> void 16 | { 17 | mData.push_back(std::forward(val)); 18 | siftUp(mData.size() - 1); 19 | } 20 | auto pop(T& out) -> bool 21 | { 22 | if (mData.empty()) { 23 | return false; 24 | } 25 | out = mData[0]; 26 | mData[0] = mData.back(); 27 | mData.pop_back(); 28 | siftDown(0); 29 | return true; 30 | } 31 | auto pop() -> bool 32 | { 33 | if (mData.empty()) { 34 | return false; 35 | } 36 | mData[0] = mData.back(); 37 | mData.pop_back(); 38 | siftDown(0); 39 | return true; 40 | } 41 | auto top() -> T& { return mData[0]; } 42 | auto top() const -> T const& { return mData[0]; } 43 | 44 | private: 45 | auto siftUp(std::size_t idx) -> void 46 | { 47 | if (idx == 0) { 48 | return; 49 | } 50 | auto parent = (idx - 1) / N; 51 | if (P()(mData[idx], mData[parent])) { 52 | std::swap(mData[idx], mData[parent]); 53 | siftUp(parent); 54 | } 55 | } 56 | auto siftDown(std::size_t idx) -> void 57 | { 58 | auto sonStart = idx * N + 1; 59 | auto sonEnd = std::min(sonStart + N, mData.size()); 60 | auto maxSon = idx; 61 | for (auto i = sonStart; i < sonEnd; i++) { 62 | if (P()(mData[i], mData[maxSon])) { 63 | maxSon = i; 64 | } 65 | } 66 | if (maxSon != idx) { 67 | std::swap(mData[maxSon], mData[idx]); 68 | siftDown(maxSon); 69 | } 70 | } 71 | 72 | private: 73 | std::vector mData; 74 | }; 75 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/util/intr_ptr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coco::util { 9 | template 10 | struct MakeIntr; 11 | template 12 | struct EnableIntrFromThis; 13 | template 14 | struct ControlBlock { 15 | template 16 | explicit ControlBlock(Us&&... us) noexcept(noexcept(T{std::declval()...})) : mRc(1) 17 | { 18 | ::new ((void*)mValue) T{(Us &&) us...}; 19 | } 20 | 21 | ~ControlBlock() { value().~T(); } 22 | auto value() const noexcept -> T& { return *(T*)mValue; } 23 | 24 | alignas(T) std::uint8_t mValue[sizeof(T)]; 25 | std::atomic_size_t mRc; 26 | }; 27 | 28 | template 29 | class IntrPtr { 30 | friend struct MakeIntr; 31 | friend struct EnableIntrFromThis; 32 | 33 | public: 34 | IntrPtr() = default; 35 | IntrPtr(IntrPtr&& other) noexcept : mData(std::exchange(other.mData, nullptr)) {} 36 | IntrPtr(IntrPtr const& other) noexcept : mData(other.mData) { addRef(); } 37 | IntrPtr& operator=(IntrPtr&& other) noexcept 38 | { 39 | [[maybe_unused]] IntrPtr old{std::exchange(mData, std::exchange(other.mData, nullptr))}; 40 | return *this; 41 | } 42 | IntrPtr& operator=(IntrPtr const& other) noexcept { return operator=(IntrPtr{other}); } 43 | ~IntrPtr() noexcept { release(); } 44 | auto reset() noexcept -> void { operator=(IntrPtr{}); } 45 | auto swap(IntrPtr& other) noexcept -> void { std::swap(mData, other.mData); } 46 | auto get() const noexcept -> T* { return &mData->value(); } 47 | auto operator*() const noexcept -> T& { return mData->value(); } 48 | explicit operator bool() const noexcept { return mData != nullptr; } 49 | auto operator!() const noexcept -> bool { return mData == nullptr; } 50 | auto operator==(IntrPtr const&) const noexcept -> bool = default; 51 | auto operator==(std::nullptr_t) const noexcept -> bool { return mData == nullptr; } 52 | 53 | private: 54 | using value_type = std::remove_cv_t; 55 | explicit IntrPtr(ControlBlock* data) noexcept : mData(data) {} 56 | 57 | auto addRef() noexcept -> void 58 | { 59 | if (mData != nullptr) { 60 | mData->mRc.fetch_add(1, std::memory_order_relaxed); 61 | } 62 | } 63 | 64 | auto release() noexcept -> void 65 | { 66 | if (mData != nullptr && mData->mRc.fetch_sub(1, std::memory_order_release) == 1) { 67 | std::atomic_thread_fence(std::memory_order_acquire); 68 | ::delete mData; 69 | } 70 | } 71 | 72 | public: 73 | ControlBlock* mData{nullptr}; 74 | }; 75 | 76 | template 77 | struct EnableIntrFromThis { 78 | auto intrFromThis() noexcept -> IntrPtr 79 | { 80 | static_assert(0 == offsetof(ControlBlock, mValue)); 81 | T* this_ = static_cast(this); 82 | IntrPtr ptr{(ControlBlock*)this_}; 83 | ptr.addRef(); 84 | return ptr; 85 | } 86 | 87 | auto intrFromThis() const noexcept -> IntrPtr 88 | { 89 | static_assert(0 == offsetof(ControlBlock, mValue)); 90 | T const* this_ = static_cast(this); 91 | IntrPtr ptr{(ControlBlock*)this_}; 92 | ptr.addRef(); 93 | return ptr; 94 | } 95 | }; 96 | 97 | template 98 | struct MakeIntr { 99 | template 100 | requires std::is_constructible_v 101 | IntrPtr operator()(Us&&... us) const 102 | { 103 | using _UncvTy = std::remove_cv_t; 104 | return IntrPtr{::new ControlBlock<_UncvTy>{(Us &&) us...}}; 105 | } 106 | }; 107 | 108 | template 109 | inline constexpr MakeIntr makeIntr{}; 110 | 111 | } // namespace coco::util -------------------------------------------------------------------------------- /include/coco/util/lockfree_queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "queue.hpp" 3 | 4 | #include 5 | 6 | namespace coco::util { 7 | template 8 | class AtomicQueue; 9 | template 10 | class AtomicQueue { 11 | public: 12 | using node_pointer = Item*; 13 | using atomic_node_pointer = std::atomic; 14 | 15 | auto empty() const noexcept -> bool { return mHead.load(std::memory_order_relaxed) == nullptr; } 16 | auto pushFront(node_pointer t) noexcept -> void 17 | { 18 | node_pointer oldHead = mHead.load(std::memory_order_relaxed); 19 | do { 20 | t->*next = oldHead; 21 | } while (!mHead.compare_exchange_weak(oldHead, t, std::memory_order_acq_rel)); 22 | } 23 | 24 | auto popAll() noexcept -> Queue 25 | { 26 | return Queue::from(mHead.exchange(nullptr, std::memory_order_acq_rel)); 27 | } 28 | 29 | private: 30 | atomic_node_pointer mHead{nullptr}; 31 | }; 32 | } // namespace coco::util -------------------------------------------------------------------------------- /include/coco/util/panic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coco::util { 9 | struct PanicDynamicStringView { 10 | template 11 | requires std::convertible_to 12 | PanicDynamicStringView(T const& s, std::source_location loc = std::source_location::current()) noexcept 13 | : str(s), loc(loc) 14 | { 15 | } 16 | 17 | std::string_view str; 18 | std::source_location loc; 19 | }; 20 | 21 | template 22 | struct PanicFormat { 23 | template 24 | consteval PanicFormat(T const& s, std::source_location loc = std::source_location::current()) noexcept 25 | : str(s), loc(loc) 26 | { 27 | } 28 | 29 | std::format_string str; 30 | std::source_location loc; 31 | }; 32 | 33 | [[noreturn]] auto panicImpl(char const* s) noexcept -> void; 34 | 35 | [[noreturn]] inline auto panic(PanicDynamicStringView s) noexcept -> void 36 | { 37 | auto msg = std::format("{}:{} panic: {}\n", s.loc.file_name(), s.loc.line(), s.str); 38 | panicImpl(msg.c_str()); 39 | } 40 | 41 | template 42 | requires(sizeof...(Args) > 0) 43 | [[noreturn]] auto panic(PanicFormat...> fmt, Args&&... args) noexcept -> void 44 | { 45 | auto msg = std::format("{}:{} panic: {}\n", fmt.loc.file_name(), fmt.loc.line(), 46 | std::format(fmt.fmt, std::forward(args)...)); 47 | panicImpl(msg.c_str()); 48 | } 49 | 50 | [[noreturn]] inline auto panicImpl(char const* s) noexcept -> void 51 | { 52 | std::fputs(s, stderr); 53 | std::abort(); 54 | } 55 | } // namespace coco::util -------------------------------------------------------------------------------- /include/coco/util/queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace coco::util { 7 | template 8 | class Queue; 9 | template 10 | class Queue { 11 | public: 12 | Queue() noexcept = default; 13 | Queue(Queue&& other) noexcept : mHead(std::exchange(other.mHead, nullptr)), mTail(std::exchange(other.mTail, nullptr)) 14 | { 15 | } 16 | auto operator=(Queue&& other) noexcept -> Queue& 17 | { 18 | std::swap(mHead, other.mHead); 19 | std::swap(mTail, other.mTail); 20 | return *this; 21 | } 22 | ~Queue() noexcept 23 | { 24 | auto r = empty(); 25 | assert(r); 26 | } 27 | 28 | static auto from(Item* list) noexcept -> Queue 29 | { 30 | Item* newHead = nullptr; 31 | Item* newTail = list; 32 | while (list != nullptr) { 33 | auto n = list->*next; 34 | list->*next = newHead; 35 | newHead = list; 36 | list = n; 37 | } 38 | auto q = Queue(); 39 | q.mHead = newHead; 40 | q.mTail = newTail; 41 | return q; 42 | } 43 | 44 | auto empty() const noexcept -> bool { return mHead == nullptr; } 45 | auto popFront() noexcept -> Item* 46 | { 47 | if (mHead == nullptr) { 48 | return nullptr; 49 | } 50 | Item* item = std::exchange(mHead, mHead->*next); 51 | if (item->*next == nullptr) { 52 | mTail = nullptr; 53 | } 54 | return item; 55 | } 56 | 57 | auto popFront(std::size_t n) noexcept -> Queue 58 | { 59 | auto q = Queue(); 60 | q.mHead = mHead; 61 | q.mTail = mHead; 62 | for (std::size_t i = 1; i < n; i++) { 63 | if (q.mTail == nullptr) { 64 | break; 65 | } 66 | q.mTail = q.mTail->*next; 67 | } 68 | if (q.mTail != nullptr) { 69 | mHead = q.mTail->*next; 70 | q.mTail->*next = nullptr; 71 | } else { 72 | mHead = nullptr; 73 | mTail = nullptr; 74 | } 75 | return q; 76 | } 77 | 78 | auto pushFront(Item* item) noexcept -> void 79 | { 80 | item->*next = mHead; 81 | mHead = item; 82 | if (mTail == nullptr) { 83 | mTail = item; 84 | } 85 | } 86 | 87 | auto pushBack(Item* item) noexcept -> void 88 | { 89 | item->*next = nullptr; 90 | if (mTail == nullptr) { 91 | mHead = item; 92 | } else { 93 | mTail->*next = item; 94 | } 95 | mTail = item; 96 | } 97 | 98 | auto append(Queue other) noexcept -> void 99 | { 100 | if (other.empty()) { 101 | return; 102 | } 103 | auto* otherHead = std::exchange(other.mHead, nullptr); 104 | if (empty()) { 105 | mHead = otherHead; 106 | } else { 107 | mTail->*next = otherHead; 108 | } 109 | mTail = std::exchange(other.mTail, nullptr); 110 | } 111 | 112 | auto prepend(Queue other) noexcept -> void 113 | { 114 | if (other.empty()) { 115 | return; 116 | } 117 | other.mTail->*next = mHead; 118 | mHead = other.mHead; 119 | if (mTail == nullptr) { 120 | mTail = other.mTail; 121 | } 122 | other.mTail = nullptr; 123 | other.mHead = nullptr; 124 | } 125 | 126 | auto front() noexcept -> Item* { return mHead; } 127 | auto back() noexcept -> Item* { return mTail; } 128 | 129 | private: 130 | Item* mHead = nullptr; 131 | Item* mTail = nullptr; 132 | }; 133 | } // namespace coco -------------------------------------------------------------------------------- /include/coco/util/ring_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | // fifo ring buffer 5 | template 6 | class RingBuffer { 7 | public: 8 | RingBuffer() : mOut(0), mSize(0) {} 9 | 10 | auto pop() noexcept -> std::optional 11 | { 12 | if (mSize == 0) { 13 | return std::nullopt; 14 | } 15 | std::optional result = mData[mOut]; 16 | mOut = (mOut + 1) % N; 17 | mSize -= 1; 18 | return result; 19 | } 20 | 21 | auto pop(T& value) -> bool 22 | { 23 | if (mSize == 0) { 24 | return false; 25 | } 26 | value = std::move(mData[mOut]); 27 | mOut = (mOut + 1) % N; 28 | mSize -= 1; 29 | return true; 30 | } 31 | 32 | auto push(T&& item) noexcept -> bool 33 | { 34 | if (mSize == N) { 35 | return false; 36 | } 37 | mData[(mOut + mSize) % N] = std::move(item); 38 | mSize += 1; 39 | return true; 40 | } 41 | 42 | auto push(T const& item) noexcept -> bool 43 | { 44 | if (mSize == N) { 45 | return false; 46 | } 47 | mData[(mOut + mSize) % N] = item; 48 | mSize += 1; 49 | return true; 50 | } 51 | 52 | auto size() const noexcept -> std::uint32_t { return mSize; } 53 | auto empty() const noexcept -> bool { return size() == 0; } 54 | auto full() const noexcept -> bool { return size() == N; } 55 | 56 | private: 57 | std::array mData; 58 | std::uint32_t mOut; 59 | std::uint32_t mSize; 60 | }; 61 | -------------------------------------------------------------------------------- /include/coco/worker_job.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coco/util/queue.hpp" 4 | 5 | #include 6 | 7 | namespace coco { 8 | inline auto genJobId() -> std::size_t 9 | { 10 | static std::atomic_size_t id = 0; 11 | return id.fetch_add(1, std::memory_order_relaxed); 12 | } 13 | 14 | enum class JobState : std::uint16_t { 15 | Ready, 16 | Executing, 17 | Pending, 18 | Final, 19 | }; 20 | 21 | struct WorkerArg { 22 | union { 23 | void* ptr; 24 | std::uint8_t u8; 25 | std::uint16_t u16; 26 | std::uint32_t u32; 27 | std::uint64_t u64; 28 | std::int8_t i8; 29 | std::int16_t i16; 30 | std::int32_t i32; 31 | std::int64_t i64; 32 | float f32; 33 | double f64; 34 | }; 35 | }; 36 | constexpr WorkerArg kWorkerArgNull{.ptr = nullptr}; 37 | 38 | struct WorkerJob { 39 | using WorkerFn = void (*)(WorkerJob* task, WorkerArg args) noexcept; 40 | constexpr WorkerJob(WorkerFn fn, std::atomic* state) noexcept : run(fn), next(nullptr), state(state) {} 41 | 42 | WorkerFn run; 43 | WorkerJob* next; 44 | std::atomic* state; 45 | }; 46 | 47 | using WorkerJobQueue = util::Queue<&WorkerJob::next>; 48 | 49 | inline auto runJob(WorkerJob* job, WorkerArg args) noexcept -> void 50 | { 51 | assert(job != nullptr && "job should not be nullptr"); 52 | job->run(job, args); 53 | } 54 | 55 | inline auto emptyFn(WorkerJob*, WorkerArg) noexcept -> void { assert(false && "empty job should not be executed"); } 56 | namespace detail { 57 | inline WorkerJob kEmptyJob{emptyFn, nullptr}; 58 | inline WorkerJob kDetachJob{emptyFn, nullptr}; 59 | } // namespace detail 60 | template 61 | struct Task; 62 | 63 | struct ExeOpt { 64 | std::uint16_t mTid; 65 | 66 | enum Opt : std::uint8_t { 67 | Balance, 68 | PreferInOne, 69 | ForceInOne, 70 | } mOpt = Balance; 71 | 72 | enum Pri : std::uint8_t { 73 | Low, 74 | High, 75 | } mPri = Low; 76 | 77 | constexpr static auto create(std::uint16_t tid, Opt opt, Pri pri) noexcept -> ExeOpt 78 | { 79 | return {.mTid = tid, .mOpt = opt, .mPri = pri}; 80 | } 81 | constexpr static auto prefInOne(Pri pri = Low) noexcept -> ExeOpt 82 | { 83 | return {.mTid = 0, .mOpt = PreferInOne, .mPri = pri}; 84 | } 85 | constexpr static auto balance(Pri pri = Low) noexcept -> ExeOpt { return {.mTid = 0, .mOpt = Balance, .mPri = pri}; } 86 | }; 87 | 88 | class Executor { 89 | public: 90 | Executor() = default; 91 | virtual ~Executor() noexcept = default; 92 | 93 | virtual auto execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void = 0; 94 | virtual auto execute(WorkerJob* handle, ExeOpt opt) noexcept -> void = 0; 95 | virtual auto runMain(Task<> task) -> void = 0; 96 | }; 97 | 98 | } // namespace coco -------------------------------------------------------------------------------- /src/inl_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/inl_executor.hpp" 2 | 3 | namespace coco { 4 | auto InlExecutor::forceStop() -> void 5 | { 6 | switch (mState) { 7 | case State::Waiting: 8 | mProactor->notify(); 9 | mState = State::Stop; 10 | break; 11 | case State::Executing: 12 | mState = State::Stop; 13 | break; 14 | case State::Stop: 15 | break; 16 | } 17 | } 18 | auto InlExecutor::loop() -> void 19 | { 20 | while (true) { 21 | auto currState = mState; 22 | if (currState == State::Waiting) { 23 | mProactor->wait(); 24 | if (mMainTaskState.load() == JobState::Final && mTaskQueue.empty()) { 25 | mState = State::Stop; 26 | } else { 27 | mState = State::Waiting; 28 | } 29 | mState = State::Executing; 30 | } else if (currState == State::Executing) { 31 | processTasks(); 32 | if (mMainTaskState.load() == JobState::Final && mTaskQueue.empty()) { 33 | mState = State::Stop; 34 | } else { 35 | mState = State::Waiting; 36 | } 37 | } else if (currState == State::Stop) [[unlikely]] { 38 | return; 39 | } else { 40 | assert(false); 41 | } 42 | } 43 | } 44 | auto InlExecutor::processTasks() -> void 45 | { 46 | while (auto job = mTaskQueue.popFront()) { 47 | runJob(job, kWorkerArgNull); 48 | } 49 | if (mMainTaskState.load() == JobState::Final) { 50 | mState = State::Stop; 51 | } 52 | } 53 | auto InlExecutor::execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void 54 | { 55 | mTaskQueue.append(std::move(queue)); 56 | } 57 | auto InlExecutor::execute(WorkerJob* handle, ExeOpt opt) noexcept -> void { mTaskQueue.pushBack(handle); } 58 | auto InlExecutor::runMain(Task<> task) -> void 59 | { 60 | Proactor::get().attachExecutor(this, 0); 61 | task.promise().setState(&mMainTaskState); 62 | execute(task.promise().getThisJob(), {}); 63 | processTasks(); 64 | loop(); 65 | } 66 | } // namespace coco -------------------------------------------------------------------------------- /src/mt_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/mt_executor.hpp" 2 | #include 3 | 4 | namespace coco { 5 | auto Worker::forceStop() -> void 6 | { 7 | auto state = mState.load(std::memory_order_acquire); 8 | switch (state) { 9 | case State::Waiting: { 10 | notify(); 11 | auto r = mState.compare_exchange_strong(state, State::Stop, std::memory_order_acq_rel); 12 | } break; 13 | case State::Executing: { 14 | auto r = mState.compare_exchange_strong(state, State::Stop, std::memory_order_acq_rel); 15 | } break; 16 | case State::Stop: { 17 | processTasks(); 18 | return; 19 | } break; 20 | } 21 | } 22 | auto Worker::start(std::latch& latch) -> void 23 | { 24 | mProactor = &Proactor::get(); 25 | mState = State::Waiting; 26 | latch.count_down(); 27 | } 28 | auto Worker::loop() -> void 29 | { 30 | while (true) { 31 | auto currState = mState.load(std::memory_order_relaxed); 32 | if (currState == State::Waiting) { 33 | mProactor->wait(); 34 | auto r = mState.compare_exchange_strong(currState, State::Executing, std::memory_order_acq_rel); 35 | if (r == false) { // must be stop 36 | return; 37 | } 38 | } else if (currState == State::Executing) { 39 | processTasks(); 40 | auto r = mState.compare_exchange_strong(currState, State::Waiting, std::memory_order_acq_rel); 41 | if (r == false) { 42 | return; 43 | } 44 | } else if (currState == State::Stop) [[unlikely]] { 45 | return; 46 | } else { 47 | assert(false); 48 | } 49 | } 50 | } 51 | auto Worker::notify() -> void { mProactor->notify(); } 52 | auto Worker::processTasks() -> void 53 | { 54 | mQueueMt.lock(); 55 | auto jobs = std::move(mTaskQueue); 56 | mQueueMt.unlock(); 57 | while (auto job = jobs.popFront()) { 58 | runJob(job, kWorkerArgNull); 59 | } 60 | } 61 | 62 | auto Worker::pushTask(WorkerJob* job, ExeOpt opt) -> void 63 | { 64 | mQueueMt.lock(); 65 | if (opt.mPri == ExeOpt::High) [[unlikely]] { 66 | mTaskQueue.pushFront(job); 67 | } else { 68 | mTaskQueue.pushBack(job); 69 | } 70 | mQueueMt.unlock(); 71 | notify(); 72 | } 73 | 74 | auto Worker::tryPushTask(WorkerJob* job, ExeOpt opt) -> bool 75 | { 76 | std::unique_lock lk(mQueueMt, std::try_to_lock); 77 | if (!lk.owns_lock()) { 78 | return false; 79 | }; 80 | if (opt.mPri == ExeOpt::High) [[unlikely]] { 81 | mTaskQueue.pushFront(job); 82 | } else { 83 | mTaskQueue.pushBack(job); 84 | } 85 | notify(); 86 | return true; 87 | } 88 | auto Worker::pushTask(WorkerJobQueue jobs, ExeOpt opt) -> void 89 | { 90 | assert(jobs.back() == nullptr); 91 | mQueueMt.lock(); 92 | if (opt.mPri == ExeOpt::High) [[unlikely]] { 93 | mTaskQueue.prepend(std::move(jobs)); 94 | } else { 95 | mTaskQueue.append(std::move(jobs)); 96 | } 97 | mQueueMt.unlock(); 98 | notify(); 99 | } 100 | auto Worker::tryPushTask(WorkerJobQueue jobs, ExeOpt opt) -> bool 101 | { 102 | std::unique_lock lk(mQueueMt, std::try_to_lock); 103 | if (!lk.owns_lock()) { 104 | return false; 105 | } 106 | if (opt.mPri == ExeOpt::High) [[unlikely]] { 107 | mTaskQueue.prepend(std::move(jobs)); 108 | } else { 109 | mTaskQueue.append(std::move(jobs)); 110 | } 111 | notify(); 112 | return true; 113 | } 114 | 115 | // MultiThread executor 116 | 117 | MtExecutor::MtExecutor(std::size_t threadCount) : mThreadCount(threadCount) 118 | { 119 | mWorkers.reserve(threadCount); 120 | mThreads.reserve(threadCount); 121 | try { 122 | for (int i = 0; i < threadCount; i++) { 123 | mWorkers.emplace_back(std::make_unique()); 124 | } 125 | auto finishLatch = std::latch(threadCount); 126 | for (int i = 0; i < threadCount; i++) { 127 | mThreads.emplace_back([this, i, &finishLatch] { 128 | mWorkers[i]->start(finishLatch); 129 | Proactor::get().attachExecutor(this, i); 130 | mWorkers[i]->loop(); 131 | }); 132 | } 133 | finishLatch.wait(); 134 | } catch (...) { 135 | requestStop(); 136 | join(); 137 | throw; 138 | } 139 | } 140 | 141 | auto MtExecutor::requestStop() noexcept -> void 142 | { 143 | for (auto& worker : mWorkers) { 144 | worker->forceStop(); 145 | } 146 | } 147 | 148 | auto MtExecutor::join() noexcept -> void 149 | { 150 | for (auto& thread : mThreads) { 151 | thread.join(); 152 | } 153 | } 154 | 155 | auto MtExecutor::execute(WorkerJob* job, ExeOpt opt) noexcept -> void 156 | { 157 | if (opt.mOpt == ExeOpt::PreferInOne) { 158 | auto b = mWorkers[opt.mTid]->tryEnqeue(job, opt); 159 | if (b) { 160 | return; 161 | } 162 | } 163 | balanceEnqueue(job, opt); 164 | } 165 | auto MtExecutor::execute(WorkerJobQueue&& queue, std::size_t count, ExeOpt opt) noexcept -> void 166 | { 167 | if (opt.mOpt == ExeOpt::PreferInOne) { 168 | auto b = mWorkers[opt.mTid]->tryEnqeue(std::move(queue), opt); 169 | if (b) { 170 | return; 171 | } 172 | } 173 | 174 | if (count == 0) { 175 | balanceEnqueue(std::move(queue), opt); 176 | } else { 177 | while (!queue.empty()) { 178 | auto const perThread = count / mThreadCount; 179 | auto perThreadJobs = queue.popFront(perThread); 180 | balanceEnqueue(std::move(perThreadJobs), opt); 181 | } 182 | } 183 | } 184 | auto MtExecutor::runMain(Task<> task) -> void 185 | { 186 | auto& promise = task.promise(); 187 | auto taskState = std::atomic(JobState::Ready); 188 | promise.setState(&taskState); 189 | this->execute(promise.getThisJob(), ExeOpt{.mOpt = ExeOpt::ForceInOne, .mPri = ExeOpt::High}); 190 | while (taskState.load() != JobState::Final) { 191 | taskState.wait(JobState::Ready); 192 | } 193 | }; 194 | 195 | } // namespace coco -------------------------------------------------------------------------------- /src/sys/socket_addr.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/sys/socket_addr.hpp" 2 | 3 | namespace coco::sys { 4 | auto Ipv4Addr::to_string() const -> std::string 5 | { 6 | constexpr char max[] = "255.255.255.255"; 7 | std::string result; 8 | result.reserve(sizeof(max)); 9 | std::format_to_n(std::back_inserter(result), sizeof(max), "{}", *this); 10 | return result; 11 | } 12 | 13 | auto Ipv6Addr::to_string() const -> std::string 14 | { 15 | constexpr char max[] = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"; 16 | std::string result; 17 | result.reserve(sizeof(max)); 18 | std::format_to_n(std::back_inserter(result), sizeof(max), "{}", *this); 19 | return result; 20 | } 21 | 22 | auto SocketAddrV4::to_string() const -> std::string 23 | { 24 | constexpr char max[] = "255.255.255.255:65536"; 25 | std::string result; 26 | result.reserve(sizeof(max)); 27 | std::format_to_n(std::back_inserter(result), 22, "{}:{}", mAddr, mPort); 28 | return result; 29 | } 30 | 31 | auto SocketAddrV6::to_string() const -> std::string 32 | { 33 | constexpr char max[] = "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65536"; 34 | std::string result; 35 | result.reserve(sizeof(max)); 36 | std::format_to_n(std::back_inserter(result), 42, "[{}]:{}", mAddr, mPort); 37 | return result; 38 | } 39 | 40 | auto SocketAddr::to_string() const -> std::string { return std::format("{}", *this); } 41 | } // namespace coco::sys -------------------------------------------------------------------------------- /src/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/timer.hpp" 2 | #include 3 | 4 | namespace coco { 5 | auto TimerManager::addTimer(Instant time, WorkerJob* job) noexcept -> void 6 | { 7 | std::scoped_lock lock(mPendingJobsMt); 8 | mPendingJobs.push({time, job, TimerOpKind::Add}); 9 | } 10 | auto TimerManager::deleteTimer(void* jobId) noexcept -> void 11 | { 12 | std::scoped_lock lock(mPendingJobsMt); 13 | mPendingJobs.push(TimerOp{.instant = Instant(), .jobId = jobId, .kind = TimerOpKind::Delete}); 14 | } 15 | auto TimerManager::nextInstant() const noexcept -> Instant 16 | { 17 | if (mTimers.empty()) { 18 | return Instant::max(); 19 | } 20 | return mTimers.top().instant; 21 | } 22 | auto TimerManager::processTimers() -> std::pair 23 | { 24 | while (true) { 25 | TimerOp op{}; 26 | { 27 | std::scoped_lock lock(mPendingJobsMt); 28 | if (!mPendingJobs.empty()) { 29 | op = mPendingJobs.front(); 30 | mPendingJobs.pop(); 31 | } else { 32 | break; 33 | } 34 | } 35 | switch (op.kind) { 36 | case TimerOpKind::Add: { 37 | mTimers.insert({op.instant, op.job}); 38 | } break; 39 | case TimerOpKind::Delete: { 40 | mDeleted.insert(op.jobId); 41 | } break; 42 | } 43 | } 44 | 45 | WorkerJobQueue jobs; 46 | std::size_t count = 0; 47 | auto now = std::chrono::steady_clock::now(); 48 | while (!mTimers.empty() && mTimers.top().instant <= now) { 49 | auto job = mTimers.top().job; 50 | mTimers.pop(); 51 | if (auto it = mDeleted.find(job->state); it != mDeleted.end()) { 52 | mDeleted.erase(it); 53 | continue; 54 | } 55 | jobs.pushBack(job); 56 | } 57 | return {std::move(jobs), count}; 58 | } 59 | } // namespace coco -------------------------------------------------------------------------------- /src/uring.cpp: -------------------------------------------------------------------------------- 1 | #include "coco/mt_executor.hpp" 2 | 3 | namespace coco { 4 | IoUring::IoUring() 5 | { 6 | if (auto r = ::io_uring_queue_init(kIoUringQueueSize, &mUring, 0); r != 0) { 7 | throw std::system_error(-r, std::system_category(), "create uring instance failed"); 8 | } 9 | mEventFd = ::eventfd(0, 0); 10 | if (mEventFd < 0) { 11 | throw std::system_error(errno, std::system_category(), "create eventfd failed"); 12 | } 13 | auto sqe = fetchSqe(); 14 | ::io_uring_prep_poll_multishot(sqe, mEventFd, POLLIN); 15 | ::io_uring_sqe_set_data(sqe, nullptr); 16 | ::io_uring_submit(&mUring); 17 | } 18 | IoUring::~IoUring() 19 | { 20 | auto sqe = fetchSqe(); 21 | ::io_uring_prep_poll_remove(sqe, 0); 22 | ::io_uring_submit_and_wait(&mUring, 1); 23 | ::close(mEventFd); 24 | ::io_uring_queue_exit(&mUring); 25 | } 26 | auto IoUring::prepRecv(Token token, int fd, std::span buf, int flag) noexcept -> void 27 | { 28 | auto sqe = fetchSqe(); 29 | ::io_uring_prep_recv(sqe, fd, (void*)buf.data(), buf.size(), 0); 30 | ::io_uring_sqe_set_data(sqe, token); 31 | } 32 | auto IoUring::prepSend(Token token, int fd, std::span buf, int flag) noexcept -> void 33 | { 34 | auto sqe = fetchSqe(); 35 | ::io_uring_prep_send(sqe, fd, (void const*)buf.data(), buf.size(), flag); 36 | ::io_uring_sqe_set_data(sqe, token); 37 | } 38 | auto IoUring::prepRecvMsg(Token token, int fd, msghdr* msg, unsigned flag) noexcept -> void 39 | { 40 | auto sqe = fetchSqe(); 41 | ::io_uring_prep_recvmsg(sqe, fd, msg, flag); 42 | ::io_uring_sqe_set_data(sqe, token); 43 | } 44 | auto IoUring::prepSendMsg(Token token, int fd, msghdr* msg, unsigned flag) noexcept -> void 45 | { 46 | auto sqe = fetchSqe(); 47 | ::io_uring_prep_sendmsg(sqe, fd, msg, flag); 48 | ::io_uring_sqe_set_data(sqe, token); 49 | } 50 | auto IoUring::prepAccept(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags) noexcept -> void 51 | { 52 | auto sqe = fetchSqe(); 53 | ::io_uring_prep_accept(sqe, fd, addr, addrlen, flags); 54 | ::io_uring_sqe_set_data(sqe, token); 55 | } 56 | auto IoUring::prepAcceptMt(Token token, int fd, sockaddr* addr, socklen_t* addrlen, int flags) noexcept -> void 57 | { 58 | auto sqe = fetchSqe(); 59 | ::io_uring_prep_multishot_accept(sqe, fd, addr, addrlen, flags); 60 | ::io_uring_sqe_set_data(sqe, token); 61 | } 62 | auto IoUring::prepConnect(Token token, int fd, sockaddr* addr, socklen_t addrlen) noexcept -> void 63 | { 64 | auto sqe = fetchSqe(); 65 | ::io_uring_prep_connect(sqe, fd, addr, addrlen); 66 | ::io_uring_sqe_set_data(sqe, token); 67 | } 68 | auto IoUring::seen(io_uring_cqe* cqe) noexcept -> void { ::io_uring_cqe_seen(&mUring, cqe); } 69 | auto IoUring::submitWait(int waitn) noexcept -> std::errc 70 | { 71 | auto r = ::io_uring_submit_and_wait(&mUring, waitn); 72 | if (r < 0) { 73 | return std::errc(-r); 74 | } 75 | return std::errc(0); 76 | } 77 | auto IoUring::prepCancel(int fd) noexcept -> void 78 | { 79 | auto sqe = fetchSqe(); 80 | ::io_uring_prep_cancel_fd(sqe, fd, 0); 81 | } 82 | auto IoUring::prepCancel(Token token) noexcept -> void 83 | { 84 | auto sqe = fetchSqe(); 85 | ::io_uring_prep_cancel(sqe, token, 0); 86 | ::io_uring_sqe_set_data(sqe, token); 87 | } 88 | auto IoUring::prepClose(Token token, int fd) noexcept -> void 89 | { 90 | auto sqe = fetchSqe(); 91 | ::io_uring_prep_close(sqe, fd); 92 | ::io_uring_sqe_set_data(sqe, token); 93 | } 94 | auto IoUring::prepRead(Token token, int fd, std::span buf, off_t offset) noexcept -> void 95 | { 96 | auto sqe = fetchSqe(); 97 | ::io_uring_prep_read(sqe, fd, (void*)buf.data(), buf.size(), offset); 98 | ::io_uring_sqe_set_data(sqe, token); 99 | } 100 | auto IoUring::prepWrite(Token token, int fd, std::span buf, off_t offset) noexcept -> void 101 | { 102 | auto sqe = fetchSqe(); 103 | ::io_uring_prep_write(sqe, fd, (void const*)buf.data(), buf.size(), offset); 104 | ::io_uring_sqe_set_data(sqe, token); 105 | } 106 | auto IoUring::notify() noexcept -> void 107 | { 108 | auto buf = std::uint64_t(0); 109 | auto r = ::write(mEventFd, &buf, sizeof(buf)); 110 | assert(r); 111 | } 112 | auto IoUring::fetchSqe() -> io_uring_sqe* 113 | { 114 | auto sqe = ::io_uring_get_sqe(&mUring); 115 | if (sqe == nullptr) [[unlikely]] { 116 | throw std::runtime_error("sqe full"); // TODO: better without exception. 117 | } 118 | return sqe; 119 | } 120 | auto IoUring::advance(std::uint32_t n) noexcept -> void { ::io_uring_cq_advance(&mUring, n); } 121 | auto IoUring::submit() noexcept -> std::errc 122 | { 123 | auto r = ::io_uring_submit(&mUring); 124 | if (r < 0) { 125 | return std::errc(-r); 126 | } 127 | return std::errc(0); 128 | } 129 | } // namespace coco 130 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | add_external(googletest; https://ghproxy.com/https://github.com/google/googletest.git; v1.13.0) 4 | 5 | add_executable(timer_test timer_test.cpp) 6 | target_link_libraries(timer_test gtest_main Coco) 7 | 8 | include(GoogleTest) 9 | gtest_discover_tests(timer_test) -------------------------------------------------------------------------------- /test/timer_test.cpp: -------------------------------------------------------------------------------- 1 | /* #include 2 | 3 | #include "coco/timer.hpp" 4 | struct MyJob : coco::WorkerJob { 5 | MyJob(int i) : i(i), WorkerJob(&run) {} 6 | static auto run(coco::WorkerJob* job, void*) noexcept -> void {} 7 | int i; 8 | }; 9 | 10 | using namespace std::chrono_literals; 11 | 12 | TEST(Timer, General) 13 | { 14 | auto mgr = coco::TimerManager(10); 15 | std::vector jobs; 16 | for (int i = 0; i < 21; i++) { 17 | jobs.push_back(new MyJob(i)); 18 | } 19 | auto now = std::chrono::steady_clock::now(); 20 | for (int i = 0; i < 21; i++) { 21 | mgr.addTimer(now + std::chrono::milliseconds(i * 10), jobs[i]); 22 | } 23 | std::this_thread::sleep_for(100ms); 24 | auto okJobs = mgr.processTimers().first; 25 | int cnt = 0; 26 | while (auto job = okJobs.popFront()) { 27 | ASSERT_EQ(job->id, cnt++); 28 | } 29 | ASSERT_EQ(cnt, 11); // (0 ~ 10) * 10ms 30 | for (int i = 11; i < jobs.size() - 1; i++) { 31 | mgr.deleteTimer(jobs[i]->id); 32 | } 33 | std::this_thread::sleep_for(100ms); 34 | okJobs = mgr.processTimers().first; 35 | auto job = okJobs.popFront(); 36 | ASSERT_EQ(job->id, 20); 37 | ASSERT_TRUE(okJobs.empty()); 38 | for (auto job : jobs) { 39 | delete (MyJob*)job; 40 | } 41 | } 42 | 43 | TEST(Timer, AddAndDelete) 44 | { 45 | auto mgr = coco::TimerManager(10); 46 | auto job1 = new MyJob(100); 47 | auto job2 = new MyJob(200); 48 | auto job3 = new MyJob(300); 49 | 50 | auto now = std::chrono::steady_clock::now(); 51 | mgr.addTimer(now + 100ms, job1); 52 | mgr.addTimer(now + 200ms, job2); 53 | mgr.addTimer(now + 300ms, job3); 54 | mgr.deleteTimer(job2->id); 55 | 56 | std::this_thread::sleep_for(100ms); 57 | auto okJobs = mgr.processTimers().first; 58 | auto job = okJobs.popFront(); 59 | ASSERT_EQ(job->id, job1->id); 60 | ASSERT_EQ(okJobs.empty(), 1); 61 | ASSERT_EQ(mgr.nextInstant(), now + 200ms); 62 | 63 | std::this_thread::sleep_for(200ms); 64 | okJobs = mgr.processTimers().first; 65 | job = okJobs.popFront(); 66 | ASSERT_EQ(job->id, job3->id); 67 | ASSERT_TRUE(okJobs.empty()); 68 | 69 | std::this_thread::sleep_for(100ms); 70 | okJobs = mgr.processTimers().first; 71 | ASSERT_TRUE(okJobs.empty()); 72 | ASSERT_EQ(mgr.nextInstant(), coco::Instant::max()); 73 | 74 | delete job1; 75 | delete job2; 76 | delete job3; 77 | } */ --------------------------------------------------------------------------------