├── .clang-format ├── .editorconfig ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── cmake ├── CMakeLists.txt ├── FindCoroutines.cmake ├── Findliburing.cmake └── cppcoroConfig.cmake ├── include └── cppcoro │ ├── async_auto_reset_event.hpp │ ├── async_generator.hpp │ ├── async_latch.hpp │ ├── async_manual_reset_event.hpp │ ├── async_mutex.hpp │ ├── async_scope.hpp │ ├── awaitable_traits.hpp │ ├── broken_promise.hpp │ ├── cancellation_registration.hpp │ ├── cancellation_source.hpp │ ├── cancellation_token.hpp │ ├── config.hpp │ ├── coroutine.hpp │ ├── detail │ ├── any.hpp │ ├── get_awaiter.hpp │ ├── is_awaiter.hpp │ ├── lightweight_manual_reset_event.hpp │ ├── linux.hpp │ ├── linux_io_operation.hpp │ ├── linux_uring_queue.hpp │ ├── manual_lifetime.hpp │ ├── remove_rvalue_reference.hpp │ ├── sync_wait_task.hpp │ ├── unwrap_reference.hpp │ ├── void_value.hpp │ ├── when_all_counter.hpp │ ├── when_all_ready_awaitable.hpp │ ├── when_all_task.hpp │ ├── win32.hpp │ └── win32_overlapped_operation.hpp │ ├── file.hpp │ ├── file_buffering_mode.hpp │ ├── file_open_mode.hpp │ ├── file_read_operation.hpp │ ├── file_share_mode.hpp │ ├── file_write_operation.hpp │ ├── filesystem.hpp │ ├── fmap.hpp │ ├── generator.hpp │ ├── inline_scheduler.hpp │ ├── io_service.hpp │ ├── is_awaitable.hpp │ ├── multi_producer_sequencer.hpp │ ├── net │ ├── ip_address.hpp │ ├── ip_endpoint.hpp │ ├── ipv4_address.hpp │ ├── ipv4_endpoint.hpp │ ├── ipv6_address.hpp │ ├── ipv6_endpoint.hpp │ ├── socket.hpp │ ├── socket_accept_operation.hpp │ ├── socket_connect_operation.hpp │ ├── socket_disconnect_operation.hpp │ ├── socket_recv_from_operation.hpp │ ├── socket_recv_operation.hpp │ ├── socket_send_operation.hpp │ └── socket_send_to_operation.hpp │ ├── on_scope_exit.hpp │ ├── operation_cancelled.hpp │ ├── read_only_file.hpp │ ├── read_write_file.hpp │ ├── readable_file.hpp │ ├── recursive_generator.hpp │ ├── resume_on.hpp │ ├── round_robin_scheduler.hpp │ ├── schedule_on.hpp │ ├── sequence_barrier.hpp │ ├── sequence_range.hpp │ ├── sequence_traits.hpp │ ├── shared_task.hpp │ ├── single_consumer_async_auto_reset_event.hpp │ ├── single_consumer_event.hpp │ ├── single_producer_sequencer.hpp │ ├── static_thread_pool.hpp │ ├── sync_wait.hpp │ ├── task.hpp │ ├── when_all.hpp │ ├── when_all_ready.hpp │ ├── writable_file.hpp │ └── write_only_file.hpp ├── lib ├── CMakeLists.txt ├── async_auto_reset_event.cpp ├── async_manual_reset_event.cpp ├── async_mutex.cpp ├── auto_reset_event.cpp ├── auto_reset_event.hpp ├── build.cake ├── cancellation_registration.cpp ├── cancellation_source.cpp ├── cancellation_state.cpp ├── cancellation_state.hpp ├── cancellation_token.cpp ├── file.cpp ├── file_read_operation.cpp ├── file_write_operation.cpp ├── io_service.cpp ├── ip_address.cpp ├── ip_endpoint.cpp ├── ipv4_address.cpp ├── ipv4_endpoint.cpp ├── ipv6_address.cpp ├── ipv6_endpoint.cpp ├── lightweight_manual_reset_event.cpp ├── linux.cpp ├── linux_uring_queue.cpp ├── read_only_file.cpp ├── read_write_file.cpp ├── readable_file.cpp ├── socket.cpp ├── socket_accept_operation.cpp ├── socket_connect_operation.cpp ├── socket_disconnect_operation.cpp ├── socket_helpers.cpp ├── socket_helpers.hpp ├── socket_recv_from_operation.cpp ├── socket_recv_operation.cpp ├── socket_send_operation.cpp ├── socket_send_to_operation.cpp ├── spin_mutex.cpp ├── spin_mutex.hpp ├── spin_wait.cpp ├── spin_wait.hpp ├── static_thread_pool.cpp ├── use.cake ├── win32.cpp ├── writable_file.cpp └── write_only_file.cpp └── test ├── CMakeLists.txt ├── async_auto_reset_event_tests.cpp ├── async_generator_tests.cpp ├── async_latch_tests.cpp ├── async_manual_reset_event_tests.cpp ├── async_mutex_tests.cpp ├── build.cake ├── cancellation_token_tests.cpp ├── counted.cpp ├── counted.hpp ├── doctest ├── cppcoro_doctest.h ├── doctest.cmake ├── doctest.h └── doctestAddTests.cmake ├── file_tests.cpp ├── generator_tests.cpp ├── io_service_fixture.hpp ├── io_service_tests.cpp ├── ip_address_tests.cpp ├── ip_endpoint_tests.cpp ├── ipv4_address_tests.cpp ├── ipv4_endpoint_tests.cpp ├── ipv6_address_tests.cpp ├── ipv6_endpoint_tests.cpp ├── main.cpp ├── multi_producer_sequencer_tests.cpp ├── recursive_generator_tests.cpp ├── scheduling_operator_tests.cpp ├── sequence_barrier_tests.cpp ├── shared_task_tests.cpp ├── single_consumer_async_auto_reset_event_tests.cpp ├── single_producer_sequencer_tests.cpp ├── socket_tests.cpp ├── static_thread_pool_tests.cpp ├── sync_wait_tests.cpp ├── task_tests.cpp ├── when_all_ready_tests.cpp └── when_all_tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | --- 4 | Language: Cpp 5 | Standard: Cpp11 6 | ColumnLimit: 100 7 | TabWidth: 4 8 | IndentWidth: 4 9 | UseTab: ForContinuationAndIndentation 10 | AccessModifierOffset: -4 11 | AlignAfterOpenBracket: AlwaysBreak 12 | AlignConsecutiveAssignments: false 13 | AlignConsecutiveDeclarations: false 14 | AlignEscapedNewlines: Left 15 | AlignOperands: false 16 | AlignTrailingComments: true 17 | AllowAllParametersOfDeclarationOnNextLine: true 18 | AllowShortBlocksOnASingleLine: false 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortIfStatementsOnASingleLine: false 21 | AllowShortFunctionsOnASingleLine: InlineOnly 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakTemplateDeclarations: true 25 | BinPackArguments: false 26 | BinPackParameters: false 27 | BreakBeforeBinaryOperators: None 28 | BreakBeforeBraces: Custom 29 | BraceWrapping: { 30 | AfterClass: true, 31 | AfterControlStatement: true, 32 | AfterEnum: true, 33 | AfterFunction: true, 34 | AfterNamespace: true, 35 | AfterStruct: true, 36 | AfterUnion: true, 37 | BeforeCatch: true, 38 | BeforeElse: true, 39 | IndentBraces: false, 40 | #SplitEmptyFunctionBody: false 41 | } 42 | BreakBeforeInheritanceComma: true 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializers: BeforeComma 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: false 47 | IncludeCategories: 48 | - Regex: '^$' 49 | Priority: 1 50 | - Regex: '^ 9 | #include 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | class async_auto_reset_event_operation; 15 | 16 | /// An async auto-reset event is a coroutine synchronisation abstraction 17 | /// that allows one or more coroutines to wait until some thread calls 18 | /// set() on the event. 19 | /// 20 | /// When a coroutine awaits a 'set' event the event is automatically 21 | /// reset back to the 'not set' state, thus the name 'auto reset' event. 22 | class async_auto_reset_event 23 | { 24 | public: 25 | 26 | /// Initialise the event to either 'set' or 'not set' state. 27 | async_auto_reset_event(bool initiallySet = false) noexcept; 28 | 29 | ~async_auto_reset_event(); 30 | 31 | /// Wait for the event to enter the 'set' state. 32 | /// 33 | /// If the event is already 'set' then the event is set to the 'not set' 34 | /// state and the awaiting coroutine continues without suspending. 35 | /// Otherwise, the coroutine is suspended and later resumed when some 36 | /// thread calls 'set()'. 37 | /// 38 | /// Note that the coroutine may be resumed inside a call to 'set()' 39 | /// or inside another thread's call to 'operator co_await()'. 40 | async_auto_reset_event_operation operator co_await() const noexcept; 41 | 42 | /// Set the state of the event to 'set'. 43 | /// 44 | /// If there are pending coroutines awaiting the event then one 45 | /// pending coroutine is resumed and the state is immediately 46 | /// set back to the 'not set' state. 47 | /// 48 | /// This operation is a no-op if the event was already 'set'. 49 | void set() noexcept; 50 | 51 | /// Set the state of the event to 'not-set'. 52 | /// 53 | /// This is a no-op if the state was already 'not set'. 54 | void reset() noexcept; 55 | 56 | private: 57 | 58 | friend class async_auto_reset_event_operation; 59 | 60 | void resume_waiters(std::uint64_t initialState) const noexcept; 61 | 62 | // Bits 0-31 - Set count 63 | // Bits 32-63 - Waiter count 64 | mutable std::atomic m_state; 65 | 66 | mutable std::atomic m_newWaiters; 67 | 68 | mutable async_auto_reset_event_operation* m_waiters; 69 | 70 | }; 71 | 72 | class async_auto_reset_event_operation 73 | { 74 | public: 75 | 76 | async_auto_reset_event_operation() noexcept; 77 | 78 | explicit async_auto_reset_event_operation(const async_auto_reset_event& event) noexcept; 79 | 80 | async_auto_reset_event_operation(const async_auto_reset_event_operation& other) noexcept; 81 | 82 | bool await_ready() const noexcept { return m_event == nullptr; } 83 | bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept; 84 | void await_resume() const noexcept {} 85 | 86 | private: 87 | 88 | friend class async_auto_reset_event; 89 | 90 | const async_auto_reset_event* m_event; 91 | async_auto_reset_event_operation* m_next; 92 | cppcoro::coroutine_handle<> m_awaiter; 93 | std::atomic m_refCount; 94 | 95 | }; 96 | } 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /include/cppcoro/async_latch.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_ASYNC_LATCH_HPP_INCLUDED 6 | #define CPPCORO_ASYNC_LATCH_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace cppcoro 14 | { 15 | class async_latch 16 | { 17 | public: 18 | 19 | /// Construct the latch with the specified initial count. 20 | /// 21 | /// \param initialCount 22 | /// The initial count of the latch. The latch will become signalled once 23 | /// \c this->count_down() has been called \p initialCount times. 24 | /// The latch will be immediately signalled on construction if this 25 | /// parameter is zero or negative. 26 | async_latch(std::ptrdiff_t initialCount) noexcept 27 | : m_count(initialCount) 28 | , m_event(initialCount <= 0) 29 | {} 30 | 31 | /// Query if the latch has become signalled. 32 | /// 33 | /// The latch is marked as signalled once the count reaches zero. 34 | bool is_ready() const noexcept { return m_event.is_set(); } 35 | 36 | /// Decrement the count by n. 37 | /// 38 | /// Any coroutines awaiting this latch will be resumed once the count 39 | /// reaches zero. ie. when this method has been called at least 'initialCount' 40 | /// times. 41 | /// 42 | /// Any awaiting coroutines that are currently suspended waiting for the 43 | /// latch to become signalled will be resumed inside the last call to this 44 | /// method (ie. the call that decrements the count to zero). 45 | /// 46 | /// \param n 47 | /// The amount to decrement the count by. 48 | void count_down(std::ptrdiff_t n = 1) noexcept 49 | { 50 | if (m_count.fetch_sub(n, std::memory_order_acq_rel) <= n) 51 | { 52 | m_event.set(); 53 | } 54 | } 55 | 56 | /// Allows the latch to be awaited within a coroutine. 57 | /// 58 | /// If the latch is already signalled (ie. the count has been decremented 59 | /// to zero) then the awaiting coroutine will continue without suspending. 60 | /// Otherwise, the coroutine will suspend and will later be resumed inside 61 | /// a call to `count_down()`. 62 | auto operator co_await() const noexcept 63 | { 64 | return m_event.operator co_await(); 65 | } 66 | 67 | private: 68 | 69 | std::atomic m_count; 70 | async_manual_reset_event m_event; 71 | 72 | }; 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /include/cppcoro/async_manual_reset_event.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED 6 | #define CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | class async_manual_reset_event_operation; 15 | 16 | /// An async manual-reset event is a coroutine synchronisation abstraction 17 | /// that allows one or more coroutines to wait until some thread calls 18 | /// set() on the event. 19 | /// 20 | /// When a coroutine awaits a 'set' event the coroutine continues without 21 | /// suspending. Otherwise, if it awaits a 'not set' event the coroutine is 22 | /// suspended and is later resumed inside the call to 'set()'. 23 | /// 24 | /// \seealso async_auto_reset_event 25 | class async_manual_reset_event 26 | { 27 | public: 28 | 29 | /// Initialise the event to either 'set' or 'not set' state. 30 | /// 31 | /// \param initiallySet 32 | /// If 'true' then initialises the event to the 'set' state, otherwise 33 | /// initialises the event to the 'not set' state. 34 | async_manual_reset_event(bool initiallySet = false) noexcept; 35 | 36 | ~async_manual_reset_event(); 37 | 38 | /// Wait for the event to enter the 'set' state. 39 | /// 40 | /// If the event is already 'set' then the coroutine continues without 41 | /// suspending. 42 | /// 43 | /// Otherwise, the coroutine is suspended and later resumed when some 44 | /// thread calls 'set()'. The coroutine will be resumed inside the next 45 | /// call to 'set()'. 46 | async_manual_reset_event_operation operator co_await() const noexcept; 47 | 48 | /// Query if the event is currently in the 'set' state. 49 | bool is_set() const noexcept; 50 | 51 | /// Set the state of the event to 'set'. 52 | /// 53 | /// If there are pending coroutines awaiting the event then all 54 | /// pending coroutines are resumed within this call. 55 | /// Any coroutines that subsequently await the event will continue 56 | /// without suspending. 57 | /// 58 | /// This operation is a no-op if the event was already 'set'. 59 | void set() noexcept; 60 | 61 | /// Set the state of the event to 'not-set'. 62 | /// 63 | /// Any coroutines that subsequently await the event will suspend 64 | /// until some thread calls 'set()'. 65 | /// 66 | /// This is a no-op if the state was already 'not set'. 67 | void reset() noexcept; 68 | 69 | private: 70 | 71 | friend class async_manual_reset_event_operation; 72 | 73 | // This variable has 3 states: 74 | // - this - The state is 'set'. 75 | // - nullptr - The state is 'not set' with no waiters. 76 | // - other - The state is 'not set'. 77 | // Points to an 'async_manual_reset_event_operation' that is 78 | // the head of a linked-list of waiters. 79 | mutable std::atomic m_state; 80 | 81 | }; 82 | 83 | class async_manual_reset_event_operation 84 | { 85 | public: 86 | 87 | explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept; 88 | 89 | bool await_ready() const noexcept; 90 | bool await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept; 91 | void await_resume() const noexcept {} 92 | 93 | private: 94 | 95 | friend class async_manual_reset_event; 96 | 97 | const async_manual_reset_event& m_event; 98 | async_manual_reset_event_operation* m_next; 99 | cppcoro::coroutine_handle<> m_awaiter; 100 | 101 | }; 102 | } 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /include/cppcoro/async_scope.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_ASYNC_SCOPE_HPP_INCLUDED 6 | #define CPPCORO_ASYNC_SCOPE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace cppcoro 16 | { 17 | class async_scope 18 | { 19 | public: 20 | 21 | async_scope() noexcept 22 | : m_count(1u) 23 | {} 24 | 25 | ~async_scope() 26 | { 27 | // scope must be co_awaited before it destructs. 28 | assert(m_continuation); 29 | } 30 | 31 | template 32 | void spawn(AWAITABLE&& awaitable) 33 | { 34 | [](async_scope* scope, std::decay_t awaitable) -> oneway_task 35 | { 36 | scope->on_work_started(); 37 | auto decrementOnCompletion = on_scope_exit([scope] { scope->on_work_finished(); }); 38 | co_await std::move(awaitable); 39 | }(this, std::forward(awaitable)); 40 | } 41 | 42 | [[nodiscard]] auto join() noexcept 43 | { 44 | class awaiter 45 | { 46 | async_scope* m_scope; 47 | public: 48 | awaiter(async_scope* scope) noexcept : m_scope(scope) {} 49 | 50 | bool await_ready() noexcept 51 | { 52 | return m_scope->m_count.load(std::memory_order_acquire) == 0; 53 | } 54 | 55 | bool await_suspend(cppcoro::coroutine_handle<> continuation) noexcept 56 | { 57 | m_scope->m_continuation = continuation; 58 | return m_scope->m_count.fetch_sub(1u, std::memory_order_acq_rel) > 1u; 59 | } 60 | 61 | void await_resume() noexcept 62 | {} 63 | }; 64 | 65 | return awaiter{ this }; 66 | } 67 | 68 | private: 69 | 70 | void on_work_finished() noexcept 71 | { 72 | if (m_count.fetch_sub(1u, std::memory_order_acq_rel) == 1) 73 | { 74 | m_continuation.resume(); 75 | } 76 | } 77 | 78 | void on_work_started() noexcept 79 | { 80 | assert(m_count.load(std::memory_order_relaxed) != 0); 81 | m_count.fetch_add(1, std::memory_order_relaxed); 82 | } 83 | 84 | struct oneway_task 85 | { 86 | struct promise_type 87 | { 88 | cppcoro::suspend_never initial_suspend() { return {}; } 89 | cppcoro::suspend_never final_suspend() { return {}; } 90 | void unhandled_exception() { std::terminate(); } 91 | oneway_task get_return_object() { return {}; } 92 | void return_void() {} 93 | }; 94 | }; 95 | 96 | std::atomic m_count; 97 | cppcoro::coroutine_handle<> m_continuation; 98 | 99 | }; 100 | } 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /include/cppcoro/awaitable_traits.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED 6 | #define CPPCORO_AWAITABLE_TRAITS_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | template 15 | struct awaitable_traits 16 | {}; 17 | 18 | template 19 | struct awaitable_traits()))>> 20 | { 21 | using awaiter_t = decltype(cppcoro::detail::get_awaiter(std::declval())); 22 | 23 | using await_result_t = decltype(std::declval().await_resume()); 24 | }; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/cppcoro/broken_promise.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_BROKEN_PROMISE_HPP_INCLUDED 6 | #define CPPCORO_BROKEN_PROMISE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | /// \brief 13 | /// Exception thrown when you attempt to retrieve the result of 14 | /// a task that has been detached from its promise/coroutine. 15 | class broken_promise : public std::logic_error 16 | { 17 | public: 18 | broken_promise() 19 | : std::logic_error("broken promise") 20 | {} 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/cppcoro/cancellation_registration.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED 6 | #define CPPCORO_CANCELLATION_REGISTRATION_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace cppcoro 17 | { 18 | namespace detail 19 | { 20 | class cancellation_state; 21 | struct cancellation_registration_list_chunk; 22 | struct cancellation_registration_state; 23 | } 24 | 25 | class cancellation_registration 26 | { 27 | public: 28 | 29 | /// Registers the callback to be executed when cancellation is requested 30 | /// on the cancellation_token. 31 | /// 32 | /// The callback will be executed if cancellation is requested for the 33 | /// specified cancellation token. If cancellation has already been requested 34 | /// then the callback will be executed immediately, before the constructor 35 | /// returns. If cancellation has not yet been requested then the callback 36 | /// will be executed on the first thread to request cancellation inside 37 | /// the call to cancellation_source::request_cancellation(). 38 | /// 39 | /// \param token 40 | /// The cancellation token to register the callback with. 41 | /// 42 | /// \param callback 43 | /// The callback to be executed when cancellation is requested on the 44 | /// the cancellation_token. Note that callback must not throw an exception 45 | /// if called when cancellation is requested otherwise std::terminate() 46 | /// will be called. 47 | /// 48 | /// \throw std::bad_alloc 49 | /// If registration failed due to insufficient memory available. 50 | template< 51 | typename FUNC, 52 | typename = std::enable_if_t, FUNC&&>>> 53 | cancellation_registration(cancellation_token token, FUNC&& callback) 54 | : m_callback(std::forward(callback)) 55 | { 56 | register_callback(std::move(token)); 57 | } 58 | 59 | cancellation_registration(const cancellation_registration& other) = delete; 60 | cancellation_registration& operator=(const cancellation_registration& other) = delete; 61 | 62 | /// Deregisters the callback. 63 | /// 64 | /// After the destructor returns it is guaranteed that the callback 65 | /// will not be subsequently called during a call to request_cancellation() 66 | /// on the cancellation_source. 67 | /// 68 | /// This may block if cancellation has been requested on another thread 69 | /// is it will need to wait until this callback has finished executing 70 | /// before the callback can be destroyed. 71 | ~cancellation_registration(); 72 | 73 | private: 74 | 75 | friend class detail::cancellation_state; 76 | friend struct detail::cancellation_registration_state; 77 | 78 | void register_callback(cancellation_token&& token); 79 | 80 | detail::cancellation_state* m_state; 81 | std::function m_callback; 82 | detail::cancellation_registration_list_chunk* m_chunk; 83 | std::uint32_t m_entryIndex; 84 | }; 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /include/cppcoro/cancellation_source.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED 6 | #define CPPCORO_CANCELLATION_SOURCE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | class cancellation_token; 11 | 12 | namespace detail 13 | { 14 | class cancellation_state; 15 | } 16 | 17 | class cancellation_source 18 | { 19 | public: 20 | 21 | /// Construct to a new cancellation source. 22 | cancellation_source(); 23 | 24 | /// Create a new reference to the same underlying cancellation 25 | /// source as \p other. 26 | cancellation_source(const cancellation_source& other) noexcept; 27 | 28 | cancellation_source(cancellation_source&& other) noexcept; 29 | 30 | ~cancellation_source(); 31 | 32 | cancellation_source& operator=(const cancellation_source& other) noexcept; 33 | 34 | cancellation_source& operator=(cancellation_source&& other) noexcept; 35 | 36 | /// Query if this cancellation source can be cancelled. 37 | /// 38 | /// A cancellation source object will not be cancellable if it has 39 | /// previously been moved into another cancellation_source instance 40 | /// or was copied from a cancellation_source that was not cancellable. 41 | bool can_be_cancelled() const noexcept; 42 | 43 | /// Obtain a cancellation token that can be used to query if 44 | /// cancellation has been requested on this source. 45 | /// 46 | /// The cancellation token can be passed into functions that you 47 | /// may want to later be able to request cancellation. 48 | cancellation_token token() const noexcept; 49 | 50 | /// Request cancellation of operations that were passed an associated 51 | /// cancellation token. 52 | /// 53 | /// Any cancellation callback registered via a cancellation_registration 54 | /// object will be called inside this function by the first thread to 55 | /// call this method. 56 | /// 57 | /// This operation is a no-op if can_be_cancelled() returns false. 58 | void request_cancellation(); 59 | 60 | /// Query if some thread has called 'request_cancellation()' on this 61 | /// cancellation_source. 62 | bool is_cancellation_requested() const noexcept; 63 | 64 | private: 65 | 66 | detail::cancellation_state* m_state; 67 | 68 | }; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /include/cppcoro/cancellation_token.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED 6 | #define CPPCORO_CANCELLATION_TOKEN_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | class cancellation_source; 11 | class cancellation_registration; 12 | 13 | namespace detail 14 | { 15 | class cancellation_state; 16 | } 17 | 18 | class cancellation_token 19 | { 20 | public: 21 | 22 | /// Construct to a cancellation token that can't be cancelled. 23 | cancellation_token() noexcept; 24 | 25 | /// Copy another cancellation token. 26 | /// 27 | /// New token will refer to the same underlying state. 28 | cancellation_token(const cancellation_token& other) noexcept; 29 | 30 | cancellation_token(cancellation_token&& other) noexcept; 31 | 32 | ~cancellation_token(); 33 | 34 | cancellation_token& operator=(const cancellation_token& other) noexcept; 35 | 36 | cancellation_token& operator=(cancellation_token&& other) noexcept; 37 | 38 | void swap(cancellation_token& other) noexcept; 39 | 40 | /// Query if it is possible that this operation will be cancelled 41 | /// or not. 42 | /// 43 | /// Cancellable operations may be able to take more efficient code-paths 44 | /// if they don't need to handle cancellation requests. 45 | bool can_be_cancelled() const noexcept; 46 | 47 | /// Query if some thread has requested cancellation on an associated 48 | /// cancellation_source object. 49 | bool is_cancellation_requested() const noexcept; 50 | 51 | /// Throws cppcoro::operation_cancelled exception if cancellation 52 | /// has been requested for the associated operation. 53 | void throw_if_cancellation_requested() const; 54 | 55 | private: 56 | 57 | friend class cancellation_source; 58 | friend class cancellation_registration; 59 | 60 | cancellation_token(detail::cancellation_state* state) noexcept; 61 | 62 | detail::cancellation_state* m_state; 63 | 64 | }; 65 | 66 | inline void swap(cancellation_token& a, cancellation_token& b) noexcept 67 | { 68 | a.swap(b); 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /include/cppcoro/coroutine.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPCORO_COROUTINE_HPP_INCLUDED 2 | #define CPPCORO_COROUTINE_HPP_INCLUDED 3 | 4 | #include 5 | 6 | #ifdef CPPCORO_COROHEADER_FOUND_AND_USABLE 7 | 8 | #include 9 | 10 | namespace cppcoro { 11 | using std::coroutine_handle; 12 | using std::suspend_always; 13 | using std::noop_coroutine; 14 | using std::suspend_never; 15 | } 16 | 17 | #elif __has_include() 18 | 19 | #include 20 | 21 | namespace cppcoro { 22 | using std::experimental::coroutine_handle; 23 | using std::experimental::suspend_always; 24 | using std::experimental::suspend_never; 25 | 26 | #if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 27 | using std::experimental::noop_coroutine; 28 | #endif 29 | } 30 | 31 | #else 32 | #error Cppcoro requires a C++20 compiler with coroutine support 33 | #endif 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /include/cppcoro/detail/any.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_ANY_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_ANY_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | namespace detail 11 | { 12 | // Helper type that can be cast-to from any type. 13 | struct any 14 | { 15 | template 16 | any(T&&) noexcept 17 | {} 18 | }; 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/cppcoro/detail/get_awaiter.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_GET_AWAITER_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | namespace cppcoro 12 | { 13 | namespace detail 14 | { 15 | template 16 | auto get_awaiter_impl(T&& value, int) 17 | noexcept(noexcept(static_cast(value).operator co_await())) 18 | -> decltype(static_cast(value).operator co_await()) 19 | { 20 | return static_cast(value).operator co_await(); 21 | } 22 | 23 | template 24 | auto get_awaiter_impl(T&& value, long) 25 | noexcept(noexcept(operator co_await(static_cast(value)))) 26 | -> decltype(operator co_await(static_cast(value))) 27 | { 28 | return operator co_await(static_cast(value)); 29 | } 30 | 31 | template< 32 | typename T, 33 | std::enable_if_t::value, int> = 0> 34 | T&& get_awaiter_impl(T&& value, cppcoro::detail::any) noexcept 35 | { 36 | return static_cast(value); 37 | } 38 | 39 | template 40 | auto get_awaiter(T&& value) 41 | noexcept(noexcept(detail::get_awaiter_impl(static_cast(value), 123))) 42 | -> decltype(detail::get_awaiter_impl(static_cast(value), 123)) 43 | { 44 | return detail::get_awaiter_impl(static_cast(value), 123); 45 | } 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/cppcoro/detail/is_awaiter.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_IS_AWAITER_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | namespace cppcoro 12 | { 13 | namespace detail 14 | { 15 | template 16 | struct is_coroutine_handle 17 | : std::false_type 18 | {}; 19 | 20 | template 21 | struct is_coroutine_handle> 22 | : std::true_type 23 | {}; 24 | 25 | // NOTE: We're accepting a return value of coroutine_handle

here 26 | // which is an extension supported by Clang which is not yet part of 27 | // the C++ coroutines TS. 28 | template 29 | struct is_valid_await_suspend_return_value : std::disjunction< 30 | std::is_void, 31 | std::is_same, 32 | is_coroutine_handle> 33 | {}; 34 | 35 | template> 36 | struct is_awaiter : std::false_type {}; 37 | 38 | // NOTE: We're testing whether await_suspend() will be callable using an 39 | // arbitrary coroutine_handle here by checking if it supports being passed 40 | // a coroutine_handle. This may result in a false-result for some 41 | // types which are only awaitable within a certain context. 42 | template 43 | struct is_awaiter().await_ready()), 45 | decltype(std::declval().await_suspend(std::declval>())), 46 | decltype(std::declval().await_resume())>> : 47 | std::conjunction< 48 | std::is_constructible().await_ready())>, 49 | detail::is_valid_await_suspend_return_value< 50 | decltype(std::declval().await_suspend(std::declval>()))>> 51 | {}; 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /include/cppcoro/detail/lightweight_manual_reset_event.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_LIGHTWEIGHT_MANUAL_RESET_EVENT_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #if CPPCORO_OS_LINUX || (CPPCORO_OS_WINNT >= 0x0602) 11 | # include 12 | # include 13 | #elif CPPCORO_OS_WINNT 14 | # include 15 | #else 16 | # include 17 | # include 18 | #endif 19 | 20 | namespace cppcoro 21 | { 22 | namespace detail 23 | { 24 | class lightweight_manual_reset_event 25 | { 26 | public: 27 | 28 | lightweight_manual_reset_event(bool initiallySet = false); 29 | 30 | ~lightweight_manual_reset_event(); 31 | 32 | void set() noexcept; 33 | 34 | void reset() noexcept; 35 | 36 | void wait() noexcept; 37 | 38 | private: 39 | 40 | #if CPPCORO_OS_LINUX 41 | std::atomic m_value; 42 | #elif CPPCORO_OS_WINNT >= 0x0602 43 | // Windows 8 or newer we can use WaitOnAddress() 44 | std::atomic m_value; 45 | #elif CPPCORO_OS_WINNT 46 | // Before Windows 8 we need to use a WIN32 manual reset event. 47 | cppcoro::detail::win32::handle_t m_eventHandle; 48 | #else 49 | // For other platforms that don't have a native futex 50 | // or manual reset event we can just use a std::mutex 51 | // and std::condition_variable to perform the wait. 52 | // Not so lightweight, but should be portable to all platforms. 53 | std::mutex m_mutex; 54 | std::condition_variable m_cv; 55 | bool m_isSet; 56 | #endif 57 | }; 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/cppcoro/detail/linux.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_LINUX_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_LINUX_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace cppcoro 30 | { 31 | namespace detail 32 | { 33 | namespace lnx 34 | { 35 | using fd_t = int; 36 | 37 | class safe_fd 38 | { 39 | public: 40 | safe_fd() 41 | : m_fd(-1) 42 | { 43 | } 44 | 45 | explicit safe_fd(fd_t fd) 46 | : m_fd(fd) 47 | { 48 | } 49 | 50 | safe_fd(const safe_fd& other) = delete; 51 | 52 | safe_fd(safe_fd&& other) noexcept 53 | : m_fd(other.m_fd) 54 | { 55 | other.m_fd = -1; 56 | } 57 | 58 | ~safe_fd() { close(); } 59 | 60 | safe_fd& operator=(safe_fd fd) noexcept 61 | { 62 | swap(fd); 63 | return *this; 64 | } 65 | 66 | constexpr fd_t fd() const { return m_fd; } 67 | constexpr fd_t handle() const { return m_fd; } 68 | 69 | /// Calls close() and sets the fd to -1. 70 | void close() noexcept; 71 | 72 | void swap(safe_fd& other) noexcept { std::swap(m_fd, other.m_fd); } 73 | 74 | /// Test operator 75 | explicit operator bool() noexcept { 76 | return m_fd >= 0; 77 | } 78 | 79 | /// Dereference operator 80 | int operator*() const noexcept { 81 | return fd(); 82 | } 83 | 84 | bool operator==(const safe_fd& other) const { return m_fd == other.m_fd; } 85 | 86 | bool operator!=(const safe_fd& other) const { return m_fd != other.m_fd; } 87 | 88 | bool operator==(fd_t fd) const { return m_fd == fd; } 89 | 90 | bool operator!=(fd_t fd) const { return m_fd != fd; } 91 | 92 | private: 93 | fd_t m_fd; 94 | }; 95 | 96 | struct io_message 97 | { 98 | std::function resume; 99 | int result = -1; 100 | 101 | io_message& operator=(coroutine_handle<> coroutine_handle) noexcept { 102 | resume = [coroutine_handle]() mutable { 103 | coroutine_handle.resume(); 104 | }; 105 | return *this; 106 | } 107 | io_message& operator=(std::function function) noexcept { 108 | resume = std::move(function); 109 | return *this; 110 | } 111 | }; 112 | 113 | } // namespace linux 114 | 115 | using safe_handle = lnx::safe_fd; 116 | using dword_t = int; 117 | struct sock_buf { 118 | sock_buf(void *buf, size_t sz) : buffer(buf), size(sz) {} 119 | void * buffer; 120 | size_t size; 121 | }; 122 | using handle_t = lnx::fd_t; 123 | } // namespace detail 124 | } // namespace cppcoro 125 | 126 | #endif // CPPCORO_DETAIL_LINUX_HPP_INCLUDED 127 | -------------------------------------------------------------------------------- /include/cppcoro/detail/linux_uring_queue.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_LINUX_URING_QUEUE_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_LINUX_URING_QUEUE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace cppcoro::detail::lnx 18 | { 19 | class uring_queue; 20 | 21 | /// RAII IO transaction 22 | class [[nodiscard]] io_transaction final { 23 | public: 24 | io_transaction(uring_queue &queue, io_message& message) noexcept; 25 | bool commit() noexcept; 26 | 27 | [[nodiscard]] io_transaction &read(int fd, void *buffer, size_t size, size_t offset) noexcept; 28 | [[nodiscard]] io_transaction &write(int fd, const void * buffer, size_t size, size_t offset) noexcept; 29 | 30 | [[nodiscard]] io_transaction &readv(int fd, iovec* vec, size_t count, size_t offset) noexcept; 31 | [[nodiscard]] io_transaction &writev(int fd, iovec* vec, size_t count, size_t offset) noexcept; 32 | 33 | [[nodiscard]] io_transaction &recv(int fd, void * buffer, size_t size, int flags = 0) noexcept; 34 | [[nodiscard]] io_transaction &send(int fd, const void *buffer, size_t size, int flags = 0) noexcept; 35 | 36 | [[nodiscard]] io_transaction &recvmsg(int fd, msghdr *msg, int flags = 0) noexcept; 37 | [[nodiscard]] io_transaction &sendmsg(int fd, msghdr *msg, int flags = 0) noexcept; 38 | 39 | [[nodiscard]] io_transaction &connect(int fd, const void* to, size_t to_size) noexcept; 40 | [[nodiscard]] io_transaction &close(int fd) noexcept; 41 | 42 | [[nodiscard]] io_transaction &accept(int fd, const void* to, socklen_t* to_size, int flags = 0) noexcept; 43 | 44 | [[nodiscard]] io_transaction &timeout(__kernel_timespec *ts, bool absolute = false) noexcept; 45 | [[nodiscard]] io_transaction &timeout_remove(int flags = 0) noexcept; 46 | 47 | [[nodiscard]] io_transaction &nop() noexcept; 48 | 49 | [[nodiscard]] io_transaction &cancel(int flags = 0) noexcept; 50 | 51 | private: 52 | uring_queue &m_queue; 53 | io_message& m_message; 54 | std::scoped_lock m_sqeLock; 55 | io_uring_sqe *m_sqe; 56 | }; 57 | 58 | class uring_queue 59 | { 60 | public: 61 | explicit uring_queue(size_t queue_length = 32, uint32_t flags = 0); 62 | ~uring_queue() noexcept; 63 | uring_queue(uring_queue&&) = delete; 64 | uring_queue& operator=(uring_queue&&) = delete; 65 | uring_queue(uring_queue const&) = delete; 66 | uring_queue& operator=(uring_queue const&) = delete; 67 | 68 | io_transaction transaction(io_message &message) noexcept; 69 | 70 | bool dequeue(io_message*& message, bool wait); 71 | 72 | private: 73 | friend class io_transaction; 74 | 75 | io_uring_sqe* get_sqe() noexcept; 76 | int submit() noexcept; 77 | 78 | std::mutex m_sqeMux; 79 | std::mutex m_outMux; 80 | io_uring ring_{}; 81 | }; 82 | using io_queue = uring_queue; 83 | } // namespace cppcoro::detail::lnx 84 | 85 | #endif // CPPCORO_DETAIL_LINUX_URING_QUEUE_HPP_INCLUDED 86 | -------------------------------------------------------------------------------- /include/cppcoro/detail/remove_rvalue_reference.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_REMOVE_RVALUE_REFERENCE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | namespace detail 11 | { 12 | template 13 | struct remove_rvalue_reference 14 | { 15 | using type = T; 16 | }; 17 | 18 | template 19 | struct remove_rvalue_reference 20 | { 21 | using type = T; 22 | }; 23 | 24 | template 25 | using remove_rvalue_reference_t = typename remove_rvalue_reference::type; 26 | } 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/cppcoro/detail/unwrap_reference.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_UNWRAP_REFERENCE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | namespace detail 13 | { 14 | template 15 | struct unwrap_reference 16 | { 17 | using type = T; 18 | }; 19 | 20 | template 21 | struct unwrap_reference> 22 | { 23 | using type = T; 24 | }; 25 | 26 | template 27 | using unwrap_reference_t = typename unwrap_reference::type; 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/cppcoro/detail/void_value.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_VOID_VALUE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | namespace detail 11 | { 12 | struct void_value {}; 13 | } 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/cppcoro/detail/when_all_counter.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED 6 | #define CPPCORO_DETAIL_WHEN_ALL_COUNTER_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | namespace detail 15 | { 16 | class when_all_counter 17 | { 18 | public: 19 | 20 | when_all_counter(std::size_t count) noexcept 21 | : m_count(count + 1) 22 | , m_awaitingCoroutine(nullptr) 23 | {} 24 | 25 | bool is_ready() const noexcept 26 | { 27 | // We consider this complete if we're asking whether it's ready 28 | // after a coroutine has already been registered. 29 | return static_cast(m_awaitingCoroutine); 30 | } 31 | 32 | bool try_await(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept 33 | { 34 | m_awaitingCoroutine = awaitingCoroutine; 35 | return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1; 36 | } 37 | 38 | void notify_awaitable_completed() noexcept 39 | { 40 | if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1) 41 | { 42 | m_awaitingCoroutine.resume(); 43 | } 44 | } 45 | 46 | protected: 47 | 48 | std::atomic m_count; 49 | cppcoro::coroutine_handle<> m_awaitingCoroutine; 50 | 51 | }; 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /include/cppcoro/file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_HPP_INCLUDED 6 | #define CPPCORO_FILE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #if CPPCORO_OS_WINNT 15 | # include 16 | #else 17 | # include 18 | #endif 19 | 20 | #include 21 | 22 | namespace cppcoro 23 | { 24 | class io_service; 25 | 26 | class file 27 | { 28 | public: 29 | 30 | file(file&& other) noexcept = default; 31 | 32 | virtual ~file(); 33 | 34 | /// Get the size of the file in bytes. 35 | std::uint64_t size() const; 36 | 37 | protected: 38 | 39 | explicit file(detail::safe_handle&& fileHandle) noexcept; 40 | 41 | static detail::safe_handle open( 42 | detail::dword_t fileAccess, 43 | io_service& ioService, 44 | const filesystem::path& path, 45 | file_open_mode openMode, 46 | file_share_mode shareMode, 47 | file_buffering_mode bufferingMode); 48 | 49 | detail::safe_handle m_fileHandle; 50 | #if CPPCORO_OS_LINUX 51 | io_service *m_ioService; 52 | #endif 53 | }; 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/cppcoro/file_buffering_mode.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED 6 | #define CPPCORO_FILE_BUFFERING_MODE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | enum class file_buffering_mode 11 | { 12 | default_ = 0, 13 | sequential = 1, 14 | random_access = 2, 15 | unbuffered = 4, 16 | write_through = 8, 17 | temporary = 16 18 | }; 19 | 20 | constexpr file_buffering_mode operator&(file_buffering_mode a, file_buffering_mode b) 21 | { 22 | return static_cast( 23 | static_cast(a) & static_cast(b)); 24 | } 25 | 26 | constexpr file_buffering_mode operator|(file_buffering_mode a, file_buffering_mode b) 27 | { 28 | return static_cast(static_cast(a) | static_cast(b)); 29 | } 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/cppcoro/file_open_mode.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED 6 | #define CPPCORO_FILE_OPEN_MODE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | enum class file_open_mode 11 | { 12 | /// Open an existing file. 13 | /// 14 | /// If file does not already exist when opening the file then raises 15 | /// an exception. 16 | open_existing, 17 | 18 | /// Create a new file, overwriting an existing file if one exists. 19 | /// 20 | /// If a file exists at the path then it is overwitten with a new file. 21 | /// If no file exists at the path then a new one is created. 22 | create_always, 23 | 24 | /// Create a new file. 25 | /// 26 | /// If the file already exists then raises an exception. 27 | create_new, 28 | 29 | /// Open the existing file if one exists, otherwise create a new empty 30 | /// file. 31 | create_or_open, 32 | 33 | /// Open the existing file, truncating the file size to zero. 34 | /// 35 | /// If the file does not exist then raises an exception. 36 | truncate_existing 37 | }; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/cppcoro/file_read_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_FILE_READ_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #if CPPCORO_OS_WINNT 17 | # include 18 | # include 19 | #elif CPPCORO_OS_LINUX 20 | # include 21 | # include 22 | #endif 23 | 24 | namespace cppcoro 25 | { 26 | class file_read_operation_impl 27 | { 28 | public: 29 | 30 | file_read_operation_impl( 31 | detail::handle_t fileHandle, void* buffer, std::size_t byteCount) noexcept 32 | : m_fileHandle(fileHandle) 33 | , m_buffer(buffer) 34 | , m_byteCount(byteCount) 35 | {} 36 | 37 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 38 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 39 | 40 | private: 41 | 42 | detail::handle_t m_fileHandle; 43 | void* m_buffer; 44 | std::size_t m_byteCount; 45 | 46 | }; 47 | 48 | class file_read_operation : public cppcoro::detail::io_operation 49 | { 50 | public: 51 | 52 | file_read_operation( 53 | #if CPPCORO_OS_LINUX 54 | io_service& ioService, 55 | #endif 56 | detail::handle_t fileHandle, 57 | std::uint64_t fileOffset, 58 | void* buffer, 59 | std::size_t byteCount) noexcept 60 | : cppcoro::detail::io_operation( 61 | #if CPPCORO_OS_LINUX 62 | ioService.io_queue(), 63 | #endif 64 | fileOffset) 65 | , m_impl(fileHandle, buffer, byteCount) 66 | {} 67 | 68 | private: 69 | 70 | friend cppcoro::detail::io_operation; 71 | 72 | bool try_start() noexcept { return m_impl.try_start(*this); } 73 | 74 | file_read_operation_impl m_impl; 75 | 76 | }; 77 | 78 | class file_read_operation_cancellable 79 | : public cppcoro::detail::io_operation_cancellable 80 | { 81 | public: 82 | 83 | file_read_operation_cancellable( 84 | #if CPPCORO_OS_LINUX 85 | io_service& ioService, 86 | #endif 87 | detail::handle_t fileHandle, 88 | std::uint64_t fileOffset, 89 | void* buffer, 90 | std::size_t byteCount, 91 | cancellation_token&& cancellationToken) noexcept 92 | : cppcoro::detail::io_operation_cancellable( 93 | #if CPPCORO_OS_LINUX 94 | ioService.io_queue(), 95 | #endif 96 | fileOffset, 97 | std::move(cancellationToken)) 98 | , m_impl(fileHandle, buffer, byteCount) 99 | {} 100 | 101 | private: 102 | 103 | friend cppcoro::detail::io_operation_cancellable; 104 | 105 | bool try_start() noexcept { return m_impl.try_start(*this); } 106 | void cancel() noexcept { m_impl.cancel(*this); } 107 | 108 | file_read_operation_impl m_impl; 109 | 110 | }; 111 | 112 | } // namespace cppcoro 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /include/cppcoro/file_share_mode.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED 6 | #define CPPCORO_FILE_SHARE_MODE_HPP_INCLUDED 7 | 8 | namespace cppcoro 9 | { 10 | enum class file_share_mode 11 | { 12 | /// Don't allow any other processes to open the file concurrently. 13 | none = 0, 14 | 15 | /// Allow other processes to open the file in read-only mode 16 | /// concurrently with this process opening the file. 17 | read = 1, 18 | 19 | /// Allow other processes to open the file in write-only mode 20 | /// concurrently with this process opening the file. 21 | write = 2, 22 | 23 | /// Allow other processes to open the file in read and/or write mode 24 | /// concurrently with this process opening the file. 25 | read_write = read | write, 26 | 27 | /// Allow other processes to delete the file while this process 28 | /// has the file open. 29 | delete_ = 4 30 | }; 31 | 32 | constexpr file_share_mode operator|(file_share_mode a, file_share_mode b) 33 | { 34 | return static_cast( 35 | static_cast(a) | static_cast(b)); 36 | } 37 | 38 | constexpr file_share_mode operator&(file_share_mode a, file_share_mode b) 39 | { 40 | return static_cast( 41 | static_cast(a) & static_cast(b)); 42 | } 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /include/cppcoro/file_write_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_FILE_WRITE_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #if CPPCORO_OS_WINNT 17 | # include 18 | # include 19 | 20 | #elif CPPCORO_OS_LINUX 21 | 22 | #include 23 | #include 24 | 25 | #endif 26 | 27 | namespace cppcoro 28 | { 29 | class file_write_operation_impl 30 | { 31 | public: 32 | 33 | file_write_operation_impl( 34 | detail::handle_t fileHandle, const void* buffer, std::size_t byteCount) noexcept 35 | : m_fileHandle(fileHandle) 36 | , m_buffer(buffer) 37 | , m_byteCount(byteCount) 38 | {} 39 | 40 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 41 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 42 | 43 | private: 44 | detail::handle_t m_fileHandle; 45 | const void* m_buffer; 46 | std::size_t m_byteCount; 47 | 48 | }; 49 | 50 | class file_write_operation 51 | : public cppcoro::detail::io_operation 52 | { 53 | public: 54 | 55 | file_write_operation( 56 | #if CPPCORO_OS_LINUX 57 | io_service& ioService, 58 | #endif 59 | detail::handle_t fileHandle, 60 | std::uint64_t fileOffset, 61 | const void* buffer, 62 | std::size_t byteCount) noexcept 63 | : cppcoro::detail::io_operation( 64 | #if CPPCORO_OS_LINUX 65 | ioService.io_queue(), 66 | #endif 67 | fileOffset) 68 | , m_impl(fileHandle, buffer, byteCount) 69 | {} 70 | 71 | private: 72 | 73 | friend cppcoro::detail::io_operation; 74 | 75 | bool try_start() noexcept { return m_impl.try_start(*this); } 76 | 77 | file_write_operation_impl m_impl; 78 | 79 | }; 80 | 81 | class file_write_operation_cancellable 82 | : public cppcoro::detail::io_operation_cancellable 83 | { 84 | public: 85 | 86 | file_write_operation_cancellable( 87 | #if CPPCORO_OS_LINUX 88 | io_service& ioService, 89 | #endif 90 | detail::handle_t fileHandle, 91 | std::uint64_t fileOffset, 92 | const void* buffer, 93 | std::size_t byteCount, 94 | cancellation_token&& ct) noexcept 95 | : cppcoro::detail::io_operation_cancellable( 96 | #if CPPCORO_OS_LINUX 97 | ioService.io_queue(), 98 | #endif 99 | fileOffset, 100 | std::move(ct)) 101 | , m_impl(fileHandle, buffer, byteCount) 102 | {} 103 | 104 | private: 105 | 106 | friend cppcoro::detail::io_operation_cancellable; 107 | 108 | bool try_start() noexcept { return m_impl.try_start(*this); } 109 | void cancel() noexcept { m_impl.cancel(*this); } 110 | 111 | file_write_operation_impl m_impl; 112 | 113 | }; 114 | } // namespace cppcoro 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/cppcoro/filesystem.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPCORO_FILESYSTEM_HPP_INCLUDED 2 | #define CPPCORO_FILESYSTEM_HPP_INCLUDED 3 | 4 | #if __has_include() 5 | 6 | #include 7 | 8 | namespace cppcoro { 9 | namespace filesystem = std::filesystem; 10 | } 11 | 12 | #elif __has_include() 13 | 14 | #include 15 | 16 | namespace cppcoro { 17 | namespace filesystem = std::experimental::filesystem; 18 | } 19 | 20 | #else 21 | #error Cppcoro requires a C++20 compiler with filesystem support 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/cppcoro/inline_scheduler.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED 6 | #define CPPCORO_INLINE_SCHEDULER_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | class inline_scheduler 13 | { 14 | public: 15 | 16 | inline_scheduler() noexcept = default; 17 | 18 | cppcoro::suspend_never schedule() const noexcept 19 | { 20 | return {}; 21 | } 22 | }; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /include/cppcoro/is_awaitable.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_IS_AWAITABLE_HPP_INCLUDED 6 | #define CPPCORO_IS_AWAITABLE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | template> 15 | struct is_awaitable : std::false_type {}; 16 | 17 | template 18 | struct is_awaitable()))>> 19 | : std::true_type 20 | {}; 21 | 22 | template 23 | constexpr bool is_awaitable_v = is_awaitable::value; 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/cppcoro/net/ipv4_address.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED 6 | #define CPPCORO_NET_IPV4_ADDRESS_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace cppcoro::net 14 | { 15 | class ipv4_address 16 | { 17 | using bytes_t = std::uint8_t[4]; 18 | 19 | public: 20 | 21 | constexpr ipv4_address() 22 | : m_bytes{ 0, 0, 0, 0 } 23 | {} 24 | 25 | explicit constexpr ipv4_address(std::uint32_t integer) 26 | : m_bytes{ 27 | static_cast(integer >> 24), 28 | static_cast(integer >> 16), 29 | static_cast(integer >> 8), 30 | static_cast(integer) } 31 | {} 32 | 33 | explicit constexpr ipv4_address(const std::uint8_t(&bytes)[4]) 34 | : m_bytes{ bytes[0], bytes[1], bytes[2], bytes[3] } 35 | {} 36 | 37 | explicit constexpr ipv4_address( 38 | std::uint8_t b0, 39 | std::uint8_t b1, 40 | std::uint8_t b2, 41 | std::uint8_t b3) 42 | : m_bytes{ b0, b1, b2, b3 } 43 | {} 44 | 45 | constexpr const bytes_t& bytes() const { return m_bytes; } 46 | 47 | constexpr std::uint32_t to_integer() const 48 | { 49 | return 50 | std::uint32_t(m_bytes[0]) << 24 | 51 | std::uint32_t(m_bytes[1]) << 16 | 52 | std::uint32_t(m_bytes[2]) << 8 | 53 | std::uint32_t(m_bytes[3]); 54 | } 55 | 56 | static constexpr ipv4_address loopback() 57 | { 58 | return ipv4_address(127, 0, 0, 1); 59 | } 60 | 61 | constexpr bool is_loopback() const 62 | { 63 | return m_bytes[0] == 127; 64 | } 65 | 66 | constexpr bool is_private_network() const 67 | { 68 | return m_bytes[0] == 10 || 69 | (m_bytes[0] == 172 && (m_bytes[1] & 0xF0) == 0x10) || 70 | (m_bytes[0] == 192 && m_bytes[2] == 168); 71 | } 72 | 73 | constexpr bool operator==(ipv4_address other) const 74 | { 75 | return 76 | m_bytes[0] == other.m_bytes[0] && 77 | m_bytes[1] == other.m_bytes[1] && 78 | m_bytes[2] == other.m_bytes[2] && 79 | m_bytes[3] == other.m_bytes[3]; 80 | } 81 | 82 | constexpr bool operator!=(ipv4_address other) const 83 | { 84 | return !(*this == other); 85 | } 86 | 87 | constexpr bool operator<(ipv4_address other) const 88 | { 89 | return to_integer() < other.to_integer(); 90 | } 91 | 92 | constexpr bool operator>(ipv4_address other) const 93 | { 94 | return other < *this; 95 | } 96 | 97 | constexpr bool operator<=(ipv4_address other) const 98 | { 99 | return !(other < *this); 100 | } 101 | 102 | constexpr bool operator>=(ipv4_address other) const 103 | { 104 | return !(*this < other); 105 | } 106 | 107 | /// Parse a string representation of an IP address. 108 | /// 109 | /// Parses strings of the form: 110 | /// - "num.num.num.num" where num is an integer in range [0, 255]. 111 | /// - A single integer value in range [0, 2^32). 112 | /// 113 | /// \param string 114 | /// The string to parse. 115 | /// Must be in ASCII, UTF-8 or Latin-1 encoding. 116 | /// 117 | /// \return 118 | /// The IP address if successful, otherwise std::nullopt if the string 119 | /// could not be parsed as an IPv4 address. 120 | static std::optional from_string(std::string_view string) noexcept; 121 | 122 | /// Convert the IP address to dotted decimal notation. 123 | /// 124 | /// eg. "12.67.190.23" 125 | std::string to_string() const; 126 | 127 | private: 128 | 129 | alignas(std::uint32_t) std::uint8_t m_bytes[4]; 130 | 131 | }; 132 | } 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /include/cppcoro/net/ipv4_endpoint.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED 6 | #define CPPCORO_NET_IPV4_ENDPOINT_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace cppcoro 15 | { 16 | namespace net 17 | { 18 | class ipv4_endpoint 19 | { 20 | public: 21 | 22 | // Construct to 0.0.0.0:0 23 | ipv4_endpoint() noexcept 24 | : m_address() 25 | , m_port(0) 26 | {} 27 | 28 | explicit ipv4_endpoint(ipv4_address address, std::uint16_t port = 0) noexcept 29 | : m_address(address) 30 | , m_port(port) 31 | {} 32 | 33 | const ipv4_address& address() const noexcept { return m_address; } 34 | 35 | std::uint16_t port() const noexcept { return m_port; } 36 | 37 | std::string to_string() const; 38 | 39 | static std::optional from_string(std::string_view string) noexcept; 40 | 41 | private: 42 | 43 | ipv4_address m_address; 44 | std::uint16_t m_port; 45 | 46 | }; 47 | 48 | inline bool operator==(const ipv4_endpoint& a, const ipv4_endpoint& b) 49 | { 50 | return a.address() == b.address() && 51 | a.port() == b.port(); 52 | } 53 | 54 | inline bool operator!=(const ipv4_endpoint& a, const ipv4_endpoint& b) 55 | { 56 | return !(a == b); 57 | } 58 | 59 | inline bool operator<(const ipv4_endpoint& a, const ipv4_endpoint& b) 60 | { 61 | return a.address() < b.address() || 62 | (a.address() == b.address() && a.port() < b.port()); 63 | } 64 | 65 | inline bool operator>(const ipv4_endpoint& a, const ipv4_endpoint& b) 66 | { 67 | return b < a; 68 | } 69 | 70 | inline bool operator<=(const ipv4_endpoint& a, const ipv4_endpoint& b) 71 | { 72 | return !(b < a); 73 | } 74 | 75 | inline bool operator>=(const ipv4_endpoint& a, const ipv4_endpoint& b) 76 | { 77 | return !(a < b); 78 | } 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /include/cppcoro/net/ipv6_endpoint.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED 6 | #define CPPCORO_NET_IPV6_ENDPOINT_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace cppcoro 15 | { 16 | namespace net 17 | { 18 | class ipv6_endpoint 19 | { 20 | public: 21 | 22 | // Construct to [::]:0 23 | ipv6_endpoint() noexcept 24 | : m_address() 25 | , m_port(0) 26 | {} 27 | 28 | explicit ipv6_endpoint(ipv6_address address, std::uint16_t port = 0) noexcept 29 | : m_address(address) 30 | , m_port(port) 31 | {} 32 | 33 | const ipv6_address& address() const noexcept { return m_address; } 34 | 35 | std::uint16_t port() const noexcept { return m_port; } 36 | 37 | std::string to_string() const; 38 | 39 | static std::optional from_string(std::string_view string) noexcept; 40 | 41 | private: 42 | 43 | ipv6_address m_address; 44 | std::uint16_t m_port; 45 | 46 | }; 47 | 48 | inline bool operator==(const ipv6_endpoint& a, const ipv6_endpoint& b) 49 | { 50 | return a.address() == b.address() && 51 | a.port() == b.port(); 52 | } 53 | 54 | inline bool operator!=(const ipv6_endpoint& a, const ipv6_endpoint& b) 55 | { 56 | return !(a == b); 57 | } 58 | 59 | inline bool operator<(const ipv6_endpoint& a, const ipv6_endpoint& b) 60 | { 61 | return a.address() < b.address() || 62 | (a.address() == b.address() && a.port() < b.port()); 63 | } 64 | 65 | inline bool operator>(const ipv6_endpoint& a, const ipv6_endpoint& b) 66 | { 67 | return b < a; 68 | } 69 | 70 | inline bool operator<=(const ipv6_endpoint& a, const ipv6_endpoint& b) 71 | { 72 | return !(b < a); 73 | } 74 | 75 | inline bool operator>=(const ipv6_endpoint& a, const ipv6_endpoint& b) 76 | { 77 | return !(a < b); 78 | } 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /include/cppcoro/net/socket_connect_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_NET_SOCKET_CONNECT_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if CPPCORO_OS_WINNT 13 | # include 14 | # include 15 | #elif CPPCORO_OS_LINUX 16 | # include 17 | #endif 18 | 19 | namespace cppcoro 20 | { 21 | namespace net 22 | { 23 | class socket; 24 | 25 | class socket_connect_operation_impl 26 | { 27 | public: 28 | 29 | socket_connect_operation_impl( 30 | socket& socket, 31 | const ip_endpoint& remoteEndPoint) noexcept 32 | : m_socket(socket) 33 | , m_remoteEndPoint(remoteEndPoint) 34 | {} 35 | 36 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 37 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 38 | void get_result(cppcoro::detail::io_operation_base& operation); 39 | 40 | private: 41 | 42 | socket& m_socket; 43 | ip_endpoint m_remoteEndPoint; 44 | }; 45 | 46 | class socket_connect_operation 47 | : public cppcoro::detail::io_operation 48 | { 49 | public: 50 | 51 | socket_connect_operation( 52 | #if CPPCORO_OS_LINUX 53 | detail::lnx::io_queue &ioQueue, 54 | #endif 55 | socket& socket, 56 | const ip_endpoint& remoteEndPoint) noexcept 57 | : cppcoro::detail::io_operation { 58 | #if CPPCORO_OS_LINUX 59 | ioQueue, 60 | #endif 61 | } 62 | , m_impl(socket, remoteEndPoint) 63 | {} 64 | 65 | private: 66 | 67 | friend cppcoro::detail::io_operation; 68 | 69 | bool try_start() noexcept { return m_impl.try_start(*this); } 70 | decltype(auto) get_result() { return m_impl.get_result(*this); } 71 | 72 | socket_connect_operation_impl m_impl; 73 | 74 | }; 75 | 76 | class socket_connect_operation_cancellable 77 | : public cppcoro::detail::io_operation_cancellable 78 | { 79 | public: 80 | 81 | socket_connect_operation_cancellable( 82 | #if CPPCORO_OS_LINUX 83 | detail::lnx::io_queue &ioQueue, 84 | #endif 85 | socket& socket, 86 | const ip_endpoint& remoteEndPoint, 87 | cancellation_token&& ct) noexcept 88 | : cppcoro::detail::io_operation_cancellable { 89 | #if CPPCORO_OS_LINUX 90 | ioQueue, 91 | #endif 92 | std::move(ct)} 93 | , m_impl(socket, remoteEndPoint) 94 | {} 95 | 96 | private: 97 | 98 | friend cppcoro::detail::io_operation_cancellable; 99 | 100 | bool try_start() noexcept { return m_impl.try_start(*this); } 101 | void cancel() noexcept { m_impl.cancel(*this); } 102 | void get_result() { m_impl.get_result(*this); } 103 | 104 | socket_connect_operation_impl m_impl; 105 | 106 | }; 107 | } 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /include/cppcoro/net/socket_disconnect_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_NET_SOCKET_DISCONNECT_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #if CPPCORO_OS_WINNT 12 | # include 13 | # include 14 | #elif CPPCORO_OS_LINUX 15 | # include 16 | #endif 17 | 18 | namespace cppcoro 19 | { 20 | namespace net 21 | { 22 | class socket; 23 | 24 | class socket_disconnect_operation_impl 25 | { 26 | public: 27 | 28 | socket_disconnect_operation_impl(socket& socket) noexcept 29 | : m_socket(socket) 30 | {} 31 | 32 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 33 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 34 | void get_result(cppcoro::detail::io_operation_base& operation); 35 | 36 | private: 37 | 38 | socket& m_socket; 39 | 40 | }; 41 | 42 | class socket_disconnect_operation 43 | : public cppcoro::detail::io_operation 44 | { 45 | public: 46 | socket_disconnect_operation( 47 | #if CPPCORO_OS_LINUX 48 | detail::lnx::io_queue& ioQueue, 49 | #endif 50 | socket& socket) noexcept 51 | : cppcoro::detail::io_operation { 52 | #if CPPCORO_OS_LINUX 53 | ioQueue 54 | #endif 55 | } 56 | , m_impl(socket) 57 | { 58 | } 59 | 60 | private: 61 | friend cppcoro::detail::io_operation; 62 | 63 | bool try_start() noexcept { return m_impl.try_start(*this); } 64 | void get_result() { m_impl.get_result(*this); } 65 | 66 | socket_disconnect_operation_impl m_impl; 67 | 68 | }; 69 | 70 | class socket_disconnect_operation_cancellable 71 | : public cppcoro::detail::io_operation_cancellable 72 | { 73 | public: 74 | socket_disconnect_operation_cancellable( 75 | #if CPPCORO_OS_LINUX 76 | detail::lnx::io_queue& ioQueue, 77 | #endif 78 | socket& socket, 79 | cancellation_token&& ct) noexcept 80 | : cppcoro::detail::io_operation_cancellable< 81 | socket_disconnect_operation_cancellable> { 82 | #if CPPCORO_OS_LINUX 83 | ioQueue, 84 | #endif 85 | std::move(ct)} 86 | , m_impl(socket) 87 | {} 88 | 89 | private: 90 | friend cppcoro::detail::io_operation_cancellable; 91 | 92 | bool try_start() noexcept { return m_impl.try_start(*this); } 93 | void cancel() noexcept { m_impl.cancel(*this); } 94 | void get_result() { m_impl.get_result(*this); } 95 | 96 | socket_disconnect_operation_impl m_impl; 97 | 98 | }; 99 | } // namespace net 100 | } // namespace cppcoro 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /include/cppcoro/net/socket_recv_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_NET_SOCKET_RECV_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #if CPPCORO_OS_WINNT 14 | # include 15 | # include 16 | #elif CPPCORO_OS_LINUX 17 | # include 18 | #endif 19 | 20 | namespace cppcoro::net 21 | { 22 | class socket; 23 | 24 | class socket_recv_operation_impl 25 | { 26 | public: 27 | 28 | socket_recv_operation_impl( 29 | socket& s, 30 | void* buffer, 31 | std::size_t byteCount) noexcept 32 | : m_socket(s) 33 | , m_buffer(buffer, byteCount) 34 | {} 35 | 36 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 37 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 38 | 39 | #if CPPCORO_OS_LINUX 40 | std::size_t get_result(cppcoro::detail::io_operation_base& operation); 41 | #endif 42 | 43 | private: 44 | 45 | socket& m_socket; 46 | cppcoro::detail::sock_buf m_buffer; 47 | 48 | }; 49 | 50 | class socket_recv_operation : public cppcoro::detail::io_operation 51 | { 52 | public: 53 | 54 | socket_recv_operation( 55 | #if CPPCORO_OS_LINUX 56 | detail::lnx::io_queue& ioQueue, 57 | #endif 58 | socket& s, 59 | void* buffer, 60 | std::size_t byteCount) noexcept 61 | : cppcoro::detail::io_operation { 62 | #if CPPCORO_OS_LINUX 63 | ioQueue 64 | #endif 65 | } 66 | , m_impl(s, buffer, byteCount) 67 | {} 68 | 69 | private: 70 | 71 | friend cppcoro::detail::io_operation; 72 | 73 | bool try_start() noexcept { return m_impl.try_start(*this); } 74 | #if CPPCORO_OS_LINUX 75 | std::size_t get_result() { return m_impl.get_result(*this); } 76 | #endif 77 | 78 | socket_recv_operation_impl m_impl; 79 | 80 | }; 81 | 82 | class socket_recv_operation_cancellable 83 | : public cppcoro::detail::io_operation_cancellable 84 | { 85 | public: 86 | 87 | socket_recv_operation_cancellable( 88 | #if CPPCORO_OS_LINUX 89 | detail::lnx::io_queue& ioQueue, 90 | #endif 91 | socket& s, 92 | void* buffer, 93 | std::size_t byteCount, 94 | cancellation_token&& ct) noexcept 95 | : cppcoro::detail::io_operation_cancellable { 96 | #if CPPCORO_OS_LINUX 97 | ioQueue, 98 | #endif 99 | std::move(ct)} 100 | , m_impl(s, buffer, byteCount) 101 | {} 102 | 103 | private: 104 | 105 | friend cppcoro::detail::io_operation_cancellable; 106 | 107 | bool try_start() noexcept { return m_impl.try_start(*this); } 108 | void cancel() noexcept { m_impl.cancel(*this); } 109 | 110 | socket_recv_operation_impl m_impl; 111 | 112 | }; 113 | 114 | } // namespace cppcoro::net 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/cppcoro/net/socket_send_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_NET_SOCKET_SEND_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #if CPPCORO_OS_WINNT 14 | # include 15 | # include 16 | #elif CPPCORO_OS_LINUX 17 | # include 18 | #endif 19 | 20 | namespace cppcoro::net 21 | { 22 | class socket; 23 | 24 | class socket_send_operation_impl 25 | { 26 | public: 27 | 28 | socket_send_operation_impl( 29 | socket& s, 30 | const void* buffer, 31 | std::size_t byteCount) noexcept 32 | : m_socket(s) 33 | , m_buffer(const_cast(buffer), byteCount) 34 | {} 35 | 36 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 37 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 38 | 39 | private: 40 | 41 | socket& m_socket; 42 | cppcoro::detail::sock_buf m_buffer; 43 | 44 | }; 45 | 46 | class socket_send_operation 47 | : public cppcoro::detail::io_operation 48 | { 49 | public: 50 | 51 | socket_send_operation( 52 | #if CPPCORO_OS_LINUX 53 | detail::lnx::io_queue& ioQueue, 54 | #endif 55 | socket& s, 56 | const void* buffer, 57 | std::size_t byteCount) noexcept 58 | : cppcoro::detail::io_operation { 59 | #if CPPCORO_OS_LINUX 60 | ioQueue 61 | #endif 62 | } 63 | , m_impl(s, buffer, byteCount) 64 | {} 65 | 66 | private: 67 | 68 | friend cppcoro::detail::io_operation; 69 | 70 | bool try_start() noexcept { return m_impl.try_start(*this); } 71 | 72 | socket_send_operation_impl m_impl; 73 | 74 | }; 75 | 76 | class socket_send_operation_cancellable 77 | : public cppcoro::detail::io_operation_cancellable 78 | { 79 | public: 80 | 81 | socket_send_operation_cancellable( 82 | #if CPPCORO_OS_LINUX 83 | detail::lnx::io_queue& ioQueue, 84 | #endif 85 | socket& s, 86 | const void* buffer, 87 | std::size_t byteCount, 88 | cancellation_token&& ct) noexcept 89 | : cppcoro::detail::io_operation_cancellable { 90 | #if CPPCORO_OS_LINUX 91 | ioQueue, 92 | #endif 93 | std::move(ct) 94 | } 95 | , m_impl(s, buffer, byteCount) 96 | {} 97 | 98 | private: 99 | 100 | friend cppcoro::detail::io_operation_cancellable; 101 | 102 | bool try_start() noexcept { return m_impl.try_start(*this); } 103 | void cancel() noexcept { return m_impl.cancel(*this); } 104 | 105 | socket_send_operation_impl m_impl; 106 | 107 | }; 108 | 109 | } // namespace cppcoro::net 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /include/cppcoro/net/socket_send_to_operation.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED 6 | #define CPPCORO_NET_SOCKET_SEND_TO_OPERATION_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #if CPPCORO_OS_WINNT 15 | # include 16 | # include 17 | #elif CPPCORO_OS_LINUX 18 | # include 19 | # include 20 | #endif 21 | 22 | namespace cppcoro::net 23 | { 24 | class socket; 25 | 26 | class socket_send_to_operation_impl 27 | { 28 | public: 29 | 30 | socket_send_to_operation_impl( 31 | socket& s, 32 | const ip_endpoint& destination, 33 | const void* buffer, 34 | std::size_t byteCount) noexcept 35 | : m_socket(s) 36 | , m_destination(destination) 37 | , m_buffer(const_cast(buffer), byteCount) 38 | {} 39 | 40 | bool try_start(cppcoro::detail::io_operation_base& operation) noexcept; 41 | void cancel(cppcoro::detail::io_operation_base& operation) noexcept; 42 | 43 | private: 44 | 45 | socket& m_socket; 46 | ip_endpoint m_destination; 47 | cppcoro::detail::sock_buf m_buffer; 48 | #if CPPCORO_USE_IO_RING 49 | sockaddr_storage m_destinationStorage; 50 | iovec m_vec; 51 | msghdr m_msgHdr; 52 | #endif 53 | }; 54 | 55 | class socket_send_to_operation 56 | : public cppcoro::detail::io_operation 57 | { 58 | public: 59 | 60 | socket_send_to_operation( 61 | #if CPPCORO_OS_LINUX 62 | detail::lnx::io_queue &ioQueue, 63 | #endif 64 | socket& s, 65 | const ip_endpoint& destination, 66 | const void* buffer, 67 | std::size_t byteCount) noexcept 68 | : cppcoro::detail::io_operation { 69 | #if CPPCORO_OS_LINUX 70 | ioQueue 71 | #endif 72 | } 73 | , m_impl(s, destination, buffer, byteCount) 74 | {} 75 | 76 | private: 77 | friend cppcoro::detail::io_operation; 78 | 79 | bool try_start() noexcept { return m_impl.try_start(*this); } 80 | 81 | socket_send_to_operation_impl m_impl; 82 | 83 | }; 84 | 85 | class socket_send_to_operation_cancellable 86 | : public cppcoro::detail::io_operation_cancellable 87 | { 88 | public: 89 | 90 | socket_send_to_operation_cancellable( 91 | #if CPPCORO_OS_LINUX 92 | detail::lnx::io_queue& ioQueue, 93 | #endif 94 | socket& s, 95 | const ip_endpoint& destination, 96 | const void* buffer, 97 | std::size_t byteCount, 98 | cancellation_token&& ct) noexcept 99 | : cppcoro::detail::io_operation_cancellable 100 | { 101 | #if CPPCORO_OS_LINUX 102 | ioQueue, 103 | #endif 104 | std::move(ct) 105 | } 106 | , m_impl(s, destination, buffer, byteCount) 107 | {} 108 | 109 | private: 110 | 111 | friend cppcoro::detail::io_operation_cancellable; 112 | 113 | bool try_start() noexcept { return m_impl.try_start(*this); } 114 | void cancel() noexcept { return m_impl.cancel(*this); } 115 | 116 | socket_send_to_operation_impl m_impl; 117 | 118 | }; 119 | 120 | } // namespace cppcoro::net 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /include/cppcoro/operation_cancelled.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED 6 | #define CPPCORO_OPERATION_CANCELLED_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | class operation_cancelled : public std::exception 13 | { 14 | public: 15 | 16 | operation_cancelled() noexcept 17 | : std::exception() 18 | {} 19 | 20 | const char* what() const noexcept override { return "operation cancelled"; } 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/cppcoro/read_only_file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_READ_ONLY_FILE_HPP_INCLUDED 6 | #define CPPCORO_READ_ONLY_FILE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace cppcoro 15 | { 16 | class read_only_file : public readable_file 17 | { 18 | public: 19 | 20 | /// Open a file for read-only access. 21 | /// 22 | /// \param ioContext 23 | /// The I/O context to use when dispatching I/O completion events. 24 | /// When asynchronous read operations on this file complete the 25 | /// completion events will be dispatched to an I/O thread associated 26 | /// with the I/O context. 27 | /// 28 | /// \param path 29 | /// Path of the file to open. 30 | /// 31 | /// \param shareMode 32 | /// Specifies the access to be allowed on the file concurrently with this file access. 33 | /// 34 | /// \param bufferingMode 35 | /// Specifies the modes/hints to provide to the OS that affects the behaviour 36 | /// of its file buffering. 37 | /// 38 | /// \return 39 | /// An object that can be used to read from the file. 40 | /// 41 | /// \throw std::system_error 42 | /// If the file could not be opened for read. 43 | [[nodiscard]] 44 | static read_only_file open( 45 | io_service& ioService, 46 | const cppcoro::filesystem::path& path, 47 | file_share_mode shareMode = file_share_mode::read, 48 | file_buffering_mode bufferingMode = file_buffering_mode::default_); 49 | 50 | protected: 51 | 52 | read_only_file(detail::safe_handle&& fileHandle) noexcept; 53 | }; 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/cppcoro/read_write_file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_READ_WRITE_FILE_HPP_INCLUDED 6 | #define CPPCORO_READ_WRITE_FILE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace cppcoro 17 | { 18 | class read_write_file : public readable_file, public writable_file 19 | { 20 | public: 21 | 22 | /// Open a file for read-write access. 23 | /// 24 | /// \param ioContext 25 | /// The I/O context to use when dispatching I/O completion events. 26 | /// When asynchronous write operations on this file complete the 27 | /// completion events will be dispatched to an I/O thread associated 28 | /// with the I/O context. 29 | /// 30 | /// \param pathMode 31 | /// Path of the file to open. 32 | /// 33 | /// \param openMode 34 | /// Specifies how the file should be opened and how to handle cases 35 | /// when the file exists or doesn't exist. 36 | /// 37 | /// \param shareMode 38 | /// Specifies the access to be allowed on the file concurrently with this file access. 39 | /// 40 | /// \param bufferingMode 41 | /// Specifies the modes/hints to provide to the OS that affects the behaviour 42 | /// of its file buffering. 43 | /// 44 | /// \return 45 | /// An object that can be used to write to the file. 46 | /// 47 | /// \throw std::system_error 48 | /// If the file could not be opened for write. 49 | [[nodiscard]] 50 | static read_write_file open( 51 | io_service& ioService, 52 | const cppcoro::filesystem::path& path, 53 | file_open_mode openMode = file_open_mode::create_or_open, 54 | file_share_mode shareMode = file_share_mode::none, 55 | file_buffering_mode bufferingMode = file_buffering_mode::default_); 56 | 57 | protected: 58 | read_write_file(detail::safe_handle&& fileHandle) noexcept; 59 | }; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /include/cppcoro/readable_file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_READABLE_FILE_HPP_INCLUDED 6 | #define CPPCORO_READABLE_FILE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | class readable_file : virtual public file 15 | { 16 | public: 17 | 18 | /// Read some data from the file. 19 | /// 20 | /// Reads \a byteCount bytes from the file starting at \a offset 21 | /// into the specified \a buffer. 22 | /// 23 | /// \param offset 24 | /// The offset within the file to start reading from. 25 | /// If the file has been opened using file_buffering_mode::unbuffered 26 | /// then the offset must be a multiple of the file-system's sector size. 27 | /// 28 | /// \param buffer 29 | /// The buffer to read the file contents into. 30 | /// If the file has been opened using file_buffering_mode::unbuffered 31 | /// then the address of the start of the buffer must be a multiple of 32 | /// the file-system's sector size. 33 | /// 34 | /// \param byteCount 35 | /// The number of bytes to read from the file. 36 | /// If the file has been opeend using file_buffering_mode::unbuffered 37 | /// then the byteCount must be a multiple of the file-system's sector size. 38 | /// 39 | /// \param ct 40 | /// An optional cancellation_token that can be used to cancel the 41 | /// read operation before it completes. 42 | /// 43 | /// \return 44 | /// An object that represents the read-operation. 45 | /// This object must be co_await'ed to start the read operation. 46 | [[nodiscard]] 47 | file_read_operation read( 48 | std::uint64_t offset, 49 | void* buffer, 50 | std::size_t byteCount) const noexcept; 51 | [[nodiscard]] 52 | file_read_operation_cancellable read( 53 | std::uint64_t offset, 54 | void* buffer, 55 | std::size_t byteCount, 56 | cancellation_token ct) const noexcept; 57 | 58 | protected: 59 | 60 | using file::file; 61 | 62 | }; 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /include/cppcoro/round_robin_scheduler.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED 6 | #define CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace cppcoro 17 | { 18 | #if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 19 | /// This is a scheduler class that schedules coroutines in a round-robin 20 | /// fashion once N coroutines have been scheduled to it. 21 | /// 22 | /// Only supports access from a single thread at a time so 23 | /// 24 | /// This implementation was inspired by Gor Nishanov's CppCon 2018 talk 25 | /// about nano-coroutines. 26 | /// 27 | /// The implementation relies on symmetric transfer and noop_coroutine() 28 | /// and so only works with a relatively recent version of Clang and does 29 | /// not yet work with MSVC. 30 | template 31 | class round_robin_scheduler 32 | { 33 | static_assert( 34 | N >= 2, 35 | "Round robin scheduler must be configured to support at least two coroutines"); 36 | 37 | class schedule_operation 38 | { 39 | public: 40 | explicit schedule_operation(round_robin_scheduler& s) noexcept : m_scheduler(s) {} 41 | 42 | bool await_ready() noexcept 43 | { 44 | return false; 45 | } 46 | 47 | cppcoro::coroutine_handle<> await_suspend( 48 | cppcoro::coroutine_handle<> awaitingCoroutine) noexcept 49 | { 50 | return m_scheduler.exchange_next(awaitingCoroutine); 51 | } 52 | 53 | void await_resume() noexcept {} 54 | 55 | private: 56 | round_robin_scheduler& m_scheduler; 57 | }; 58 | 59 | friend class schedule_operation; 60 | 61 | public: 62 | round_robin_scheduler() noexcept 63 | : m_index(0) 64 | , m_noop(cppcoro::noop_coroutine()) 65 | { 66 | for (size_t i = 0; i < N - 1; ++i) 67 | { 68 | m_coroutines[i] = m_noop(); 69 | } 70 | } 71 | 72 | ~round_robin_scheduler() 73 | { 74 | // All tasks should have been joined before calling destructor. 75 | assert(std::all_of( 76 | m_coroutines.begin(), 77 | m_coroutines.end(), 78 | [&](auto h) { return h == m_noop; })); 79 | } 80 | 81 | schedule_operation schedule() noexcept 82 | { 83 | return schedule_operation{ *this }; 84 | } 85 | 86 | /// Resume any queued coroutines until there are no more coroutines. 87 | void drain() noexcept 88 | { 89 | size_t countRemaining = N - 1; 90 | do 91 | { 92 | auto nextToResume = exchange_next(m_noop); 93 | if (nextToResume != m_noop) 94 | { 95 | nextToResume.resume(); 96 | countRemaining = N - 1; 97 | } 98 | else 99 | { 100 | --countRemaining; 101 | } 102 | } while (countRemaining > 0); 103 | } 104 | 105 | private: 106 | 107 | cppcoro::coroutine_handle exchange_next( 108 | cppcoro::coroutine_handle<> coroutine) noexcept 109 | { 110 | auto coroutineToResume = std::exchange( 111 | m_scheduler.m_coroutines[m_scheduler.m_index], 112 | awaitingCoroutine); 113 | m_scheduler.m_index = m_scheduler.m_index < (N - 2) ? m_scheduler.m_index + 1 : 0; 114 | return coroutineToResume; 115 | } 116 | 117 | size_t m_index; 118 | const cppcoro::coroutine_handle<> m_noop; 119 | std::array, N - 1> m_coroutines; 120 | }; 121 | #endif 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /include/cppcoro/schedule_on.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SCHEDULE_ON_HPP_INCLUDED 6 | #define CPPCORO_SCHEDULE_ON_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace cppcoro 16 | { 17 | template 18 | struct schedule_on_transform 19 | { 20 | explicit schedule_on_transform(SCHEDULER& scheduler) noexcept 21 | : scheduler(scheduler) 22 | {} 23 | 24 | SCHEDULER& scheduler; 25 | }; 26 | 27 | template 28 | schedule_on_transform schedule_on(SCHEDULER& scheduler) 29 | { 30 | return schedule_on_transform{ scheduler }; 31 | } 32 | 33 | template 34 | decltype(auto) operator|(T&& value, schedule_on_transform transform) 35 | { 36 | return schedule_on(transform.scheduler, std::forward(value)); 37 | } 38 | 39 | template 40 | auto schedule_on(SCHEDULER& scheduler, AWAITABLE awaitable) 41 | -> task::await_result_t>> 42 | { 43 | co_await scheduler.schedule(); 44 | co_return co_await std::move(awaitable); 45 | } 46 | 47 | template 48 | async_generator schedule_on(SCHEDULER& scheduler, async_generator source) 49 | { 50 | // Transfer exection to the scheduler before the implicit calls to 51 | // 'co_await begin()' or subsequent calls to `co_await iterator::operator++()` 52 | // below. This ensures that all calls to the generator's coroutine_handle<>::resume() 53 | // are executed on the execution context of the scheduler. 54 | co_await scheduler.schedule(); 55 | 56 | const auto itEnd = source.end(); 57 | auto it = co_await source.begin(); 58 | while (it != itEnd) 59 | { 60 | co_yield *it; 61 | 62 | co_await scheduler.schedule(); 63 | 64 | (void)co_await ++it; 65 | } 66 | } 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /include/cppcoro/sequence_range.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED 6 | #define CPPCORO_SEQUENCE_RANGE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace cppcoro 14 | { 15 | template> 16 | class sequence_range 17 | { 18 | public: 19 | 20 | using value_type = SEQUENCE; 21 | using difference_type = typename TRAITS::difference_type; 22 | using size_type = typename TRAITS::size_type; 23 | 24 | class const_iterator 25 | { 26 | public: 27 | 28 | using iterator_category = std::random_access_iterator_tag; 29 | using value_type = SEQUENCE; 30 | using difference_type = typename TRAITS::difference_type; 31 | using reference = const SEQUENCE&; 32 | using pointer = const SEQUENCE*; 33 | 34 | explicit constexpr const_iterator(SEQUENCE value) noexcept : m_value(value) {} 35 | 36 | const SEQUENCE& operator*() const noexcept { return m_value; } 37 | const SEQUENCE* operator->() const noexcept { return std::addressof(m_value); } 38 | 39 | const_iterator& operator++() noexcept { ++m_value; return *this; } 40 | const_iterator& operator--() noexcept { --m_value; return *this; } 41 | 42 | const_iterator operator++(int) noexcept { return const_iterator(m_value++); } 43 | const_iterator operator--(int) noexcept { return const_iterator(m_value--); } 44 | 45 | constexpr difference_type operator-(const_iterator other) const noexcept { return TRAITS::difference(m_value, other.m_value); } 46 | constexpr const_iterator operator-(difference_type delta) const noexcept { return const_iterator{ static_cast(m_value - delta) }; } 47 | constexpr const_iterator operator+(difference_type delta) const noexcept { return const_iterator{ static_cast(m_value + delta) }; } 48 | 49 | constexpr bool operator==(const_iterator other) const noexcept { return m_value == other.m_value; } 50 | constexpr bool operator!=(const_iterator other) const noexcept { return m_value != other.m_value; } 51 | 52 | private: 53 | 54 | SEQUENCE m_value; 55 | 56 | }; 57 | 58 | constexpr sequence_range() noexcept 59 | : m_begin() 60 | , m_end() 61 | {} 62 | 63 | constexpr sequence_range(SEQUENCE begin, SEQUENCE end) noexcept 64 | : m_begin(begin) 65 | , m_end(end) 66 | {} 67 | 68 | constexpr const_iterator begin() const noexcept { return const_iterator(m_begin); } 69 | constexpr const_iterator end() const noexcept { return const_iterator(m_end); } 70 | 71 | constexpr SEQUENCE front() const noexcept { return m_begin; } 72 | constexpr SEQUENCE back() const noexcept { return m_end - 1; } 73 | 74 | constexpr size_type size() const noexcept 75 | { 76 | return static_cast(TRAITS::difference(m_end, m_begin)); 77 | } 78 | 79 | constexpr bool empty() const noexcept 80 | { 81 | return m_begin == m_end; 82 | } 83 | 84 | constexpr SEQUENCE operator[](size_type index) const noexcept 85 | { 86 | return m_begin + index; 87 | } 88 | 89 | constexpr sequence_range first(size_type count) const noexcept 90 | { 91 | return sequence_range{ m_begin, static_cast(m_begin + std::min(size(), count)) }; 92 | } 93 | 94 | constexpr sequence_range skip(size_type count) const noexcept 95 | { 96 | return sequence_range{ m_begin + std::min(size(), count), m_end }; 97 | } 98 | 99 | private: 100 | 101 | SEQUENCE m_begin; 102 | SEQUENCE m_end; 103 | 104 | }; 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /include/cppcoro/sequence_traits.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED 6 | #define CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | template 13 | struct sequence_traits 14 | { 15 | using value_type = SEQUENCE; 16 | using difference_type = std::make_signed_t; 17 | using size_type = std::make_unsigned_t; 18 | 19 | static constexpr value_type initial_sequence = static_cast(-1); 20 | 21 | static constexpr difference_type difference(value_type a, value_type b) 22 | { 23 | return static_cast(a - b); 24 | } 25 | 26 | static constexpr bool precedes(value_type a, value_type b) 27 | { 28 | return difference(a, b) < 0; 29 | } 30 | }; 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /include/cppcoro/single_consumer_async_auto_reset_event.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED 6 | #define CPPCORO_SINGLE_CONSUMER_ASYNC_AUTO_RESET_EVENT_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace cppcoro 14 | { 15 | class single_consumer_async_auto_reset_event 16 | { 17 | public: 18 | 19 | single_consumer_async_auto_reset_event(bool initiallySet = false) noexcept 20 | : m_state(initiallySet ? this : nullptr) 21 | {} 22 | 23 | void set() noexcept 24 | { 25 | void* oldValue = m_state.exchange(this, std::memory_order_release); 26 | if (oldValue != nullptr && oldValue != this) 27 | { 28 | // There was a waiting coroutine that we now need to resume. 29 | auto handle = *static_cast*>(oldValue); 30 | 31 | // We also need to transition the state back to 'not set' before 32 | // resuming the coroutine. This operation needs to be 'acquire' 33 | // so that it synchronises with other calls to .set() that execute 34 | // concurrently with this call and execute the above m_state.exchange(this) 35 | // operation with 'release' semantics. 36 | // This needs to be an exchange() instead of a store() so that it can have 37 | // 'acquire' semantics. 38 | (void)m_state.exchange(nullptr, std::memory_order_acquire); 39 | 40 | // Finally, resume the waiting coroutine. 41 | handle.resume(); 42 | } 43 | } 44 | 45 | auto operator co_await() const noexcept 46 | { 47 | class awaiter 48 | { 49 | public: 50 | 51 | awaiter(const single_consumer_async_auto_reset_event& event) noexcept 52 | : m_event(event) 53 | {} 54 | 55 | bool await_ready() const noexcept { return false; } 56 | 57 | bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept 58 | { 59 | m_awaitingCoroutine = awaitingCoroutine; 60 | 61 | void* oldValue = nullptr; 62 | if (!m_event.m_state.compare_exchange_strong( 63 | oldValue, 64 | &m_awaitingCoroutine, 65 | std::memory_order_release, 66 | std::memory_order_relaxed)) 67 | { 68 | // This will only fail if the event was already 'set' 69 | // In which case we can just reset back to 'not set' 70 | // Need to use exchange() rather than store() here so we can make this 71 | // operation an 'acquire' operation so that we get visibility of all 72 | // writes prior to all preceding calls to .set(). 73 | assert(oldValue == &m_event); 74 | (void)m_event.m_state.exchange(nullptr, std::memory_order_acquire); 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | 81 | void await_resume() noexcept {} 82 | 83 | private: 84 | const single_consumer_async_auto_reset_event& m_event; 85 | cppcoro::coroutine_handle<> m_awaitingCoroutine; 86 | }; 87 | 88 | return awaiter{ *this }; 89 | } 90 | 91 | private: 92 | 93 | // nullptr - not set, no waiter 94 | // this - set 95 | // other - not set, pointer is address of a coroutine_handle<> to resume. 96 | mutable std::atomic m_state; 97 | 98 | }; 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /include/cppcoro/static_thread_pool.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED 6 | #define CPPCORO_STATIC_THREAD_POOL_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace cppcoro 17 | { 18 | class static_thread_pool 19 | { 20 | public: 21 | 22 | /// Initialise to a number of threads equal to the number of cores 23 | /// on the current machine. 24 | static_thread_pool(); 25 | 26 | /// Construct a thread pool with the specified number of threads. 27 | /// 28 | /// \param threadCount 29 | /// The number of threads in the pool that will be used to execute work. 30 | explicit static_thread_pool(std::uint32_t threadCount); 31 | 32 | ~static_thread_pool(); 33 | 34 | class schedule_operation 35 | { 36 | public: 37 | 38 | schedule_operation(static_thread_pool* tp) noexcept : m_threadPool(tp) {} 39 | 40 | bool await_ready() noexcept { return false; } 41 | void await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept; 42 | void await_resume() noexcept {} 43 | 44 | private: 45 | 46 | friend class static_thread_pool; 47 | 48 | static_thread_pool* m_threadPool; 49 | cppcoro::coroutine_handle<> m_awaitingCoroutine; 50 | schedule_operation* m_next; 51 | 52 | }; 53 | 54 | std::uint32_t thread_count() const noexcept { return m_threadCount; } 55 | 56 | [[nodiscard]] 57 | schedule_operation schedule() noexcept { return schedule_operation{ this }; } 58 | 59 | private: 60 | 61 | friend class schedule_operation; 62 | 63 | void run_worker_thread(std::uint32_t threadIndex) noexcept; 64 | 65 | void shutdown(); 66 | 67 | void schedule_impl(schedule_operation* operation) noexcept; 68 | 69 | void remote_enqueue(schedule_operation* operation) noexcept; 70 | 71 | bool has_any_queued_work_for(std::uint32_t threadIndex) noexcept; 72 | 73 | bool approx_has_any_queued_work_for(std::uint32_t threadIndex) const noexcept; 74 | 75 | bool is_shutdown_requested() const noexcept; 76 | 77 | void notify_intent_to_sleep(std::uint32_t threadIndex) noexcept; 78 | void try_clear_intent_to_sleep(std::uint32_t threadIndex) noexcept; 79 | 80 | schedule_operation* try_global_dequeue() noexcept; 81 | 82 | /// Try to steal a task from another thread. 83 | /// 84 | /// \return 85 | /// A pointer to the operation that was stolen if one could be stolen 86 | /// from another thread. Otherwise returns nullptr if none of the other 87 | /// threads had any tasks that could be stolen. 88 | schedule_operation* try_steal_from_other_thread(std::uint32_t thisThreadIndex) noexcept; 89 | 90 | void wake_one_thread() noexcept; 91 | 92 | class thread_state; 93 | 94 | static thread_local thread_state* s_currentState; 95 | static thread_local static_thread_pool* s_currentThreadPool; 96 | 97 | const std::uint32_t m_threadCount; 98 | const std::unique_ptr m_threadStates; 99 | 100 | std::vector m_threads; 101 | 102 | std::atomic m_stopRequested; 103 | 104 | std::mutex m_globalQueueMutex; 105 | std::atomic m_globalQueueHead; 106 | 107 | //alignas(std::hardware_destructive_interference_size) 108 | std::atomic m_globalQueueTail; 109 | 110 | //alignas(std::hardware_destructive_interference_size) 111 | std::atomic m_sleepingThreadCount; 112 | 113 | }; 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/cppcoro/sync_wait.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SYNC_WAIT_HPP_INCLUDED 6 | #define CPPCORO_SYNC_WAIT_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace cppcoro 16 | { 17 | template 18 | auto sync_wait(AWAITABLE&& awaitable) 19 | -> typename cppcoro::awaitable_traits::await_result_t 20 | { 21 | #if CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC < 19'20'00000 22 | // HACK: Need to explicitly specify template argument to make_sync_wait_task 23 | // here to work around a bug in MSVC when passing parameters by universal 24 | // reference to a coroutine which causes the compiler to think it needs to 25 | // 'move' parameters passed by rvalue reference. 26 | auto task = detail::make_sync_wait_task(awaitable); 27 | #else 28 | auto task = detail::make_sync_wait_task(std::forward(awaitable)); 29 | #endif 30 | detail::lightweight_manual_reset_event event; 31 | task.start(event); 32 | event.wait(); 33 | return task.result(); 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /include/cppcoro/when_all.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_WHEN_ALL_HPP_INCLUDED 6 | #define CPPCORO_WHEN_ALL_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace cppcoro 23 | { 24 | ////////// 25 | // Variadic when_all() 26 | 27 | template< 28 | typename... AWAITABLES, 29 | std::enable_if_t< 30 | std::conjunction_v>>...>, 31 | int> = 0> 32 | [[nodiscard]] auto when_all(AWAITABLES&&... awaitables) 33 | { 34 | return fmap([](auto&& taskTuple) 35 | { 36 | return std::apply([](auto&&... tasks) { 37 | return std::make_tuple(static_cast(tasks).non_void_result()...); 38 | }, static_cast(taskTuple)); 39 | }, when_all_ready(std::forward(awaitables)...)); 40 | } 41 | 42 | ////////// 43 | // when_all() with vector of awaitable 44 | 45 | template< 46 | typename AWAITABLE, 47 | typename RESULT = typename awaitable_traits>::await_result_t, 48 | std::enable_if_t, int> = 0> 49 | [[nodiscard]] 50 | auto when_all(std::vector awaitables) 51 | { 52 | return fmap([](auto&& taskVector) { 53 | for (auto& task : taskVector) 54 | { 55 | task.result(); 56 | } 57 | }, when_all_ready(std::move(awaitables))); 58 | } 59 | 60 | template< 61 | typename AWAITABLE, 62 | typename RESULT = typename awaitable_traits>::await_result_t, 63 | std::enable_if_t, int> = 0> 64 | [[nodiscard]] 65 | auto when_all(std::vector awaitables) 66 | { 67 | using result_t = std::conditional_t< 68 | std::is_lvalue_reference_v, 69 | std::reference_wrapper>, 70 | std::remove_reference_t>; 71 | 72 | return fmap([](auto&& taskVector) { 73 | std::vector results; 74 | results.reserve(taskVector.size()); 75 | for (auto& task : taskVector) 76 | { 77 | if constexpr (std::is_rvalue_reference_v) 78 | { 79 | results.emplace_back(std::move(task).result()); 80 | } 81 | else 82 | { 83 | results.emplace_back(task.result()); 84 | } 85 | } 86 | return results; 87 | }, when_all_ready(std::move(awaitables))); 88 | } 89 | } 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /include/cppcoro/when_all_ready.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_WHEN_ALL_READY_HPP_INCLUDED 6 | #define CPPCORO_WHEN_ALL_READY_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace cppcoro 22 | { 23 | template< 24 | typename... AWAITABLES, 25 | std::enable_if_t>>...>, int> = 0> 27 | [[nodiscard]] 28 | CPPCORO_FORCE_INLINE auto when_all_ready(AWAITABLES&&... awaitables) 29 | { 30 | return detail::when_all_ready_awaitable>>::await_result_t>...>>( 32 | std::make_tuple(detail::make_when_all_task(std::forward(awaitables))...)); 33 | } 34 | 35 | // TODO: Generalise this from vector to arbitrary sequence of awaitable. 36 | 37 | template< 38 | typename AWAITABLE, 39 | typename RESULT = typename awaitable_traits>::await_result_t> 40 | [[nodiscard]] auto when_all_ready(std::vector awaitables) 41 | { 42 | std::vector> tasks; 43 | 44 | tasks.reserve(awaitables.size()); 45 | 46 | for (auto& awaitable : awaitables) 47 | { 48 | tasks.emplace_back(detail::make_when_all_task(std::move(awaitable))); 49 | } 50 | 51 | return detail::when_all_ready_awaitable>>( 52 | std::move(tasks)); 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/cppcoro/writable_file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_WRITABLE_FILE_HPP_INCLUDED 6 | #define CPPCORO_WRITABLE_FILE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cppcoro 13 | { 14 | class writable_file : virtual public file 15 | { 16 | public: 17 | 18 | /// Set the size of the file. 19 | /// 20 | /// \param fileSize 21 | /// The new size of the file in bytes. 22 | void set_size(std::uint64_t fileSize); 23 | 24 | /// Write some data to the file. 25 | /// 26 | /// Writes \a byteCount bytes from the file starting at \a offset 27 | /// into the specified \a buffer. 28 | /// 29 | /// \param offset 30 | /// The offset within the file to start writing from. 31 | /// If the file has been opened using file_buffering_mode::unbuffered 32 | /// then the offset must be a multiple of the file-system's sector size. 33 | /// 34 | /// \param buffer 35 | /// The buffer containing the data to be written to the file. 36 | /// If the file has been opened using file_buffering_mode::unbuffered 37 | /// then the address of the start of the buffer must be a multiple of 38 | /// the file-system's sector size. 39 | /// 40 | /// \param byteCount 41 | /// The number of bytes to write to the file. 42 | /// If the file has been opeend using file_buffering_mode::unbuffered 43 | /// then the byteCount must be a multiple of the file-system's sector size. 44 | /// 45 | /// \param ct 46 | /// An optional cancellation_token that can be used to cancel the 47 | /// write operation before it completes. 48 | /// 49 | /// \return 50 | /// An object that represents the write operation. 51 | /// This object must be co_await'ed to start the write operation. 52 | [[nodiscard]] 53 | file_write_operation write( 54 | std::uint64_t offset, 55 | const void* buffer, 56 | std::size_t byteCount) noexcept; 57 | [[nodiscard]] 58 | file_write_operation_cancellable write( 59 | std::uint64_t offset, 60 | const void* buffer, 61 | std::size_t byteCount, 62 | cancellation_token ct) noexcept; 63 | 64 | protected: 65 | 66 | using file::file; 67 | 68 | }; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /include/cppcoro/write_only_file.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED 6 | #define CPPCORO_WRITE_ONLY_FILE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace cppcoro 16 | { 17 | class write_only_file : public writable_file 18 | { 19 | public: 20 | 21 | /// Open a file for write-only access. 22 | /// 23 | /// \param ioContext 24 | /// The I/O context to use when dispatching I/O completion events. 25 | /// When asynchronous write operations on this file complete the 26 | /// completion events will be dispatched to an I/O thread associated 27 | /// with the I/O context. 28 | /// 29 | /// \param pathMode 30 | /// Path of the file to open. 31 | /// 32 | /// \param openMode 33 | /// Specifies how the file should be opened and how to handle cases 34 | /// when the file exists or doesn't exist. 35 | /// 36 | /// \param shareMode 37 | /// Specifies the access to be allowed on the file concurrently with this file access. 38 | /// 39 | /// \param bufferingMode 40 | /// Specifies the modes/hints to provide to the OS that affects the behaviour 41 | /// of its file buffering. 42 | /// 43 | /// \return 44 | /// An object that can be used to write to the file. 45 | /// 46 | /// \throw std::system_error 47 | /// If the file could not be opened for write. 48 | [[nodiscard]] 49 | static write_only_file open( 50 | io_service& ioService, 51 | const cppcoro::filesystem::path& path, 52 | file_open_mode openMode = file_open_mode::create_or_open, 53 | file_share_mode shareMode = file_share_mode::none, 54 | file_buffering_mode bufferingMode = file_buffering_mode::default_); 55 | 56 | protected: 57 | write_only_file(detail::safe_handle&& fileHandle) noexcept; 58 | }; 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /lib/async_manual_reset_event.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | cppcoro::async_manual_reset_event::async_manual_reset_event(bool initiallySet) noexcept 13 | : m_state(initiallySet ? static_cast(this) : nullptr) 14 | {} 15 | 16 | cppcoro::async_manual_reset_event::~async_manual_reset_event() 17 | { 18 | // There should be no coroutines still awaiting the event. 19 | assert( 20 | m_state.load(std::memory_order_relaxed) == nullptr || 21 | m_state.load(std::memory_order_relaxed) == static_cast(this)); 22 | } 23 | 24 | bool cppcoro::async_manual_reset_event::is_set() const noexcept 25 | { 26 | return m_state.load(std::memory_order_acquire) == static_cast(this); 27 | } 28 | 29 | cppcoro::async_manual_reset_event_operation 30 | cppcoro::async_manual_reset_event::operator co_await() const noexcept 31 | { 32 | return async_manual_reset_event_operation{ *this }; 33 | } 34 | 35 | void cppcoro::async_manual_reset_event::set() noexcept 36 | { 37 | void* const setState = static_cast(this); 38 | 39 | // Needs 'release' semantics so that prior writes are visible to event awaiters 40 | // that synchronise either via 'is_set()' or 'operator co_await()'. 41 | // Needs 'acquire' semantics in case there are any waiters so that we see 42 | // prior writes to the waiting coroutine's state and to the contents of 43 | // the queued async_manual_reset_event_operation objects. 44 | void* oldState = m_state.exchange(setState, std::memory_order_acq_rel); 45 | if (oldState != setState) 46 | { 47 | auto* current = static_cast(oldState); 48 | while (current != nullptr) 49 | { 50 | auto* next = current->m_next; 51 | current->m_awaiter.resume(); 52 | current = next; 53 | } 54 | } 55 | } 56 | 57 | void cppcoro::async_manual_reset_event::reset() noexcept 58 | { 59 | void* oldState = static_cast(this); 60 | m_state.compare_exchange_strong(oldState, nullptr, std::memory_order_relaxed); 61 | } 62 | 63 | cppcoro::async_manual_reset_event_operation::async_manual_reset_event_operation( 64 | const async_manual_reset_event& event) noexcept 65 | : m_event(event) 66 | { 67 | } 68 | 69 | bool cppcoro::async_manual_reset_event_operation::await_ready() const noexcept 70 | { 71 | return m_event.is_set(); 72 | } 73 | 74 | bool cppcoro::async_manual_reset_event_operation::await_suspend( 75 | cppcoro::coroutine_handle<> awaiter) noexcept 76 | { 77 | m_awaiter = awaiter; 78 | 79 | const void* const setState = static_cast(&m_event); 80 | 81 | void* oldState = m_event.m_state.load(std::memory_order_acquire); 82 | do 83 | { 84 | if (oldState == setState) 85 | { 86 | // State is now 'set' no need to suspend. 87 | return false; 88 | } 89 | 90 | m_next = static_cast(oldState); 91 | } while (!m_event.m_state.compare_exchange_weak( 92 | oldState, 93 | static_cast(this), 94 | std::memory_order_release, 95 | std::memory_order_acquire)); 96 | 97 | // Successfully queued this waiter to the list. 98 | return true; 99 | } 100 | -------------------------------------------------------------------------------- /lib/async_mutex.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | cppcoro::async_mutex::async_mutex() noexcept 11 | : m_state(not_locked) 12 | , m_waiters(nullptr) 13 | {} 14 | 15 | cppcoro::async_mutex::~async_mutex() 16 | { 17 | [[maybe_unused]] auto state = m_state.load(std::memory_order_relaxed); 18 | assert(state == not_locked || state == locked_no_waiters); 19 | assert(m_waiters == nullptr); 20 | } 21 | 22 | bool cppcoro::async_mutex::try_lock() noexcept 23 | { 24 | // Try to atomically transition from nullptr (not-locked) -> this (locked-no-waiters). 25 | auto oldState = not_locked; 26 | return m_state.compare_exchange_strong( 27 | oldState, 28 | locked_no_waiters, 29 | std::memory_order_acquire, 30 | std::memory_order_relaxed); 31 | } 32 | 33 | cppcoro::async_mutex_lock_operation cppcoro::async_mutex::lock_async() noexcept 34 | { 35 | return async_mutex_lock_operation{ *this }; 36 | } 37 | 38 | cppcoro::async_mutex_scoped_lock_operation cppcoro::async_mutex::scoped_lock_async() noexcept 39 | { 40 | return async_mutex_scoped_lock_operation{ *this }; 41 | } 42 | 43 | void cppcoro::async_mutex::unlock() 44 | { 45 | assert(m_state.load(std::memory_order_relaxed) != not_locked); 46 | 47 | async_mutex_lock_operation* waitersHead = m_waiters; 48 | if (waitersHead == nullptr) 49 | { 50 | auto oldState = locked_no_waiters; 51 | const bool releasedLock = m_state.compare_exchange_strong( 52 | oldState, 53 | not_locked, 54 | std::memory_order_release, 55 | std::memory_order_relaxed); 56 | if (releasedLock) 57 | { 58 | return; 59 | } 60 | 61 | // At least one new waiter. 62 | // Acquire the list of new waiter operations atomically. 63 | oldState = m_state.exchange(locked_no_waiters, std::memory_order_acquire); 64 | 65 | assert(oldState != locked_no_waiters && oldState != not_locked); 66 | 67 | // Transfer the list to m_waiters, reversing the list in the process so 68 | // that the head of the list is the first to be resumed. 69 | auto* next = reinterpret_cast(oldState); 70 | do 71 | { 72 | auto* temp = next->m_next; 73 | next->m_next = waitersHead; 74 | waitersHead = next; 75 | next = temp; 76 | } while (next != nullptr); 77 | } 78 | 79 | assert(waitersHead != nullptr); 80 | 81 | m_waiters = waitersHead->m_next; 82 | 83 | // Resume the waiter. 84 | // This will pass the ownership of the lock on to that operation/coroutine. 85 | waitersHead->m_awaiter.resume(); 86 | } 87 | 88 | bool cppcoro::async_mutex_lock_operation::await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept 89 | { 90 | m_awaiter = awaiter; 91 | 92 | std::uintptr_t oldState = m_mutex.m_state.load(std::memory_order_acquire); 93 | while (true) 94 | { 95 | if (oldState == async_mutex::not_locked) 96 | { 97 | if (m_mutex.m_state.compare_exchange_weak( 98 | oldState, 99 | async_mutex::locked_no_waiters, 100 | std::memory_order_acquire, 101 | std::memory_order_relaxed)) 102 | { 103 | // Acquired lock, don't suspend. 104 | return false; 105 | } 106 | } 107 | else 108 | { 109 | // Try to push this operation onto the head of the waiter stack. 110 | m_next = reinterpret_cast(oldState); 111 | if (m_mutex.m_state.compare_exchange_weak( 112 | oldState, 113 | reinterpret_cast(this), 114 | std::memory_order_release, 115 | std::memory_order_relaxed)) 116 | { 117 | // Queued operation to waiters list, suspend now. 118 | return true; 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/auto_reset_event.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "auto_reset_event.hpp" 7 | 8 | #if CPPCORO_OS_WINNT 9 | # define WIN32_LEAN_AND_MEAN 10 | # include 11 | # include 12 | #endif 13 | 14 | namespace cppcoro 15 | { 16 | #if CPPCORO_OS_WINNT 17 | 18 | auto_reset_event::auto_reset_event(bool initiallySet) 19 | : m_event(::CreateEventW(NULL, FALSE, initiallySet ? TRUE : FALSE, NULL)) 20 | { 21 | if (m_event.handle() == NULL) 22 | { 23 | DWORD errorCode = ::GetLastError(); 24 | throw std::system_error 25 | { 26 | static_cast(errorCode), 27 | std::system_category(), 28 | "auto_reset_event: CreateEvent failed" 29 | }; 30 | } 31 | } 32 | 33 | auto_reset_event::~auto_reset_event() 34 | { 35 | } 36 | 37 | void auto_reset_event::set() 38 | { 39 | BOOL ok =::SetEvent(m_event.handle()); 40 | if (!ok) 41 | { 42 | DWORD errorCode = ::GetLastError(); 43 | throw std::system_error 44 | { 45 | static_cast(errorCode), 46 | std::system_category(), 47 | "auto_reset_event: SetEvent failed" 48 | }; 49 | } 50 | } 51 | 52 | void auto_reset_event::wait() 53 | { 54 | DWORD result = ::WaitForSingleObjectEx(m_event.handle(), INFINITE, FALSE); 55 | if (result != WAIT_OBJECT_0) 56 | { 57 | DWORD errorCode = ::GetLastError(); 58 | throw std::system_error 59 | { 60 | static_cast(errorCode), 61 | std::system_category(), 62 | "auto_reset_event: WaitForSingleObjectEx failed" 63 | }; 64 | } 65 | } 66 | 67 | #else 68 | 69 | auto_reset_event::auto_reset_event(bool initiallySet) 70 | : m_isSet(initiallySet) 71 | {} 72 | 73 | auto_reset_event::~auto_reset_event() 74 | {} 75 | 76 | void auto_reset_event::set() 77 | { 78 | std::unique_lock lock{ m_mutex }; 79 | if (!m_isSet) 80 | { 81 | m_isSet = true; 82 | m_cv.notify_one(); 83 | } 84 | } 85 | 86 | void auto_reset_event::wait() 87 | { 88 | std::unique_lock lock{ m_mutex }; 89 | while (!m_isSet) 90 | { 91 | m_cv.wait(lock); 92 | } 93 | m_isSet = false; 94 | } 95 | 96 | #endif 97 | } 98 | -------------------------------------------------------------------------------- /lib/auto_reset_event.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED 6 | #define CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #if CPPCORO_OS_WINNT 11 | # include 12 | #else 13 | # include 14 | # include 15 | #endif 16 | 17 | namespace cppcoro 18 | { 19 | class auto_reset_event 20 | { 21 | public: 22 | 23 | auto_reset_event(bool initiallySet = false); 24 | 25 | ~auto_reset_event(); 26 | 27 | void set(); 28 | 29 | void wait(); 30 | 31 | private: 32 | 33 | #if CPPCORO_OS_WINNT 34 | cppcoro::detail::win32::safe_handle m_event; 35 | #else 36 | std::mutex m_mutex; 37 | std::condition_variable m_cv; 38 | bool m_isSet; 39 | #endif 40 | 41 | }; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /lib/cancellation_registration.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include "cancellation_state.hpp" 9 | 10 | #include 11 | 12 | cppcoro::cancellation_registration::~cancellation_registration() 13 | { 14 | if (m_state != nullptr) 15 | { 16 | m_state->deregister_callback(this); 17 | m_state->release_token_ref(); 18 | } 19 | } 20 | 21 | void cppcoro::cancellation_registration::register_callback(cancellation_token&& token) 22 | { 23 | auto* state = token.m_state; 24 | if (state != nullptr && state->can_be_cancelled()) 25 | { 26 | m_state = state; 27 | if (state->try_register_callback(this)) 28 | { 29 | token.m_state = nullptr; 30 | } 31 | else 32 | { 33 | m_state = nullptr; 34 | m_callback(); 35 | } 36 | } 37 | else 38 | { 39 | m_state = nullptr; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/cancellation_source.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include "cancellation_state.hpp" 9 | 10 | #include 11 | 12 | cppcoro::cancellation_source::cancellation_source() 13 | : m_state(detail::cancellation_state::create()) 14 | { 15 | } 16 | 17 | cppcoro::cancellation_source::cancellation_source(const cancellation_source& other) noexcept 18 | : m_state(other.m_state) 19 | { 20 | if (m_state != nullptr) 21 | { 22 | m_state->add_source_ref(); 23 | } 24 | } 25 | 26 | cppcoro::cancellation_source::cancellation_source(cancellation_source&& other) noexcept 27 | : m_state(other.m_state) 28 | { 29 | other.m_state = nullptr; 30 | } 31 | 32 | cppcoro::cancellation_source::~cancellation_source() 33 | { 34 | if (m_state != nullptr) 35 | { 36 | m_state->release_source_ref(); 37 | } 38 | } 39 | 40 | cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(const cancellation_source& other) noexcept 41 | { 42 | if (m_state != other.m_state) 43 | { 44 | if (m_state != nullptr) 45 | { 46 | m_state->release_source_ref(); 47 | } 48 | 49 | m_state = other.m_state; 50 | 51 | if (m_state != nullptr) 52 | { 53 | m_state->add_source_ref(); 54 | } 55 | } 56 | 57 | return *this; 58 | } 59 | 60 | cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(cancellation_source&& other) noexcept 61 | { 62 | if (this != &other) 63 | { 64 | if (m_state != nullptr) 65 | { 66 | m_state->release_source_ref(); 67 | } 68 | 69 | m_state = other.m_state; 70 | other.m_state = nullptr; 71 | } 72 | 73 | return *this; 74 | } 75 | 76 | bool cppcoro::cancellation_source::can_be_cancelled() const noexcept 77 | { 78 | return m_state != nullptr; 79 | } 80 | 81 | cppcoro::cancellation_token cppcoro::cancellation_source::token() const noexcept 82 | { 83 | return cancellation_token(m_state); 84 | } 85 | 86 | void cppcoro::cancellation_source::request_cancellation() 87 | { 88 | if (m_state != nullptr) 89 | { 90 | m_state->request_cancellation(); 91 | } 92 | } 93 | 94 | bool cppcoro::cancellation_source::is_cancellation_requested() const noexcept 95 | { 96 | return m_state != nullptr && m_state->is_cancellation_requested(); 97 | } 98 | -------------------------------------------------------------------------------- /lib/cancellation_token.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #include "cancellation_state.hpp" 10 | 11 | #include 12 | #include 13 | 14 | cppcoro::cancellation_token::cancellation_token() noexcept 15 | : m_state(nullptr) 16 | { 17 | } 18 | 19 | cppcoro::cancellation_token::cancellation_token(const cancellation_token& other) noexcept 20 | : m_state(other.m_state) 21 | { 22 | if (m_state != nullptr) 23 | { 24 | m_state->add_token_ref(); 25 | } 26 | } 27 | 28 | cppcoro::cancellation_token::cancellation_token(cancellation_token&& other) noexcept 29 | : m_state(other.m_state) 30 | { 31 | other.m_state = nullptr; 32 | } 33 | 34 | cppcoro::cancellation_token::~cancellation_token() 35 | { 36 | if (m_state != nullptr) 37 | { 38 | m_state->release_token_ref(); 39 | } 40 | } 41 | 42 | cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(const cancellation_token& other) noexcept 43 | { 44 | if (other.m_state != m_state) 45 | { 46 | if (m_state != nullptr) 47 | { 48 | m_state->release_token_ref(); 49 | } 50 | 51 | m_state = other.m_state; 52 | 53 | if (m_state != nullptr) 54 | { 55 | m_state->add_token_ref(); 56 | } 57 | } 58 | 59 | return *this; 60 | } 61 | 62 | cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(cancellation_token&& other) noexcept 63 | { 64 | if (this != &other) 65 | { 66 | if (m_state != nullptr) 67 | { 68 | m_state->release_token_ref(); 69 | } 70 | 71 | m_state = other.m_state; 72 | other.m_state = nullptr; 73 | } 74 | 75 | return *this; 76 | } 77 | 78 | void cppcoro::cancellation_token::swap(cancellation_token& other) noexcept 79 | { 80 | std::swap(m_state, other.m_state); 81 | } 82 | 83 | bool cppcoro::cancellation_token::can_be_cancelled() const noexcept 84 | { 85 | return m_state != nullptr && m_state->can_be_cancelled(); 86 | } 87 | 88 | bool cppcoro::cancellation_token::is_cancellation_requested() const noexcept 89 | { 90 | return m_state != nullptr && m_state->is_cancellation_requested(); 91 | } 92 | 93 | void cppcoro::cancellation_token::throw_if_cancellation_requested() const 94 | { 95 | if (is_cancellation_requested()) 96 | { 97 | throw operation_cancelled{}; 98 | } 99 | } 100 | 101 | cppcoro::cancellation_token::cancellation_token(detail::cancellation_state* state) noexcept 102 | : m_state(state) 103 | { 104 | if (m_state != nullptr) 105 | { 106 | m_state->add_token_ref(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/file_read_operation.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #if CPPCORO_OS_WINNT 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # include 13 | 14 | bool cppcoro::file_read_operation_impl::try_start( 15 | cppcoro::detail::win32_overlapped_operation_base& operation) noexcept 16 | { 17 | const DWORD numberOfBytesToRead = 18 | m_byteCount <= 0xFFFFFFFF ? 19 | static_cast(m_byteCount) : DWORD(0xFFFFFFFF); 20 | 21 | DWORD numberOfBytesRead = 0; 22 | BOOL ok = ::ReadFile( 23 | m_fileHandle, 24 | m_buffer, 25 | numberOfBytesToRead, 26 | &numberOfBytesRead, 27 | operation.get_overlapped()); 28 | const DWORD errorCode = ok ? ERROR_SUCCESS : ::GetLastError(); 29 | if (errorCode != ERROR_IO_PENDING) 30 | { 31 | // Completed synchronously. 32 | // 33 | // We are assuming that the file-handle has been set to the 34 | // mode where synchronous completions do not post a completion 35 | // event to the I/O completion port and thus can return without 36 | // suspending here. 37 | 38 | operation.m_errorCode = errorCode; 39 | operation.m_numberOfBytesTransferred = numberOfBytesRead; 40 | 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | void cppcoro::file_read_operation_impl::cancel( 48 | cppcoro::detail::win32_overlapped_operation_base& operation) noexcept 49 | { 50 | (void)::CancelIoEx(m_fileHandle, operation.get_overlapped()); 51 | } 52 | 53 | #elif CPPCORO_OS_LINUX 54 | 55 | bool cppcoro::file_read_operation_impl::try_start( 56 | cppcoro::detail::io_operation_base& operation) noexcept 57 | { 58 | const size_t numberOfBytesToRead = 59 | m_byteCount <= std::numeric_limits::max() ? 60 | m_byteCount : std::numeric_limits::max(); 61 | return operation.m_ioQueue.transaction(operation.m_message) 62 | .read(m_fileHandle, m_buffer, numberOfBytesToRead, operation.m_offset) 63 | .commit(); 64 | } 65 | 66 | void cppcoro::file_read_operation_impl::cancel( 67 | cppcoro::detail::io_operation_base& operation) noexcept 68 | { 69 | operation.m_ioQueue.transaction(operation.m_message) 70 | .cancel().commit(); 71 | } 72 | 73 | #endif // CPPCORO_OS_WINNT 74 | -------------------------------------------------------------------------------- /lib/file_write_operation.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #if CPPCORO_OS_WINNT 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # include 13 | 14 | bool cppcoro::file_write_operation_impl::try_start( 15 | cppcoro::detail::win32_overlapped_operation_base& operation) noexcept 16 | { 17 | const DWORD numberOfBytesToWrite = 18 | m_byteCount <= 0xFFFFFFFF ? 19 | static_cast(m_byteCount) : DWORD(0xFFFFFFFF); 20 | 21 | DWORD numberOfBytesWritten = 0; 22 | BOOL ok = ::WriteFile( 23 | m_fileHandle, 24 | m_buffer, 25 | numberOfBytesToWrite, 26 | &numberOfBytesWritten, 27 | operation.get_overlapped()); 28 | const DWORD errorCode = ok ? ERROR_SUCCESS : ::GetLastError(); 29 | if (errorCode != ERROR_IO_PENDING) 30 | { 31 | // Completed synchronously. 32 | // 33 | // We are assuming that the file-handle has been set to the 34 | // mode where synchronous completions do not post a completion 35 | // event to the I/O completion port and thus can return without 36 | // suspending here. 37 | 38 | operation.m_errorCode = errorCode; 39 | operation.m_numberOfBytesTransferred = numberOfBytesWritten; 40 | 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | void cppcoro::file_write_operation_impl::cancel( 48 | cppcoro::detail::win32_overlapped_operation_base& operation) noexcept 49 | { 50 | (void)::CancelIoEx(m_fileHandle, operation.get_overlapped()); 51 | } 52 | 53 | #elif CPPCORO_OS_LINUX 54 | 55 | bool cppcoro::file_write_operation_impl::try_start( 56 | cppcoro::detail::io_operation_base& operation) noexcept 57 | { 58 | const size_t numberOfBytesToWrite = m_byteCount <= std::numeric_limits::max() 59 | ? m_byteCount 60 | : std::numeric_limits::max(); 61 | return operation.m_ioQueue.transaction(operation.m_message) 62 | .write(m_fileHandle, m_buffer, numberOfBytesToWrite, operation.m_offset) 63 | .commit(); 64 | } 65 | 66 | void cppcoro::file_write_operation_impl::cancel( 67 | cppcoro::detail::io_operation_base& operation) noexcept 68 | { 69 | operation.m_ioQueue.transaction(operation.m_message).cancel().commit(); 70 | } 71 | 72 | #endif // CPPCORO_OS_WINNT 73 | -------------------------------------------------------------------------------- /lib/ip_address.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | std::string cppcoro::net::ip_address::to_string() const 9 | { 10 | return is_ipv4() ? m_ipv4.to_string() : m_ipv6.to_string(); 11 | } 12 | 13 | std::optional 14 | cppcoro::net::ip_address::from_string(std::string_view string) noexcept 15 | { 16 | if (auto ipv4 = ipv4_address::from_string(string); ipv4) 17 | { 18 | return *ipv4; 19 | } 20 | 21 | if (auto ipv6 = ipv6_address::from_string(string); ipv6) 22 | { 23 | return *ipv6; 24 | } 25 | 26 | return std::nullopt; 27 | } 28 | -------------------------------------------------------------------------------- /lib/ip_endpoint.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | std::string cppcoro::net::ip_endpoint::to_string() const 9 | { 10 | return is_ipv4() ? m_ipv4.to_string() : m_ipv6.to_string(); 11 | } 12 | 13 | std::optional 14 | cppcoro::net::ip_endpoint::from_string(std::string_view string) noexcept 15 | { 16 | if (auto ipv4 = ipv4_endpoint::from_string(string); ipv4) 17 | { 18 | return *ipv4; 19 | } 20 | 21 | if (auto ipv6 = ipv6_endpoint::from_string(string); ipv6) 22 | { 23 | return *ipv6; 24 | } 25 | 26 | return std::nullopt; 27 | } 28 | -------------------------------------------------------------------------------- /lib/ipv4_endpoint.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Kt C++ Library 3 | // Copyright (c) 2015 Lewis Baker 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace 11 | { 12 | namespace local 13 | { 14 | bool is_digit(char c) 15 | { 16 | return c >= '0' && c <= '9'; 17 | } 18 | 19 | std::uint8_t digit_value(char c) 20 | { 21 | return static_cast(c - '0'); 22 | } 23 | 24 | std::optional parse_port(std::string_view string) 25 | { 26 | if (string.empty()) return std::nullopt; 27 | 28 | std::uint32_t value = 0; 29 | for (auto c : string) 30 | { 31 | if (!is_digit(c)) return std::nullopt; 32 | value = value * 10 + digit_value(c); 33 | if (value > 0xFFFFu) return std::nullopt; 34 | } 35 | 36 | return static_cast(value); 37 | } 38 | } 39 | } 40 | 41 | std::string cppcoro::net::ipv4_endpoint::to_string() const 42 | { 43 | auto s = m_address.to_string(); 44 | s.push_back(':'); 45 | s.append(std::to_string(m_port)); 46 | return s; 47 | } 48 | 49 | std::optional 50 | cppcoro::net::ipv4_endpoint::from_string(std::string_view string) noexcept 51 | { 52 | auto colonPos = string.find(':'); 53 | if (colonPos == std::string_view::npos) 54 | { 55 | return std::nullopt; 56 | } 57 | 58 | auto address = ipv4_address::from_string(string.substr(0, colonPos)); 59 | if (!address) 60 | { 61 | return std::nullopt; 62 | } 63 | 64 | auto port = local::parse_port(string.substr(colonPos + 1)); 65 | if (!port) 66 | { 67 | return std::nullopt; 68 | } 69 | 70 | return ipv4_endpoint{ *address, *port }; 71 | } 72 | -------------------------------------------------------------------------------- /lib/ipv6_endpoint.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Kt C++ Library 3 | // Copyright (c) 2015 Lewis Baker 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace 11 | { 12 | namespace local 13 | { 14 | bool is_digit(char c) 15 | { 16 | return c >= '0' && c <= '9'; 17 | } 18 | 19 | std::uint8_t digit_value(char c) 20 | { 21 | return static_cast(c - '0'); 22 | } 23 | 24 | std::optional parse_port(std::string_view string) 25 | { 26 | if (string.empty()) return std::nullopt; 27 | 28 | std::uint32_t value = 0; 29 | for (auto c : string) 30 | { 31 | if (!is_digit(c)) return std::nullopt; 32 | value = value * 10 + digit_value(c); 33 | if (value > 0xFFFFu) return std::nullopt; 34 | } 35 | 36 | return static_cast(value); 37 | } 38 | } 39 | } 40 | 41 | std::string cppcoro::net::ipv6_endpoint::to_string() const 42 | { 43 | std::string result; 44 | result.push_back('['); 45 | result += m_address.to_string(); 46 | result += "]:"; 47 | result += std::to_string(m_port); 48 | return result; 49 | } 50 | 51 | std::optional 52 | cppcoro::net::ipv6_endpoint::from_string(std::string_view string) noexcept 53 | { 54 | // Shortest valid endpoint is "[::]:0" 55 | if (string.size() < 6) 56 | { 57 | return std::nullopt; 58 | } 59 | 60 | if (string[0] != '[') 61 | { 62 | return std::nullopt; 63 | } 64 | 65 | auto closeBracketPos = string.find("]:", 1); 66 | if (closeBracketPos == std::string_view::npos) 67 | { 68 | return std::nullopt; 69 | } 70 | 71 | auto address = ipv6_address::from_string(string.substr(1, closeBracketPos - 1)); 72 | if (!address) 73 | { 74 | return std::nullopt; 75 | } 76 | 77 | auto port = local::parse_port(string.substr(closeBracketPos + 2)); 78 | if (!port) 79 | { 80 | return std::nullopt; 81 | } 82 | 83 | return ipv6_endpoint{ *address, *port }; 84 | } 85 | -------------------------------------------------------------------------------- /lib/linux.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Microsoft 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | namespace cppcoro::detail::lnx 10 | { 11 | void safe_fd::close() noexcept 12 | { 13 | if (m_fd != -1) 14 | { 15 | ::close(m_fd); 16 | m_fd = -1; 17 | } 18 | } 19 | } // namespace cppcoro::detail::lnx 20 | -------------------------------------------------------------------------------- /lib/read_only_file.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #if CPPCORO_OS_WINNT 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # include 13 | #elif CPPCORO_OS_LINUX 14 | #define GENERIC_READ (S_IRUSR | S_IRGRP | S_IROTH) 15 | #endif 16 | 17 | cppcoro::read_only_file cppcoro::read_only_file::open( 18 | io_service& ioService, 19 | const filesystem::path& path, 20 | file_share_mode shareMode, 21 | file_buffering_mode bufferingMode) 22 | { 23 | read_only_file file(file::open( 24 | GENERIC_READ, 25 | ioService, 26 | path, 27 | file_open_mode::open_existing, 28 | shareMode, 29 | bufferingMode)); 30 | #if CPPCORO_OS_LINUX 31 | file.m_ioService = &ioService; 32 | #endif 33 | return std::move(file); 34 | } 35 | 36 | cppcoro::read_only_file::read_only_file( 37 | detail::safe_handle&& fileHandle) noexcept 38 | : file(std::move(fileHandle)) 39 | , readable_file(detail::safe_handle{}) 40 | { 41 | } 42 | -------------------------------------------------------------------------------- /lib/read_write_file.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #if CPPCORO_OS_WINNT 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # include 13 | #else 14 | #define GENERIC_READ (S_IRUSR | S_IRGRP | S_IROTH) 15 | #define GENERIC_WRITE (S_IWUSR | S_IWGRP /* | S_IWOTH */) 16 | #endif 17 | 18 | cppcoro::read_write_file cppcoro::read_write_file::open( 19 | io_service& ioService, 20 | const filesystem::path& path, 21 | file_open_mode openMode, 22 | file_share_mode shareMode, 23 | file_buffering_mode bufferingMode) 24 | { 25 | auto file = read_write_file(file::open( 26 | GENERIC_READ | GENERIC_WRITE, 27 | ioService, 28 | path, 29 | openMode, 30 | shareMode, 31 | bufferingMode)); 32 | #if CPPCORO_OS_LINUX 33 | file.m_ioService = &ioService; 34 | #endif 35 | return std::move(file); 36 | } 37 | 38 | cppcoro::read_write_file::read_write_file( 39 | detail::safe_handle&& fileHandle) noexcept 40 | : file(std::move(fileHandle)) 41 | , readable_file(detail::safe_handle{}) 42 | , writable_file(detail::safe_handle{}) 43 | { 44 | } 45 | -------------------------------------------------------------------------------- /lib/readable_file.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | cppcoro::file_read_operation cppcoro::readable_file::read( 9 | std::uint64_t offset, 10 | void* buffer, 11 | std::size_t byteCount) const noexcept 12 | { 13 | return file_read_operation( 14 | #if CPPCORO_OS_LINUX 15 | *m_ioService, 16 | #endif 17 | m_fileHandle.handle(), 18 | offset, 19 | buffer, 20 | byteCount); 21 | } 22 | 23 | cppcoro::file_read_operation_cancellable cppcoro::readable_file::read( 24 | std::uint64_t offset, 25 | void* buffer, 26 | std::size_t byteCount, 27 | cancellation_token ct) const noexcept 28 | { 29 | return file_read_operation_cancellable( 30 | #if CPPCORO_OS_LINUX 31 | *m_ioService, 32 | #endif 33 | m_fileHandle.handle(), 34 | offset, 35 | buffer, 36 | byteCount, 37 | std::move(ct)); 38 | } 39 | -------------------------------------------------------------------------------- /lib/socket_helpers.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "socket_helpers.hpp" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #if CPPCORO_OS_WINNT 14 | #include 15 | #include 16 | #include 17 | #include 18 | #endif 19 | 20 | 21 | cppcoro::net::ip_endpoint 22 | cppcoro::net::detail::sockaddr_to_ip_endpoint(const sockaddr& address) noexcept 23 | { 24 | if (address.sa_family == AF_INET) 25 | { 26 | SOCKADDR_IN ipv4Address; 27 | std::memcpy(&ipv4Address, &address, sizeof(ipv4Address)); 28 | 29 | std::uint8_t addressBytes[4]; 30 | std::memcpy(addressBytes, &ipv4Address.sin_addr, 4); 31 | 32 | return ipv4_endpoint{ 33 | ipv4_address{ addressBytes }, 34 | ntohs(ipv4Address.sin_port) 35 | }; 36 | } 37 | else 38 | { 39 | assert(address.sa_family == AF_INET6); 40 | 41 | SOCKADDR_IN6 ipv6Address; 42 | std::memcpy(&ipv6Address, &address, sizeof(ipv6Address)); 43 | 44 | return ipv6_endpoint{ 45 | ipv6_address{ ipv6Address.sin6_addr.s6_addr }, 46 | ntohs(ipv6Address.sin6_port) 47 | }; 48 | } 49 | } 50 | 51 | int cppcoro::net::detail::ip_endpoint_to_sockaddr( 52 | const ip_endpoint& endPoint, 53 | std::reference_wrapper address) noexcept 54 | { 55 | if (endPoint.is_ipv4()) 56 | { 57 | const auto& ipv4EndPoint = endPoint.to_ipv4(); 58 | 59 | SOCKADDR_IN ipv4Address; 60 | ipv4Address.sin_family = AF_INET; 61 | std::memcpy(&ipv4Address.sin_addr, ipv4EndPoint.address().bytes(), 4); 62 | ipv4Address.sin_port = htons(ipv4EndPoint.port()); 63 | std::memset(&ipv4Address.sin_zero, 0, sizeof(ipv4Address.sin_zero)); 64 | 65 | std::memcpy(&address.get(), &ipv4Address, sizeof(ipv4Address)); 66 | 67 | return sizeof(SOCKADDR_IN); 68 | } 69 | else 70 | { 71 | const auto& ipv6EndPoint = endPoint.to_ipv6(); 72 | 73 | SOCKADDR_IN6 ipv6Address; 74 | ipv6Address.sin6_family = AF_INET6; 75 | std::memcpy(&ipv6Address.sin6_addr, ipv6EndPoint.address().bytes(), 16); 76 | ipv6Address.sin6_port = htons(ipv6EndPoint.port()); 77 | ipv6Address.sin6_flowinfo = 0; 78 | #if CPPCORO_OS_WINNT 79 | ipv6Address.sin6_scope_struct = SCOPEID_UNSPECIFIED_INIT; 80 | #else 81 | ipv6Address.sin6_scope_id = 0; 82 | #endif 83 | 84 | std::memcpy(&address.get(), &ipv6Address, sizeof(ipv6Address)); 85 | 86 | return sizeof(SOCKADDR_IN6); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/socket_helpers.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_PRIVATE_SOCKET_HELPERS_HPP_INCLUDED 6 | #define CPPCORO_PRIVATE_SOCKET_HELPERS_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #if CPPCORO_OS_WINNT 11 | # include 12 | struct sockaddr; 13 | struct sockaddr_storage; 14 | #else 15 | # include 16 | # define SD_RECEIVE SHUT_RD 17 | # define SD_SEND SHUT_WR 18 | # define INVALID_SOCKET -1 19 | # define SOCKET_ERROR -1 20 | # define SOCKADDR_STORAGE struct sockaddr_storage 21 | # define SOCKADDR struct sockaddr 22 | # define SOCKADDR_IN struct sockaddr_in 23 | # define SOCKADDR_IN6 struct sockaddr_in6 24 | # define closesocket(__handle) close((__handle)) 25 | # include 26 | #endif 27 | 28 | namespace cppcoro 29 | { 30 | namespace net 31 | { 32 | class ip_endpoint; 33 | 34 | namespace detail 35 | { 36 | /// Convert a sockaddr to an IP endpoint. 37 | ip_endpoint sockaddr_to_ip_endpoint(const sockaddr& address) noexcept; 38 | 39 | /// Converts an ip_endpoint to a sockaddr structure. 40 | /// 41 | /// \param endPoint 42 | /// The IP endpoint to convert to a sockaddr structure. 43 | /// 44 | /// \param address 45 | /// The sockaddr structure to populate. 46 | /// 47 | /// \return 48 | /// The length of the sockaddr structure that was populated. 49 | int ip_endpoint_to_sockaddr( 50 | const ip_endpoint& endPoint, 51 | std::reference_wrapper address) noexcept; 52 | } 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /lib/socket_recv_operation.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #if CPPCORO_OS_WINNT 10 | #include 11 | #include 12 | 13 | bool cppcoro::net::socket_recv_operation_impl::try_start( 14 | cppcoro::detail::io_operation_base& operation) noexcept 15 | { 16 | // Need to read this flag before starting the operation, otherwise 17 | // it may be possible that the operation will complete immediately 18 | // on another thread and then destroy the socket before we get a 19 | // chance to read it. 20 | const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success(); 21 | 22 | DWORD numberOfBytesReceived = 0; 23 | DWORD flags = 0; 24 | int result = ::WSARecv( 25 | m_socket.native_handle(), 26 | reinterpret_cast(&m_buffer), 27 | 1, // buffer count 28 | &numberOfBytesReceived, 29 | &flags, 30 | operation.get_overlapped(), 31 | nullptr); 32 | if (result == SOCKET_ERROR) 33 | { 34 | int errorCode = ::WSAGetLastError(); 35 | if (errorCode != WSA_IO_PENDING) 36 | { 37 | // Failed synchronously. 38 | operation.m_errorCode = static_cast(errorCode); 39 | operation.m_numberOfBytesTransferred = numberOfBytesReceived; 40 | return false; 41 | } 42 | } 43 | else if (skipCompletionOnSuccess) 44 | { 45 | // Completed synchronously, no completion event will be posted to the IOCP. 46 | operation.m_errorCode = ERROR_SUCCESS; 47 | operation.m_numberOfBytesTransferred = numberOfBytesReceived; 48 | return false; 49 | } 50 | 51 | // Operation will complete asynchronously. 52 | return true; 53 | } 54 | 55 | void cppcoro::net::socket_recv_operation_impl::cancel( 56 | cppcoro::detail::io_operation_base& operation) noexcept 57 | { 58 | (void)::CancelIoEx( 59 | reinterpret_cast(m_socket.native_handle()), operation.get_overlapped()); 60 | } 61 | 62 | #else 63 | 64 | bool cppcoro::net::socket_recv_operation_impl::try_start( 65 | cppcoro::detail::io_operation_base& operation) noexcept 66 | { 67 | return operation.m_ioQueue.transaction(operation.m_message) 68 | .recv(m_socket.native_handle(), m_buffer.buffer, m_buffer.size, m_socket.m_recvFlags) 69 | .commit(); 70 | } 71 | 72 | void cppcoro::net::socket_recv_operation_impl::cancel( 73 | cppcoro::detail::io_operation_base& operation) noexcept 74 | { 75 | operation.m_ioQueue.transaction(operation.m_message) 76 | .cancel() 77 | .commit(); 78 | } 79 | 80 | std::size_t 81 | cppcoro::net::socket_recv_operation_impl::get_result(cppcoro::detail::io_operation_base& operation) 82 | { 83 | auto size = operation.get_result(); 84 | if (size > m_buffer.size) 85 | { 86 | throw std::system_error{ 87 | EAGAIN, std::generic_category() // TODO is EAGAIN the good choice here ? 88 | }; 89 | } 90 | return size; 91 | } 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /lib/socket_send_operation.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #if CPPCORO_OS_WINNT 10 | #include 11 | #include 12 | 13 | bool cppcoro::net::socket_send_operation_impl::try_start( 14 | cppcoro::detail::io_operation_base& operation) noexcept 15 | { 16 | // Need to read this flag before starting the operation, otherwise 17 | // it may be possible that the operation will complete immediately 18 | // on another thread and then destroy the socket before we get a 19 | // chance to read it. 20 | const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success(); 21 | 22 | DWORD numberOfBytesSent = 0; 23 | int result = ::WSASend( 24 | m_socket.native_handle(), 25 | reinterpret_cast(&m_buffer), 26 | 1, // buffer count 27 | &numberOfBytesSent, 28 | 0, // flags 29 | operation.get_overlapped(), 30 | nullptr); 31 | if (result == SOCKET_ERROR) 32 | { 33 | int errorCode = ::WSAGetLastError(); 34 | if (errorCode != WSA_IO_PENDING) 35 | { 36 | // Failed synchronously. 37 | operation.m_errorCode = static_cast(errorCode); 38 | operation.m_numberOfBytesTransferred = numberOfBytesSent; 39 | return false; 40 | } 41 | } 42 | else if (skipCompletionOnSuccess) 43 | { 44 | // Completed synchronously, no completion event will be posted to the IOCP. 45 | operation.m_errorCode = ERROR_SUCCESS; 46 | operation.m_numberOfBytesTransferred = numberOfBytesSent; 47 | return false; 48 | } 49 | 50 | // Operation will complete asynchronously. 51 | return true; 52 | } 53 | 54 | void cppcoro::net::socket_send_operation_impl::cancel( 55 | cppcoro::detail::io_operation_base& operation) noexcept 56 | { 57 | (void)::CancelIoEx( 58 | reinterpret_cast(m_socket.native_handle()), 59 | operation.get_overlapped()); 60 | } 61 | 62 | #else 63 | 64 | bool cppcoro::net::socket_send_operation_impl::try_start( 65 | cppcoro::detail::io_operation_base& operation) noexcept 66 | { 67 | return operation.m_ioQueue.transaction(operation.m_message) 68 | .send(m_socket.native_handle(), m_buffer.buffer, m_buffer.size) 69 | .commit(); 70 | } 71 | 72 | void cppcoro::net::socket_send_operation_impl::cancel( 73 | cppcoro::detail::io_operation_base& operation) noexcept 74 | { 75 | operation.m_ioQueue.transaction(operation.m_message) 76 | .cancel() 77 | .commit(); 78 | } 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /lib/socket_send_to_operation.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #include "socket_helpers.hpp" 10 | 11 | #if CPPCORO_OS_WINNT 12 | #include 13 | #include 14 | 15 | bool cppcoro::net::socket_send_to_operation_impl::try_start( 16 | cppcoro::detail::io_operation_base& operation) noexcept 17 | { 18 | // Need to read this flag before starting the operation, otherwise 19 | // it may be possible that the operation will complete immediately 20 | // on another thread and then destroy the socket before we get a 21 | // chance to read it. 22 | const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success(); 23 | 24 | SOCKADDR_STORAGE destinationAddress; 25 | const int destinationLength = 26 | detail::ip_endpoint_to_sockaddr(m_destination, std::ref(destinationAddress)); 27 | 28 | DWORD numberOfBytesSent = 0; 29 | int result = ::WSASendTo( 30 | m_socket.native_handle(), 31 | reinterpret_cast(&m_buffer), 32 | 1, // buffer count 33 | &numberOfBytesSent, 34 | 0, // flags 35 | reinterpret_cast(&destinationAddress), 36 | destinationLength, 37 | operation.get_overlapped(), 38 | nullptr); 39 | if (result == SOCKET_ERROR) 40 | { 41 | int errorCode = ::WSAGetLastError(); 42 | if (errorCode != WSA_IO_PENDING) 43 | { 44 | // Failed synchronously. 45 | operation.m_errorCode = static_cast(errorCode); 46 | operation.m_numberOfBytesTransferred = numberOfBytesSent; 47 | return false; 48 | } 49 | } 50 | else if (skipCompletionOnSuccess) 51 | { 52 | // Completed synchronously, no completion event will be posted to the IOCP. 53 | operation.m_errorCode = ERROR_SUCCESS; 54 | operation.m_numberOfBytesTransferred = numberOfBytesSent; 55 | return false; 56 | } 57 | 58 | // Operation will complete asynchronously. 59 | return true; 60 | } 61 | 62 | void cppcoro::net::socket_send_to_operation_impl::cancel( 63 | cppcoro::detail::io_operation_base& operation) noexcept 64 | { 65 | (void)::CancelIoEx( 66 | reinterpret_cast(m_socket.native_handle()), operation.get_overlapped()); 67 | } 68 | #elif CPPCORO_OS_LINUX 69 | bool cppcoro::net::socket_send_to_operation_impl::try_start( 70 | cppcoro::detail::io_operation_base& operation) noexcept 71 | { 72 | const int destinationLength = 73 | detail::ip_endpoint_to_sockaddr(m_destination, std::ref(m_destinationStorage)); 74 | 75 | m_vec.iov_base = m_buffer.buffer; 76 | m_vec.iov_len = m_buffer.size; 77 | std::memset(&m_msgHdr, 0, sizeof(m_msgHdr)); 78 | m_msgHdr.msg_name = &m_destinationStorage; 79 | m_msgHdr.msg_namelen = destinationLength; 80 | m_msgHdr.msg_iov = &m_vec; 81 | m_msgHdr.msg_iovlen = 1; 82 | return operation.m_ioQueue.transaction(operation.m_message) 83 | .sendmsg(m_socket.native_handle(), &m_msgHdr) 84 | .commit(); 85 | } 86 | 87 | void cppcoro::net::socket_send_to_operation_impl::cancel( 88 | cppcoro::detail::io_operation_base& operation) noexcept 89 | { 90 | operation.m_ioQueue.transaction(operation.m_message) 91 | .cancel().commit(); 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /lib/spin_mutex.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "spin_mutex.hpp" 7 | #include "spin_wait.hpp" 8 | 9 | namespace cppcoro 10 | { 11 | spin_mutex::spin_mutex() noexcept 12 | : m_isLocked(false) 13 | { 14 | } 15 | 16 | bool spin_mutex::try_lock() noexcept 17 | { 18 | return !m_isLocked.exchange(true, std::memory_order_acquire); 19 | } 20 | 21 | void spin_mutex::lock() noexcept 22 | { 23 | spin_wait wait; 24 | while (!try_lock()) 25 | { 26 | while (m_isLocked.load(std::memory_order_relaxed)) 27 | { 28 | wait.spin_one(); 29 | } 30 | } 31 | } 32 | 33 | void spin_mutex::unlock() noexcept 34 | { 35 | m_isLocked.store(false, std::memory_order_release); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/spin_mutex.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SPIN_MUTEX_HPP_INCLUDED 6 | #define CPPCORO_SPIN_MUTEX_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | class spin_mutex 13 | { 14 | public: 15 | 16 | /// Initialise the mutex to the unlocked state. 17 | spin_mutex() noexcept; 18 | 19 | /// Attempt to lock the mutex without blocking 20 | /// 21 | /// \return 22 | /// true if the lock was acquired, false if the lock was already held 23 | /// and could not be immediately acquired. 24 | bool try_lock() noexcept; 25 | 26 | /// Block the current thread until the lock is acquired. 27 | /// 28 | /// This will busy-wait until it acquires the lock. 29 | /// 30 | /// This has 'acquire' memory semantics and synchronises 31 | /// with prior calls to unlock(). 32 | void lock() noexcept; 33 | 34 | /// Release the lock. 35 | /// 36 | /// This has 'release' memory semantics and synchronises with 37 | /// lock() and try_lock(). 38 | void unlock() noexcept; 39 | 40 | private: 41 | 42 | std::atomic m_isLocked; 43 | 44 | }; 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /lib/spin_wait.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "spin_wait.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #if CPPCORO_OS_WINNT 12 | # define WIN32_LEAN_AND_MEAN 13 | # include 14 | #endif 15 | 16 | namespace 17 | { 18 | namespace local 19 | { 20 | constexpr std::uint32_t yield_threshold = 10; 21 | } 22 | } 23 | 24 | namespace cppcoro 25 | { 26 | spin_wait::spin_wait() noexcept 27 | { 28 | reset(); 29 | } 30 | 31 | bool spin_wait::next_spin_will_yield() const noexcept 32 | { 33 | return m_count >= local::yield_threshold; 34 | } 35 | 36 | void spin_wait::reset() noexcept 37 | { 38 | static const std::uint32_t initialCount = 39 | std::thread::hardware_concurrency() > 1 ? 0 : local::yield_threshold; 40 | m_count = initialCount; 41 | } 42 | 43 | void spin_wait::spin_one() noexcept 44 | { 45 | #if CPPCORO_OS_WINNT 46 | // Spin strategy taken from .NET System.SpinWait class. 47 | // I assume the Microsoft developers knew what they're doing. 48 | if (!next_spin_will_yield()) 49 | { 50 | // CPU-level pause 51 | // Allow other hyper-threads to run while we busy-wait. 52 | 53 | // Make each busy-spin exponentially longer 54 | const std::uint32_t loopCount = 2u << m_count; 55 | for (std::uint32_t i = 0; i < loopCount; ++i) 56 | { 57 | ::YieldProcessor(); 58 | ::YieldProcessor(); 59 | } 60 | } 61 | else 62 | { 63 | // We've already spun a number of iterations. 64 | // 65 | const auto yieldCount = m_count - local::yield_threshold; 66 | if (yieldCount % 20 == 19) 67 | { 68 | // Yield remainder of time slice to another thread and 69 | // don't schedule this thread for a little while. 70 | ::SleepEx(1, FALSE); 71 | } 72 | else if (yieldCount % 5 == 4) 73 | { 74 | // Yield remainder of time slice to another thread 75 | // that is ready to run (possibly from another processor?). 76 | ::SleepEx(0, FALSE); 77 | } 78 | else 79 | { 80 | // Yield to another thread that is ready to run on the 81 | // current processor. 82 | ::SwitchToThread(); 83 | } 84 | } 85 | #else 86 | if (next_spin_will_yield()) 87 | { 88 | std::this_thread::yield(); 89 | } 90 | #endif 91 | 92 | ++m_count; 93 | if (m_count == 0) 94 | { 95 | // Don't wrap around to zero as this would go back to 96 | // busy-waiting. 97 | m_count = local::yield_threshold; 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /lib/spin_wait.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SPIN_WAIT_HPP_INCLUDED 6 | #define CPPCORO_SPIN_WAIT_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace cppcoro 11 | { 12 | class spin_wait 13 | { 14 | public: 15 | 16 | spin_wait() noexcept; 17 | 18 | bool next_spin_will_yield() const noexcept; 19 | 20 | void spin_one() noexcept; 21 | 22 | void reset() noexcept; 23 | 24 | private: 25 | 26 | std::uint32_t m_count; 27 | 28 | }; 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /lib/use.cake: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) Lewis Baker 3 | # Licenced under MIT license. See LICENSE.txt for details. 4 | ############################################################################### 5 | 6 | import cake.path 7 | 8 | from cake.tools import script, env, compiler, variant 9 | 10 | compiler.addIncludePath(env.expand('${CPPCORO}/include')) 11 | 12 | buildScript = script.get(script.cwd('build.cake')) 13 | compiler.addLibrary(buildScript.getResult('library')) 14 | 15 | if variant.platform == "windows": 16 | compiler.addLibrary("Synchronization") 17 | compiler.addLibrary("kernel32") 18 | compiler.addLibrary("WS2_32") 19 | compiler.addLibrary("Mswsock") 20 | 21 | -------------------------------------------------------------------------------- /lib/win32.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #ifndef WIN32_LEAN_AND_MEAN 9 | # define WIN32_LEAN_AND_MEAN 10 | #endif 11 | #include 12 | 13 | void cppcoro::detail::win32::safe_handle::close() noexcept 14 | { 15 | if (m_handle != nullptr && m_handle != INVALID_HANDLE_VALUE) 16 | { 17 | ::CloseHandle(m_handle); 18 | m_handle = nullptr; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/writable_file.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | #if CPPCORO_OS_WINNT 11 | # ifndef WIN32_LEAN_AND_MEAN 12 | # define WIN32_LEAN_AND_MEAN 13 | # endif 14 | # include 15 | 16 | void cppcoro::writable_file::set_size( 17 | std::uint64_t fileSize) 18 | { 19 | LARGE_INTEGER position; 20 | position.QuadPart = fileSize; 21 | 22 | BOOL ok = ::SetFilePointerEx(m_fileHandle.handle(), position, nullptr, FILE_BEGIN); 23 | if (!ok) 24 | { 25 | DWORD errorCode = ::GetLastError(); 26 | throw std::system_error 27 | { 28 | static_cast(errorCode), 29 | std::system_category(), 30 | "error setting file size: SetFilePointerEx" 31 | }; 32 | } 33 | 34 | ok = ::SetEndOfFile(m_fileHandle.handle()); 35 | if (!ok) 36 | { 37 | DWORD errorCode = ::GetLastError(); 38 | throw std::system_error 39 | { 40 | static_cast(errorCode), 41 | std::system_category(), 42 | "error setting file size: SetEndOfFile" 43 | }; 44 | } 45 | } 46 | #else 47 | 48 | void cppcoro::writable_file::set_size( 49 | std::uint64_t fileSize) 50 | { 51 | if (ftruncate(m_fileHandle.fd(), fileSize) < 0) { 52 | throw std::system_error 53 | { 54 | static_cast(errno), 55 | std::system_category(), 56 | "error setting file size: ftruncate" 57 | }; 58 | } 59 | } 60 | 61 | #endif 62 | 63 | cppcoro::file_write_operation cppcoro::writable_file::write( 64 | std::uint64_t offset, 65 | const void* buffer, 66 | std::size_t byteCount) noexcept 67 | { 68 | return file_write_operation{ 69 | #if CPPCORO_OS_LINUX 70 | *m_ioService, 71 | #endif 72 | m_fileHandle.handle(), 73 | offset, 74 | buffer, 75 | byteCount 76 | }; 77 | } 78 | 79 | cppcoro::file_write_operation_cancellable cppcoro::writable_file::write( 80 | std::uint64_t offset, 81 | const void* buffer, 82 | std::size_t byteCount, 83 | cancellation_token ct) noexcept 84 | { 85 | return file_write_operation_cancellable{ 86 | #if CPPCORO_OS_LINUX 87 | *m_ioService, 88 | #endif 89 | m_fileHandle.handle(), 90 | offset, 91 | buffer, 92 | byteCount, 93 | std::move(ct) 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /lib/write_only_file.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #if CPPCORO_OS_WINNT 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # include 13 | #elif CPPCORO_OS_LINUX 14 | #define GENERIC_WRITE (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) 15 | #endif 16 | 17 | cppcoro::write_only_file cppcoro::write_only_file::open( 18 | io_service& ioService, 19 | const filesystem::path& path, 20 | file_open_mode openMode, 21 | file_share_mode shareMode, 22 | file_buffering_mode bufferingMode) 23 | { 24 | auto file = write_only_file(file::open( 25 | GENERIC_WRITE, 26 | ioService, 27 | path, 28 | openMode, 29 | shareMode, 30 | bufferingMode)); 31 | #if CPPCORO_OS_LINUX 32 | file.m_ioService = &ioService; 33 | #endif 34 | return std::move(file); 35 | } 36 | 37 | cppcoro::write_only_file::write_only_file( 38 | detail::safe_handle&& fileHandle) noexcept 39 | : file(std::move(fileHandle)) 40 | , writable_file(detail::safe_handle{}) 41 | { 42 | } 43 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(doctest::doctest INTERFACE IMPORTED) 2 | target_include_directories(doctest::doctest INTERFACE doctest) 3 | 4 | include(${CMAKE_CURRENT_LIST_DIR}/doctest/doctest.cmake) 5 | 6 | find_package(Threads REQUIRED) 7 | 8 | option(CPPCORO_TESTS_LIMITED_RESOURCES "Use limited-resources testing parameters" OFF) 9 | if(CPPCORO_TESTS_LIMITED_RESOURCES) 10 | add_definitions(-DCPPCORO_TESTS_LIMITED_RESOURCES=1) 11 | endif() 12 | 13 | add_library(tests-main STATIC 14 | main.cpp 15 | counted.cpp 16 | ) 17 | target_link_libraries(tests-main PUBLIC cppcoro doctest::doctest Threads::Threads) 18 | if(WIN32) 19 | target_compile_definitions(tests-main PUBLIC 20 | DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL=1 21 | DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN=1) 22 | endif() 23 | 24 | set(tests 25 | generator_tests.cpp 26 | recursive_generator_tests.cpp 27 | async_generator_tests.cpp 28 | async_auto_reset_event_tests.cpp 29 | async_manual_reset_event_tests.cpp 30 | async_mutex_tests.cpp 31 | async_latch_tests.cpp 32 | cancellation_token_tests.cpp 33 | task_tests.cpp 34 | sequence_barrier_tests.cpp 35 | shared_task_tests.cpp 36 | sync_wait_tests.cpp 37 | single_consumer_async_auto_reset_event_tests.cpp 38 | single_producer_sequencer_tests.cpp 39 | multi_producer_sequencer_tests.cpp 40 | when_all_tests.cpp 41 | when_all_ready_tests.cpp 42 | ip_address_tests.cpp 43 | ip_endpoint_tests.cpp 44 | ipv4_address_tests.cpp 45 | ipv4_endpoint_tests.cpp 46 | ipv6_address_tests.cpp 47 | ipv6_endpoint_tests.cpp 48 | static_thread_pool_tests.cpp 49 | ) 50 | 51 | if(WIN32) 52 | list(APPEND tests 53 | scheduling_operator_tests.cpp 54 | io_service_tests.cpp 55 | file_tests.cpp 56 | socket_tests.cpp 57 | ) 58 | else() 59 | # assuming linux ! 60 | 61 | if(CPPCORO_USE_IO_RING) 62 | list(APPEND tests 63 | file_tests.cpp 64 | io_service_tests.cpp 65 | socket_tests.cpp 66 | ) 67 | endif() 68 | # let more time for some tests 69 | set(async_auto_reset_event_tests_TIMEOUT 60) 70 | endif() 71 | 72 | foreach(test ${tests}) 73 | get_filename_component(test_name ${test} NAME_WE) 74 | add_executable(${test_name} ${test}) 75 | target_link_libraries(${test_name} PRIVATE tests-main) 76 | string(REPLACE "_" " " test_prefix ${test_name}) 77 | if (NOT DEFINED ${test_name}_TIMEOUT) 78 | set(${test_name}_TIMEOUT 30) 79 | endif() 80 | doctest_discover_tests(${test_name} TEST_PREFIX ${test_prefix}- PROPERTIES TIMEOUT ${${test_name}_TIMEOUT}) 81 | endforeach() 82 | -------------------------------------------------------------------------------- /test/async_auto_reset_event_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include "doctest/cppcoro_doctest.h" 22 | 23 | TEST_SUITE_BEGIN("async_auto_reset_event"); 24 | 25 | TEST_CASE("single waiter") 26 | { 27 | cppcoro::async_auto_reset_event event; 28 | 29 | bool started = false; 30 | bool finished = false; 31 | auto run = [&]() -> cppcoro::task<> 32 | { 33 | started = true; 34 | co_await event; 35 | finished = true; 36 | }; 37 | 38 | auto check = [&]() -> cppcoro::task<> 39 | { 40 | CHECK(started); 41 | CHECK(!finished); 42 | 43 | event.set(); 44 | 45 | CHECK(finished); 46 | 47 | co_return; 48 | }; 49 | 50 | cppcoro::sync_wait(cppcoro::when_all_ready(run(), check())); 51 | } 52 | 53 | TEST_CASE("multiple waiters") 54 | { 55 | cppcoro::async_auto_reset_event event; 56 | 57 | 58 | auto run = [&](bool& flag) -> cppcoro::task<> 59 | { 60 | co_await event; 61 | flag = true; 62 | }; 63 | 64 | bool completed1 = false; 65 | bool completed2 = false; 66 | 67 | auto check = [&]() -> cppcoro::task<> 68 | { 69 | CHECK(!completed1); 70 | CHECK(!completed2); 71 | 72 | event.set(); 73 | 74 | CHECK(completed1); 75 | CHECK(!completed2); 76 | 77 | event.set(); 78 | 79 | CHECK(completed2); 80 | 81 | co_return; 82 | }; 83 | 84 | cppcoro::sync_wait(cppcoro::when_all_ready( 85 | run(completed1), 86 | run(completed2), 87 | check())); 88 | } 89 | 90 | TEST_CASE("multi-threaded") 91 | { 92 | cppcoro::static_thread_pool tp{ 3 }; 93 | 94 | auto run = [&]() -> cppcoro::task<> 95 | { 96 | cppcoro::async_auto_reset_event event; 97 | 98 | int value = 0; 99 | 100 | auto startWaiter = [&]() -> cppcoro::task<> 101 | { 102 | co_await tp.schedule(); 103 | co_await event; 104 | ++value; 105 | event.set(); 106 | }; 107 | 108 | auto startSignaller = [&]() -> cppcoro::task<> 109 | { 110 | co_await tp.schedule(); 111 | value = 5; 112 | event.set(); 113 | }; 114 | 115 | std::vector> tasks; 116 | 117 | tasks.emplace_back(startSignaller()); 118 | 119 | for (int i = 0; i < 1000; ++i) 120 | { 121 | tasks.emplace_back(startWaiter()); 122 | } 123 | 124 | co_await cppcoro::when_all(std::move(tasks)); 125 | 126 | // NOTE: Can't use CHECK() here because it's not thread-safe 127 | assert(value == 1005); 128 | }; 129 | 130 | std::vector> tasks; 131 | 132 | for (int i = 0; i < 1000; ++i) 133 | { 134 | tasks.emplace_back(run()); 135 | } 136 | 137 | cppcoro::sync_wait(cppcoro::when_all(std::move(tasks))); 138 | } 139 | 140 | TEST_SUITE_END(); 141 | -------------------------------------------------------------------------------- /test/async_latch_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include "doctest/cppcoro_doctest.h" 14 | 15 | TEST_SUITE_BEGIN("async_latch"); 16 | 17 | using namespace cppcoro; 18 | 19 | TEST_CASE("latch constructed with zero count is initially ready") 20 | { 21 | async_latch latch(0); 22 | CHECK(latch.is_ready()); 23 | } 24 | 25 | TEST_CASE("latch constructed with negative count is initially ready") 26 | { 27 | async_latch latch(-3); 28 | CHECK(latch.is_ready()); 29 | } 30 | 31 | TEST_CASE("count_down and is_ready") 32 | { 33 | async_latch latch(3); 34 | CHECK(!latch.is_ready()); 35 | latch.count_down(); 36 | CHECK(!latch.is_ready()); 37 | latch.count_down(); 38 | CHECK(!latch.is_ready()); 39 | latch.count_down(); 40 | CHECK(latch.is_ready()); 41 | } 42 | 43 | TEST_CASE("count_down by n") 44 | { 45 | async_latch latch(5); 46 | latch.count_down(3); 47 | CHECK(!latch.is_ready()); 48 | latch.count_down(2); 49 | CHECK(latch.is_ready()); 50 | } 51 | 52 | TEST_CASE("single awaiter") 53 | { 54 | async_latch latch(2); 55 | bool after = false; 56 | sync_wait(when_all_ready( 57 | [&]() -> task<> 58 | { 59 | co_await latch; 60 | after = true; 61 | }(), 62 | [&]() -> task<> 63 | { 64 | CHECK(!after); 65 | latch.count_down(); 66 | CHECK(!after); 67 | latch.count_down(); 68 | CHECK(after); 69 | co_return; 70 | }() 71 | )); 72 | } 73 | 74 | TEST_CASE("multiple awaiters") 75 | { 76 | async_latch latch(2); 77 | bool after1 = false; 78 | bool after2 = false; 79 | bool after3 = false; 80 | sync_wait(when_all_ready( 81 | [&]() -> task<> 82 | { 83 | co_await latch; 84 | after1 = true; 85 | }(), 86 | [&]() -> task<> 87 | { 88 | co_await latch; 89 | after2 = true; 90 | }(), 91 | [&]() -> task<> 92 | { 93 | co_await latch; 94 | after3 = true; 95 | }(), 96 | [&]() -> task<> 97 | { 98 | CHECK(!after1); 99 | CHECK(!after2); 100 | CHECK(!after3); 101 | latch.count_down(); 102 | CHECK(!after1); 103 | CHECK(!after2); 104 | CHECK(!after3); 105 | latch.count_down(); 106 | CHECK(after1); 107 | CHECK(after2); 108 | CHECK(after3); 109 | co_return; 110 | }())); 111 | } 112 | 113 | TEST_SUITE_END(); 114 | -------------------------------------------------------------------------------- /test/async_manual_reset_event_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "doctest/cppcoro_doctest.h" 13 | 14 | TEST_SUITE_BEGIN("async_manual_reset_event"); 15 | 16 | TEST_CASE("default constructor initially not set") 17 | { 18 | cppcoro::async_manual_reset_event event; 19 | CHECK(!event.is_set()); 20 | } 21 | 22 | TEST_CASE("construct event initially set") 23 | { 24 | cppcoro::async_manual_reset_event event{ true }; 25 | CHECK(event.is_set()); 26 | } 27 | 28 | TEST_CASE("set and reset") 29 | { 30 | cppcoro::async_manual_reset_event event; 31 | CHECK(!event.is_set()); 32 | event.set(); 33 | CHECK(event.is_set()); 34 | event.set(); 35 | CHECK(event.is_set()); 36 | event.reset(); 37 | CHECK(!event.is_set()); 38 | event.reset(); 39 | CHECK(!event.is_set()); 40 | event.set(); 41 | CHECK(event.is_set()); 42 | } 43 | 44 | TEST_CASE("await not set event") 45 | { 46 | cppcoro::async_manual_reset_event event; 47 | 48 | auto createWaiter = [&](bool& flag) -> cppcoro::task<> 49 | { 50 | co_await event; 51 | flag = true; 52 | }; 53 | 54 | bool completed1 = false; 55 | bool completed2 = false; 56 | 57 | auto check = [&]() -> cppcoro::task<> 58 | { 59 | CHECK(!completed1); 60 | CHECK(!completed2); 61 | 62 | event.reset(); 63 | 64 | CHECK(!completed1); 65 | CHECK(!completed2); 66 | 67 | event.set(); 68 | 69 | CHECK(completed1); 70 | CHECK(completed2); 71 | 72 | co_return; 73 | }; 74 | 75 | cppcoro::sync_wait(cppcoro::when_all_ready( 76 | createWaiter(completed1), 77 | createWaiter(completed2), 78 | check())); 79 | } 80 | 81 | TEST_CASE("awaiting already set event doesn't suspend") 82 | { 83 | cppcoro::async_manual_reset_event event{ true }; 84 | 85 | auto createWaiter = [&]() -> cppcoro::task<> 86 | { 87 | co_await event; 88 | }; 89 | 90 | // Should complete without blocking. 91 | cppcoro::sync_wait(cppcoro::when_all_ready( 92 | createWaiter(), 93 | createWaiter())); 94 | } 95 | 96 | TEST_SUITE_END(); 97 | -------------------------------------------------------------------------------- /test/async_mutex_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include "doctest/cppcoro_doctest.h" 14 | 15 | TEST_SUITE_BEGIN("async_mutex"); 16 | 17 | TEST_CASE("try_lock") 18 | { 19 | cppcoro::async_mutex mutex; 20 | 21 | CHECK(mutex.try_lock()); 22 | 23 | CHECK_FALSE(mutex.try_lock()); 24 | 25 | mutex.unlock(); 26 | 27 | CHECK(mutex.try_lock()); 28 | } 29 | 30 | #if 0 31 | TEST_CASE("multiple lockers") 32 | { 33 | int value = 0; 34 | cppcoro::async_mutex mutex; 35 | cppcoro::single_consumer_event a; 36 | cppcoro::single_consumer_event b; 37 | cppcoro::single_consumer_event c; 38 | cppcoro::single_consumer_event d; 39 | 40 | auto f = [&](cppcoro::single_consumer_event& e) -> cppcoro::task<> 41 | { 42 | auto lock = co_await mutex.scoped_lock_async(); 43 | co_await e; 44 | ++value; 45 | }; 46 | 47 | auto check = [&]() -> cppcoro::task<> 48 | { 49 | CHECK(value == 0); 50 | 51 | a.set(); 52 | 53 | CHECK(value == 1); 54 | 55 | auto check2 = [&]() -> cppcoro::task<> 56 | { 57 | b.set(); 58 | 59 | CHECK(value == 2); 60 | 61 | c.set(); 62 | 63 | CHECK(value == 3); 64 | 65 | d.set(); 66 | 67 | CHECK(value == 4); 68 | 69 | co_return; 70 | }; 71 | 72 | // Now that we've queued some waiters and released one waiter this will 73 | // have acquired the list of pending waiters in the local cache. 74 | // We'll now queue up another one before releasing any more waiters 75 | // to test the code-path that looks at the newly queued waiter list 76 | // when the cache of waiters is exhausted. 77 | (void)co_await cppcoro::when_all_ready(f(d), check2()); 78 | }; 79 | 80 | cppcoro::sync_wait(cppcoro::when_all_ready( 81 | f(a), 82 | f(b), 83 | f(c), 84 | check())); 85 | 86 | CHECK(value == 4); 87 | } 88 | #endif 89 | 90 | TEST_SUITE_END(); 91 | -------------------------------------------------------------------------------- /test/build.cake: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) Lewis Baker 3 | # Licenced under MIT license. See LICENSE.txt for details. 4 | ############################################################################### 5 | 6 | import cake.path 7 | 8 | from cake.tools import script, env, compiler, project, variant, test 9 | 10 | script.include([ 11 | env.expand('${CPPCORO}/lib/use.cake'), 12 | ]) 13 | 14 | headers = script.cwd([ 15 | "counted.hpp", 16 | "io_service_fixture.hpp", 17 | ]) 18 | 19 | sources = script.cwd([ 20 | 'main.cpp', 21 | 'counted.cpp', 22 | 'generator_tests.cpp', 23 | 'recursive_generator_tests.cpp', 24 | 'async_generator_tests.cpp', 25 | 'async_auto_reset_event_tests.cpp', 26 | 'async_manual_reset_event_tests.cpp', 27 | 'async_mutex_tests.cpp', 28 | 'async_latch_tests.cpp', 29 | 'cancellation_token_tests.cpp', 30 | 'task_tests.cpp', 31 | 'sequence_barrier_tests.cpp', 32 | 'shared_task_tests.cpp', 33 | 'sync_wait_tests.cpp', 34 | 'single_consumer_async_auto_reset_event_tests.cpp', 35 | 'single_producer_sequencer_tests.cpp', 36 | 'multi_producer_sequencer_tests.cpp', 37 | 'when_all_tests.cpp', 38 | 'when_all_ready_tests.cpp', 39 | 'ip_address_tests.cpp', 40 | 'ip_endpoint_tests.cpp', 41 | 'ipv4_address_tests.cpp', 42 | 'ipv4_endpoint_tests.cpp', 43 | 'ipv6_address_tests.cpp', 44 | 'ipv6_endpoint_tests.cpp', 45 | 'static_thread_pool_tests.cpp', 46 | ]) 47 | 48 | if variant.platform == 'windows': 49 | sources += script.cwd([ 50 | 'scheduling_operator_tests.cpp', 51 | 'io_service_tests.cpp', 52 | 'file_tests.cpp', 53 | 'socket_tests.cpp', 54 | ]) 55 | 56 | extras = script.cwd([ 57 | 'build.cake', 58 | ]) 59 | 60 | intermediateBuildDir = cake.path.join(env.expand('${CPPCORO_BUILD}'), 'test', 'obj') 61 | 62 | compiler.addDefine('CPPCORO_RELEASE_' + variant.release.upper()) 63 | 64 | objects = compiler.objects( 65 | targetDir=intermediateBuildDir, 66 | sources=sources, 67 | ) 68 | 69 | testExe = compiler.program( 70 | target=env.expand('${CPPCORO_BUILD}/test/run'), 71 | sources=objects, 72 | ) 73 | 74 | test.alwaysRun = True 75 | testResult = test.run( 76 | program=testExe, 77 | results=env.expand('${CPPCORO_BUILD}/test/run.results'), 78 | ) 79 | script.addTarget('testresult', testResult) 80 | 81 | vcproj = project.project( 82 | target=env.expand('${CPPCORO_PROJECT}/cppcoro_tests'), 83 | items={ 84 | 'Source': sources + headers, 85 | '': extras, 86 | }, 87 | output=testExe, 88 | ) 89 | 90 | script.setResult( 91 | project=vcproj, 92 | test=testExe, 93 | ) 94 | -------------------------------------------------------------------------------- /test/counted.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "counted.hpp" 7 | 8 | int counted::default_construction_count; 9 | int counted::copy_construction_count; 10 | int counted::move_construction_count; 11 | int counted::destruction_count; 12 | -------------------------------------------------------------------------------- /test/counted.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_TESTS_COUNTED_HPP_INCLUDED 6 | #define CPPCORO_TESTS_COUNTED_HPP_INCLUDED 7 | 8 | struct counted 9 | { 10 | static int default_construction_count; 11 | static int copy_construction_count; 12 | static int move_construction_count; 13 | static int destruction_count; 14 | 15 | int id; 16 | 17 | static void reset_counts() 18 | { 19 | default_construction_count = 0; 20 | copy_construction_count = 0; 21 | move_construction_count = 0; 22 | destruction_count = 0; 23 | } 24 | 25 | static int construction_count() 26 | { 27 | return default_construction_count + copy_construction_count + move_construction_count; 28 | } 29 | 30 | static int active_count() 31 | { 32 | return construction_count() - destruction_count; 33 | } 34 | 35 | counted() : id(default_construction_count++) {} 36 | counted(const counted& other) : id(other.id) { ++copy_construction_count; } 37 | counted(counted&& other) : id(other.id) { ++move_construction_count; other.id = -1; } 38 | ~counted() { ++destruction_count; } 39 | 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /test/doctest/cppcoro_doctest.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Andreas Buhr 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_CPPCORO_DOCTEST_H_INCLUDED 6 | #define CPPCORO_CPPCORO_DOCTEST_H_INCLUDED 7 | 8 | #define DOCTEST_CONFIG_USE_STD_HEADERS 9 | #include "doctest.h" 10 | 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /test/doctest/doctestAddTests.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | set(prefix "${TEST_PREFIX}") 5 | set(suffix "${TEST_SUFFIX}") 6 | set(spec ${TEST_SPEC}) 7 | set(extra_args ${TEST_EXTRA_ARGS}) 8 | set(properties ${TEST_PROPERTIES}) 9 | set(script) 10 | set(suite) 11 | set(tests) 12 | 13 | function(add_command NAME) 14 | set(_args "") 15 | foreach(_arg ${ARGN}) 16 | if(_arg MATCHES "[^-./:a-zA-Z0-9_]") 17 | set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument 18 | else() 19 | set(_args "${_args} ${_arg}") 20 | endif() 21 | endforeach() 22 | set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) 23 | endfunction() 24 | 25 | # Run test executable to get list of available tests 26 | if(NOT EXISTS "${TEST_EXECUTABLE}") 27 | message(FATAL_ERROR 28 | "Specified test executable '${TEST_EXECUTABLE}' does not exist" 29 | ) 30 | endif() 31 | 32 | if("${spec}" MATCHES .) 33 | set(spec "--test-case=${spec}") 34 | endif() 35 | 36 | execute_process( 37 | COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases 38 | OUTPUT_VARIABLE output 39 | RESULT_VARIABLE result 40 | ) 41 | if(NOT ${result} EQUAL 0) 42 | message(FATAL_ERROR 43 | "Error running test executable '${TEST_EXECUTABLE}':\n" 44 | " Result: ${result}\n" 45 | " Output: ${output}\n" 46 | ) 47 | endif() 48 | 49 | string(REPLACE "\n" ";" output "${output}") 50 | 51 | # Parse output 52 | foreach(line ${output}) 53 | if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==]) 54 | continue() 55 | endif() 56 | set(test ${line}) 57 | # use escape commas to handle properly test cases with commas inside the name 58 | string(REPLACE "," "\\," test_name ${test}) 59 | # ...and add to script 60 | add_command(add_test 61 | "${prefix}${test}${suffix}" 62 | ${TEST_EXECUTOR} 63 | "${TEST_EXECUTABLE}" 64 | "--test-case=${test_name}" 65 | ${extra_args} 66 | ) 67 | add_command(set_tests_properties 68 | "${prefix}${test}${suffix}" 69 | PROPERTIES 70 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 71 | ${properties} 72 | ) 73 | list(APPEND tests "${prefix}${test}${suffix}") 74 | endforeach() 75 | 76 | # Create a list of all discovered tests, which users may use to e.g. set 77 | # properties on the tests 78 | add_command(set ${TEST_LIST} ${tests}) 79 | 80 | # Write CTest script 81 | file(WRITE "${CTEST_FILE}" "${script}") 82 | -------------------------------------------------------------------------------- /test/io_service_fixture.hpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_TESTS_IO_SERVICE_FIXTURE_HPP_INCLUDED 6 | #define CPPCORO_TESTS_IO_SERVICE_FIXTURE_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | /// \brief 14 | /// Test fixture that creates an io_service and starts up a background thread 15 | /// to process I/O completion events. 16 | /// 17 | /// Thread and io_service are shutdown on destruction. 18 | struct io_service_fixture 19 | { 20 | public: 21 | 22 | io_service_fixture(std::uint32_t threadCount = 1, std::uint32_t concurrencyHint = 0) 23 | : m_ioService(concurrencyHint) 24 | { 25 | m_ioThreads.reserve(threadCount); 26 | try 27 | { 28 | for (std::uint32_t i = 0; i < threadCount; ++i) 29 | { 30 | m_ioThreads.emplace_back([this] { m_ioService.process_events(); }); 31 | } 32 | } 33 | catch (...) 34 | { 35 | stop(); 36 | throw; 37 | } 38 | } 39 | 40 | ~io_service_fixture() 41 | { 42 | stop(); 43 | } 44 | 45 | cppcoro::io_service& io_service() { return m_ioService; } 46 | 47 | private: 48 | 49 | void stop() 50 | { 51 | m_ioService.stop(); 52 | for (auto& thread : m_ioThreads) 53 | { 54 | thread.join(); 55 | } 56 | } 57 | 58 | cppcoro::io_service m_ioService; 59 | std::vector m_ioThreads; 60 | 61 | }; 62 | 63 | template 64 | struct io_service_fixture_with_threads : io_service_fixture 65 | { 66 | io_service_fixture_with_threads() 67 | : io_service_fixture(thread_count, concurrency_hint) 68 | {} 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /test/ip_address_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include "doctest/cppcoro_doctest.h" 9 | 10 | TEST_SUITE_BEGIN("ip_address"); 11 | 12 | using cppcoro::net::ip_address; 13 | using cppcoro::net::ipv4_address; 14 | using cppcoro::net::ipv6_address; 15 | 16 | TEST_CASE("default constructor") 17 | { 18 | ip_address x; 19 | CHECK(x.is_ipv4()); 20 | CHECK(x.to_ipv4() == ipv4_address{}); 21 | } 22 | 23 | TEST_CASE("to_string") 24 | { 25 | ip_address a = ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 }; 26 | ip_address b = ipv4_address{ 192, 168, 0, 1 }; 27 | 28 | CHECK(a.to_string() == "aabb:ccdd:11:2233:102:304:506:708"); 29 | CHECK(b.to_string() == "192.168.0.1"); 30 | } 31 | 32 | TEST_CASE("from_string") 33 | { 34 | CHECK(ip_address::from_string("") == std::nullopt); 35 | CHECK(ip_address::from_string("foo") == std::nullopt); 36 | CHECK(ip_address::from_string(" 192.168.0.1") == std::nullopt); 37 | CHECK(ip_address::from_string("192.168.0.1asdf") == std::nullopt); 38 | 39 | CHECK(ip_address::from_string("192.168.0.1") == ipv4_address(192, 168, 0, 1)); 40 | CHECK(ip_address::from_string("::192.168.0.1") == ipv6_address(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1)); 41 | CHECK(ip_address::from_string("aabb:ccdd:11:2233:102:304:506:708") == 42 | ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 }); 43 | } 44 | 45 | TEST_SUITE_END(); 46 | -------------------------------------------------------------------------------- /test/ip_endpoint_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #include "doctest/cppcoro_doctest.h" 10 | 11 | TEST_SUITE_BEGIN("ip_endpoint"); 12 | 13 | using namespace cppcoro::net; 14 | 15 | namespace 16 | { 17 | constexpr bool isMsvc15_5X86Optimised = 18 | #if CPPCORO_COMPILER_MSVC && CPPCORO_CPU_X86 && _MSC_VER == 1912 && defined(CPPCORO_RELEASE_OPTIMISED) 19 | true; 20 | #else 21 | false; 22 | #endif 23 | } 24 | 25 | // BUG: Skip this test under MSVC 15.5 x86 optimised builds due to a compiler bug 26 | // that generates bad code. 27 | // See https://developercommunity.visualstudio.com/content/problem/177151/bad-code-generation-under-x86-optimised-for-stdopt.html 28 | TEST_CASE("to_string" * doctest::skip{ isMsvc15_5X86Optimised }) 29 | { 30 | ip_endpoint a = ipv4_endpoint{ ipv4_address{ 192, 168, 2, 254 }, 80 }; 31 | ip_endpoint b = ipv6_endpoint{ 32 | *ipv6_address::from_string("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 33 | 22 }; 34 | 35 | CHECK(a.to_string() == "192.168.2.254:80"); 36 | CHECK(b.to_string() == "[2001:db8:85a3::8a2e:370:7334]:22"); 37 | } 38 | 39 | TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised }) 40 | { 41 | CHECK(ip_endpoint::from_string("") == std::nullopt); 42 | CHECK(ip_endpoint::from_string("[foo]:123") == std::nullopt); 43 | CHECK(ip_endpoint::from_string("[123]:1000") == std::nullopt); 44 | CHECK(ip_endpoint::from_string("[10.11.12.13]:1000") == std::nullopt); 45 | 46 | CHECK(ip_endpoint::from_string("192.168.2.254:80") == 47 | ipv4_endpoint{ 48 | ipv4_address{ 192, 168, 2, 254 }, 80 }); 49 | CHECK(ip_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443") == 50 | ipv6_endpoint{ 51 | ipv6_address{ 0x2001, 0xdb8, 0x85a3, 0x0, 0x0, 0x8a2e, 0x370, 0x7334 }, 52 | 443 }); 53 | } 54 | 55 | TEST_SUITE_END(); 56 | 57 | -------------------------------------------------------------------------------- /test/ipv4_address_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include "doctest/cppcoro_doctest.h" 9 | 10 | 11 | TEST_SUITE_BEGIN("ipv4_address"); 12 | 13 | using cppcoro::net::ipv4_address; 14 | 15 | TEST_CASE("DefaultConstructToZeroes") 16 | { 17 | CHECK(ipv4_address{}.to_integer() == 0u); 18 | } 19 | 20 | TEST_CASE("to_integer() is BigEndian") 21 | { 22 | ipv4_address address{ 10, 11, 12, 13 }; 23 | CHECK(address.to_integer() == 0x0A0B0C0Du); 24 | } 25 | 26 | TEST_CASE("is_loopback()") 27 | { 28 | CHECK(ipv4_address{ 127, 0, 0, 1 }.is_loopback()); 29 | CHECK(ipv4_address{ 127, 0, 0, 50 }.is_loopback()); 30 | CHECK(ipv4_address{ 127, 5, 10, 15 }.is_loopback()); 31 | CHECK(!ipv4_address{ 10, 11, 12, 13 }.is_loopback()); 32 | } 33 | 34 | TEST_CASE("bytes()") 35 | { 36 | ipv4_address ip{ 19, 63, 129, 200 }; 37 | CHECK(ip.bytes()[0] == 19); 38 | CHECK(ip.bytes()[1] == 63); 39 | CHECK(ip.bytes()[2] == 129); 40 | CHECK(ip.bytes()[3] == 200); 41 | } 42 | 43 | TEST_CASE("to_string()") 44 | { 45 | CHECK(ipv4_address(0, 0, 0, 0).to_string() == "0.0.0.0"); 46 | CHECK(ipv4_address(10, 125, 255, 7).to_string() == "10.125.255.7"); 47 | CHECK(ipv4_address(123, 234, 101, 255).to_string() == "123.234.101.255"); 48 | } 49 | 50 | TEST_CASE("from_string") 51 | { 52 | // Check for some invalid strings. 53 | CHECK(ipv4_address::from_string("") == std::nullopt); 54 | CHECK(ipv4_address::from_string("asdf") == std::nullopt); 55 | CHECK(ipv4_address::from_string(" 123.34.56.8") == std::nullopt); 56 | CHECK(ipv4_address::from_string("123.34.56.8 ") == std::nullopt); 57 | CHECK(ipv4_address::from_string("123.") == std::nullopt); 58 | CHECK(ipv4_address::from_string("123.1") == std::nullopt); 59 | CHECK(ipv4_address::from_string("123.12") == std::nullopt); 60 | CHECK(ipv4_address::from_string("123.12.") == std::nullopt); 61 | CHECK(ipv4_address::from_string("123.12.4") == std::nullopt); 62 | CHECK(ipv4_address::from_string("123.12.45") == std::nullopt); 63 | CHECK(ipv4_address::from_string("123.12.45.") == std::nullopt); 64 | 65 | // Overflow of individual parts 66 | CHECK(ipv4_address::from_string("456.12.45.30") == std::nullopt); 67 | CHECK(ipv4_address::from_string("45.256.45.30") == std::nullopt); 68 | CHECK(ipv4_address::from_string("45.25.677.30") == std::nullopt); 69 | CHECK(ipv4_address::from_string("123.12.45.301") == std::nullopt); 70 | 71 | // Can't parse octal yet. 72 | CHECK(ipv4_address::from_string("00") == std::nullopt); 73 | CHECK(ipv4_address::from_string("012345") == std::nullopt); 74 | CHECK(ipv4_address::from_string("045.25.67.30") == std::nullopt); 75 | CHECK(ipv4_address::from_string("45.025.67.30") == std::nullopt); 76 | CHECK(ipv4_address::from_string("45.25.067.30") == std::nullopt); 77 | CHECK(ipv4_address::from_string("45.25.67.030") == std::nullopt); 78 | 79 | // Parse single integer format 80 | CHECK(ipv4_address::from_string("0") == ipv4_address(0)); 81 | CHECK(ipv4_address::from_string("1") == ipv4_address(0, 0, 0, 1)); 82 | CHECK(ipv4_address::from_string("255") == ipv4_address(0, 0, 0, 255)); 83 | CHECK(ipv4_address::from_string("43534243") == ipv4_address(43534243)); 84 | 85 | // Parse dotted decimal format 86 | CHECK(ipv4_address::from_string("45.25.67.30") == ipv4_address(45, 25, 67, 30)); 87 | CHECK(ipv4_address::from_string("0.0.0.0") == ipv4_address(0, 0, 0, 0)); 88 | CHECK(ipv4_address::from_string("1.2.3.4") == ipv4_address(1, 2, 3, 4)); 89 | } 90 | TEST_SUITE_END(); 91 | -------------------------------------------------------------------------------- /test/ipv4_endpoint_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include "doctest/cppcoro_doctest.h" 9 | 10 | TEST_SUITE_BEGIN("ip_endpoint"); 11 | 12 | using namespace cppcoro::net; 13 | 14 | TEST_CASE("to_string") 15 | { 16 | CHECK(ipv4_endpoint{ ipv4_address{ 192, 168, 2, 254 }, 80 }.to_string() == "192.168.2.254:80"); 17 | } 18 | 19 | TEST_CASE("from_string") 20 | { 21 | CHECK(ipv4_endpoint::from_string("") == std::nullopt); 22 | CHECK(ipv4_endpoint::from_string(" ") == std::nullopt); 23 | CHECK(ipv4_endpoint::from_string("100") == std::nullopt); 24 | CHECK(ipv4_endpoint::from_string("100.10.200.20") == std::nullopt); 25 | CHECK(ipv4_endpoint::from_string("100.10.200.20:") == std::nullopt); 26 | CHECK(ipv4_endpoint::from_string("100.10.200.20::80") == std::nullopt); 27 | CHECK(ipv4_endpoint::from_string("100.10.200.20 80") == std::nullopt); 28 | 29 | CHECK(ipv4_endpoint::from_string("192.168.2.254:80") == 30 | ipv4_endpoint{ ipv4_address{ 192, 168, 2, 254 }, 80 }); 31 | } 32 | 33 | TEST_SUITE_END(); 34 | -------------------------------------------------------------------------------- /test/ipv6_endpoint_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | #include 8 | 9 | #include "doctest/cppcoro_doctest.h" 10 | 11 | TEST_SUITE_BEGIN("ipv6_endpoint"); 12 | 13 | using namespace cppcoro::net; 14 | 15 | namespace 16 | { 17 | constexpr bool isMsvc15_5X86Optimised = 18 | #if CPPCORO_COMPILER_MSVC && CPPCORO_CPU_X86 && _MSC_VER == 1912 && defined(CPPCORO_RELEASE_OPTIMISED) 19 | true; 20 | #else 21 | false; 22 | #endif 23 | } 24 | 25 | // BUG: MSVC 15.5 x86 optimised builds generates bad code 26 | TEST_CASE("to_string" * doctest::skip{ isMsvc15_5X86Optimised }) 27 | { 28 | CHECK(ipv6_endpoint{ ipv6_address{ 0x20010db885a30000, 0x00008a2e03707334 }, 80 }.to_string() == 29 | "[2001:db8:85a3::8a2e:370:7334]:80"); 30 | } 31 | 32 | // BUG: MSVC 15.5 x86 optimised builds generates bad code 33 | TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised }) 34 | { 35 | CHECK(ipv6_endpoint::from_string("") == std::nullopt); 36 | CHECK(ipv6_endpoint::from_string(" ") == std::nullopt); 37 | CHECK(ipv6_endpoint::from_string("asdf") == std::nullopt); 38 | CHECK(ipv6_endpoint::from_string("100:100") == std::nullopt); 39 | CHECK(ipv6_endpoint::from_string("100.10.200.20:100") == std::nullopt); 40 | CHECK(ipv6_endpoint::from_string("2001:0db8:85a3:0000:0000:8a2e:0370:7334") == std::nullopt); 41 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334") == std::nullopt); 42 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]") == std::nullopt); 43 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:") == std::nullopt); 44 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334] :123") == std::nullopt); 45 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:65536") == std::nullopt); 46 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:6553600") == std::nullopt); 47 | 48 | CHECK(ipv6_endpoint::from_string("[::]:0") == ipv6_endpoint{}); 49 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80") == 50 | ipv6_endpoint{ ipv6_address{ 0x20010db885a30000, 0x00008a2e03707334 }, 80 }); 51 | CHECK(ipv6_endpoint::from_string("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:65535") == 52 | ipv6_endpoint{ ipv6_address{ 0x20010db885a30000, 0x00008a2e03707334 }, 65535 }); 53 | } 54 | 55 | TEST_SUITE_END(); 56 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 7 | #include "doctest/cppcoro_doctest.h" 8 | -------------------------------------------------------------------------------- /test/single_consumer_async_auto_reset_event_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "doctest/cppcoro_doctest.h" 21 | 22 | TEST_SUITE_BEGIN("single_consumer_async_auto_reset_event"); 23 | 24 | TEST_CASE("single waiter") 25 | { 26 | cppcoro::single_consumer_async_auto_reset_event event; 27 | 28 | bool started = false; 29 | bool finished = false; 30 | auto run = [&]() -> cppcoro::task<> 31 | { 32 | started = true; 33 | co_await event; 34 | finished = true; 35 | }; 36 | 37 | auto check = [&]() -> cppcoro::task<> 38 | { 39 | CHECK(started); 40 | CHECK(!finished); 41 | 42 | event.set(); 43 | 44 | CHECK(finished); 45 | 46 | co_return; 47 | }; 48 | 49 | cppcoro::sync_wait(cppcoro::when_all_ready(run(), check())); 50 | } 51 | 52 | TEST_CASE("multi-threaded") 53 | { 54 | cppcoro::static_thread_pool tp; 55 | 56 | cppcoro::sync_wait([&]() -> cppcoro::task<> 57 | { 58 | cppcoro::single_consumer_async_auto_reset_event valueChangedEvent; 59 | 60 | std::atomic value; 61 | 62 | auto consumer = [&]() -> cppcoro::task 63 | { 64 | while (value.load(std::memory_order_relaxed) < 10'000) 65 | { 66 | co_await valueChangedEvent; 67 | } 68 | 69 | co_return 0; 70 | }; 71 | 72 | auto modifier = [&](int count) -> cppcoro::task 73 | { 74 | co_await tp.schedule(); 75 | for (int i = 0; i < count; ++i) 76 | { 77 | value.fetch_add(1, std::memory_order_relaxed); 78 | valueChangedEvent.set(); 79 | } 80 | co_return 0; 81 | }; 82 | 83 | for (int i = 0; i < 1000; ++i) 84 | { 85 | value.store(0, std::memory_order_relaxed); 86 | 87 | // Really just checking that we don't deadlock here due to a missed wake-up. 88 | (void)co_await cppcoro::when_all(consumer(), modifier(5'000), modifier(5'000)); 89 | } 90 | }()); 91 | } 92 | 93 | TEST_SUITE_END(); 94 | -------------------------------------------------------------------------------- /test/single_producer_sequencer_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include "doctest/cppcoro_doctest.h" 21 | 22 | DOCTEST_TEST_SUITE_BEGIN("single_producer_sequencer"); 23 | 24 | using namespace cppcoro; 25 | 26 | DOCTEST_TEST_CASE("multi-threaded usage single consumer") 27 | { 28 | static_thread_pool tp{ 2 }; 29 | 30 | constexpr std::size_t bufferSize = 256; 31 | 32 | sequence_barrier readBarrier; 33 | single_producer_sequencer sequencer(readBarrier, bufferSize); 34 | 35 | constexpr std::size_t iterationCount = 1'000'000; 36 | 37 | std::uint64_t buffer[bufferSize]; 38 | 39 | auto[result, dummy] = sync_wait(when_all( 40 | [&]() -> task 41 | { 42 | // Consumer 43 | std::uint64_t sum = 0; 44 | 45 | bool reachedEnd = false; 46 | std::size_t nextToRead = 0; 47 | do 48 | { 49 | const std::size_t available = co_await sequencer.wait_until_published(nextToRead, tp); 50 | do 51 | { 52 | sum += buffer[nextToRead % bufferSize]; 53 | } while (nextToRead++ != available); 54 | 55 | // Zero value is sentinel that indicates the end of the stream. 56 | reachedEnd = buffer[available % bufferSize] == 0; 57 | 58 | // Notify that we've finished processing up to 'available'. 59 | readBarrier.publish(available); 60 | } while (!reachedEnd); 61 | 62 | co_return sum; 63 | }(), 64 | [&]() -> task<> 65 | { 66 | // Producer 67 | constexpr std::size_t maxBatchSize = 10; 68 | 69 | std::size_t i = 0; 70 | while (i < iterationCount) 71 | { 72 | const std::size_t batchSize = std::min(maxBatchSize, iterationCount - i); 73 | auto sequences = co_await sequencer.claim_up_to(batchSize, tp); 74 | for (auto seq : sequences) 75 | { 76 | buffer[seq % bufferSize] = ++i; 77 | } 78 | sequencer.publish(sequences.back()); 79 | } 80 | 81 | auto finalSeq = co_await sequencer.claim_one(tp); 82 | buffer[finalSeq % bufferSize] = 0; 83 | sequencer.publish(finalSeq); 84 | }())); 85 | 86 | // Suppress unused variable warning. 87 | (void)dummy; 88 | 89 | constexpr std::uint64_t expectedResult = 90 | std::uint64_t(iterationCount) * std::uint64_t(iterationCount + 1) / 2; 91 | 92 | CHECK(result == expectedResult); 93 | } 94 | 95 | DOCTEST_TEST_SUITE_END(); 96 | -------------------------------------------------------------------------------- /test/sync_wait_tests.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "doctest/cppcoro_doctest.h" 18 | 19 | TEST_SUITE_BEGIN("sync_wait"); 20 | 21 | static_assert(std::is_same< 22 | decltype(cppcoro::sync_wait(std::declval>())), 23 | std::string&&>::value); 24 | static_assert(std::is_same< 25 | decltype(cppcoro::sync_wait(std::declval&>())), 26 | std::string&>::value); 27 | 28 | TEST_CASE("sync_wait(task)") 29 | { 30 | auto makeTask = []() -> cppcoro::task 31 | { 32 | co_return "foo"; 33 | }; 34 | 35 | auto task = makeTask(); 36 | CHECK(cppcoro::sync_wait(task) == "foo"); 37 | 38 | CHECK(cppcoro::sync_wait(makeTask()) == "foo"); 39 | } 40 | 41 | TEST_CASE("sync_wait(shared_task)") 42 | { 43 | auto makeTask = []() -> cppcoro::shared_task 44 | { 45 | co_return "foo"; 46 | }; 47 | 48 | auto task = makeTask(); 49 | 50 | CHECK(cppcoro::sync_wait(task) == "foo"); 51 | CHECK(cppcoro::sync_wait(makeTask()) == "foo"); 52 | } 53 | 54 | TEST_CASE("multiple threads") 55 | { 56 | // We are creating a new task and starting it inside the sync_wait(). 57 | // The task will reschedule itself for resumption on a thread-pool thread 58 | // which will sometimes complete before this thread calls event.wait() 59 | // inside sync_wait(). Thus we're roughly testing the thread-safety of 60 | // sync_wait(). 61 | cppcoro::static_thread_pool tp{ 1 }; 62 | 63 | int value = 0; 64 | auto createLazyTask = [&]() -> cppcoro::task 65 | { 66 | co_await tp.schedule(); 67 | co_return value++; 68 | }; 69 | 70 | for (int i = 0; i < 10'000; ++i) 71 | { 72 | CHECK(cppcoro::sync_wait(createLazyTask()) == i); 73 | } 74 | } 75 | 76 | TEST_SUITE_END(); 77 | --------------------------------------------------------------------------------