├── .github └── workflows │ ├── coverage.yml │ └── docs.yml ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── book.toml ├── meson.build └── src │ ├── SUMMARY.md │ ├── contributing.md │ ├── headers.md │ ├── headers │ ├── algorithm.md │ ├── algorithm │ │ ├── invocable.md │ │ ├── ite.md │ │ ├── let.md │ │ ├── race_and_cancel.md │ │ ├── repeat_while.md │ │ ├── sequence.md │ │ ├── transform.md │ │ └── when_all.md │ ├── basic.md │ ├── basic │ │ ├── any_receiver.md │ │ ├── co_awaits_to.md │ │ ├── detached.md │ │ ├── run.md │ │ ├── sender_awaiter.md │ │ └── spawn.md │ ├── cancellation.md │ ├── cancellation │ │ ├── cancellation_callback.md │ │ ├── cancellation_event.md │ │ └── suspend_indefinitely.md │ ├── execution.md │ ├── mutex.md │ ├── mutex │ │ ├── mutex.md │ │ └── shared_mutex.md │ ├── oneshot-event.md │ ├── post-ack.md │ ├── post-ack │ │ ├── post_ack_agent.md │ │ ├── post_ack_handle.md │ │ └── post_ack_mechanism.md │ ├── promise.md │ ├── promise │ │ ├── future.md │ │ └── promise.md │ ├── queue.md │ ├── recurring-event.md │ ├── result.md │ ├── sequenced-event.md │ ├── wait-group.html │ └── wait-group │ │ ├── wait_group.md │ │ └── wait_in_group.md │ ├── index.md │ ├── io-service.md │ ├── sender-receiver.md │ ├── wait-group.md │ └── your-own-sender.md ├── include └── async │ ├── algorithm.hpp │ ├── barrier.hpp │ ├── basic.hpp │ ├── cancellation.hpp │ ├── execution.hpp │ ├── mutex.hpp │ ├── oneshot-event.hpp │ ├── post-ack.hpp │ ├── promise.hpp │ ├── queue.hpp │ ├── recurring-event.hpp │ ├── result.hpp │ ├── sequenced-event.hpp │ └── wait-group.hpp ├── meson.build ├── meson_options.txt └── tests ├── algorithm.cpp ├── basic.cpp ├── meson.build ├── mutex.cpp ├── post-ack.cpp ├── promise.cpp ├── queue.cpp ├── race.cpp └── sequenced.cpp /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Tests and coverage 2 | on: 3 | push: 4 | pull_request: 5 | 6 | permissions: 7 | statuses: write 8 | 9 | jobs: 10 | tests: 11 | # XXX(arsen): stop doing this when GHAs ubuntu-latest is 22.04 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Get dependencies 16 | run: | 17 | export 18 | sudo DEBIAN_FRONTEND=noninteractive apt-get update 19 | sudo DEBIAN_FRONTEND=noninteractive \ 20 | apt-get -y install python3-dev libxml2-dev libxslt1-dev \ 21 | cmake libyaml-dev libclang-dev llvm-dev \ 22 | libglib2.0-dev libjson-glib-dev flex \ 23 | ninja-build build-essential python3-pip \ 24 | git libgtest-dev 25 | sudo pip3 install -U meson hotdoc gcovr 26 | - name: Build 27 | run: | 28 | git clone --depth=1 https://github.com/managarm/frigg.git subprojects/frigg 29 | meson --buildtype=debug -Dbuild_tests=true -Db_coverage=true build 30 | cd build 31 | ninja test 32 | ninja coverage-html 33 | ninja coverage-xml 34 | ninja coverage-text 35 | ninja coverage-sonarqube 36 | - name: Upload coverage reports as artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: coverages 40 | path: | 41 | build/meson-logs/coverage.xml 42 | build/meson-logs/sonarqube.xml 43 | build/meson-logs/coverage.txt 44 | build/meson-logs/coveragereport/ 45 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Get dependencies 12 | run: | 13 | set -e 14 | sudo apt-get -y install python3-dev libxml2-dev libxslt1-dev \ 15 | cmake libyaml-dev libclang-dev llvm-dev \ 16 | libglib2.0-dev libjson-glib-dev flex \ 17 | ninja-build jq 18 | pip install -U meson 19 | wget https://raw.githubusercontent.com/WayneD/rsync/c3f7414c450faaf6a8281cc4a4403529aeb7d859/rsync-ssl 20 | curl -sL https://github.com/rust-lang/mdBook/releases/download/v0.4.20/mdbook-v0.4.20-x86_64-unknown-linux-gnu.tar.gz | tar xz 21 | sha512sum --check < 5 | ``` 6 | 7 | This header provides various algorithms that can be used as building blocks for 8 | creating more complex functionality without incuring any costs of memory allocation. 9 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/invocable.md: -------------------------------------------------------------------------------- 1 | # invocable 2 | 3 | `invocable` is an operation that executes the given functor and completes inline 4 | with the value returned by the functor. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | sender invocable(F f); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | `F` is a functor that takes no arguments. 16 | 17 | ### Arguments 18 | 19 | - `f` - the functor to execute. 20 | 21 | ### Return value 22 | 23 | This function returns a sender of unspecified type. This sender returns the value 24 | returned by the functor. 25 | 26 | ## Examples 27 | 28 | ```cpp 29 | auto fn = [] { return 1; }; 30 | std::cout << async::run(async::invocable(fn)) << std::endl; 31 | ``` 32 | 33 | Output: 34 | ``` 35 | 1 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/ite.md: -------------------------------------------------------------------------------- 1 | # ite 2 | 3 | `ite` is an operation that checks the given condition, and starts the "then" or 4 | "else" sender depending on the result. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | sender ite(C cond, ST then_s, SE else_s); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | `C` is a functor that returns a truthy or falsy value. `ST` and `SE` are senders. 16 | `ST` and `SE` must have the same return type. 17 | 18 | ### Arguments 19 | 20 | - `cond` - the condition to check. 21 | - `then_s` - the sender to start if condition is true. 22 | - `else_s` - the sender to start if condition is false. 23 | 24 | ### Return value 25 | 26 | This function returns a sender of unspecified type. The sender returns the value of 27 | the sender that was started depending on the condition. 28 | 29 | ## Examples 30 | 31 | ```cpp 32 | auto then_coro = [] () -> async::result { 33 | co_return 1; 34 | }; 35 | 36 | auto else_coro = [] () -> async::result { 37 | co_return 2; 38 | }; 39 | 40 | std::cout << async::run(async::ite([] { return true; }, then_coro(), else_coro())) << std::endl; 41 | std::cout << async::run(async::ite([] { return false; }, then_coro(), else_coro())) << std::endl; 42 | ``` 43 | 44 | Output: 45 | ``` 46 | 1 47 | 2 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/let.md: -------------------------------------------------------------------------------- 1 | # let 2 | 3 | `let` is an operation that obtains a value using the given functor, stores it in 4 | a variable, and obtains a sender to start using the second functor, passing a 5 | reference to the variable as an argument. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | sender let(Pred pred, Func func); 12 | ``` 13 | 14 | ### Requirements 15 | 16 | `Pred` is a functor that returns a value. `Func` is a functor that returns a sender 17 | and accepts a reference to the value returned by `Pred` as an argument. 18 | 19 | ### Arguments 20 | 21 | - `pred` - the functor to execute to obtain the value. 22 | - `func` - the function to call to obtain the sender. 23 | 24 | ### Return value 25 | 26 | This function returns a sender of unspecified type. The sender returns the value returned 27 | by the obtained sender. 28 | 29 | ## Examples 30 | 31 | ```cpp 32 | std::cout << async::run(async::let([] { return 3; }, 33 | [] (int &i) -> async::result { 34 | return i * 2; 35 | })) << std::endl; 36 | ``` 37 | 38 | Output: 39 | ``` 40 | 6 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/race_and_cancel.md: -------------------------------------------------------------------------------- 1 | # race_and_cancel 2 | 3 | `race_and_cancel` is an operation that obtains senders using the given functors, 4 | starts all of them concurrently, and cancels the remaining ones when one finishes. 5 | 6 | **Note:** `race_and_cancel` does not guarantee that only one sender completes 7 | without cancellation. 8 | 9 | ## Prototype 10 | 11 | ```cpp 12 | template 13 | sender race_and_cancel(Functors... fs); 14 | ``` 15 | 16 | ### Requirements 17 | 18 | Every type of `Functors` is invocable with an argument of type `async::cancellation_token` 19 | and produces a sender. 20 | 21 | ### Arguments 22 | 23 | - `fs` - the functors to invoke to obtain the senders. 24 | 25 | ### Return value 26 | 27 | This function returns a sender of unspecified type. The sender does not return any value. 28 | 29 | ## Examples 30 | 31 | ```cpp 32 | async::run(async::race_and_cancel( 33 | [] (async::cancellation_token) -> async::result { 34 | std::cout << "Hi 1" << std::endl; 35 | co_return; 36 | }, 37 | [] (async::cancellation_token) -> async::result { 38 | std::cout << "Hi 2" << std::endl; 39 | co_return; 40 | } 41 | )); 42 | ``` 43 | 44 | Possible output: 45 | ``` 46 | Hi 1 47 | Hi 2 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/repeat_while.md: -------------------------------------------------------------------------------- 1 | # repeat_while 2 | 3 | `repeat_while` is an operation that continuously checks the given condition, and 4 | as long as it's true, it invokes the given functor to obtain a sender, and starts it. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | sender repeat_while(C cond, SF factory); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | `C` is a functor that returns a truthy or falsy value. `SF` is a functor that 16 | returns a sender. 17 | 18 | ### Arguments 19 | 20 | - `cond` - the condition to check. 21 | - `factory` - the functor to call to obtain the sender on every iteration. 22 | 23 | ### Return value 24 | 25 | This function returns a sender of unspecified type. The sender does not return 26 | any value. 27 | 28 | ## Examples 29 | 30 | ```cpp 31 | int i = 0; 32 | async::run(async::repeat_while([&] { return i++ < 5; }, 33 | [] () -> async::result { 34 | std::cout << "Hi" << std::endl; 35 | co_return; 36 | })); 37 | ``` 38 | 39 | Output: 40 | ``` 41 | Hi 42 | Hi 43 | Hi 44 | Hi 45 | Hi 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/sequence.md: -------------------------------------------------------------------------------- 1 | # sequence 2 | 3 | `sequence` is an operation that sequentially starts the given senders. 4 | 5 | ## Prototype 6 | 7 | ```cpp 8 | template 9 | sender sequence(Senders ...senders); 10 | ``` 11 | 12 | ### Requirements 13 | 14 | `Senders` is not empty, and every type in it is a sender. All but the last sender 15 | must have a return type of `void`. 16 | 17 | ### Arguments 18 | 19 | - `senders` - the senders to run. 20 | 21 | ### Return value 22 | 23 | This function returns a sender of unspecified type. The sender returns the value 24 | returned by the last sender. 25 | 26 | ## Examples 27 | 28 | ```cpp 29 | int steps[4] = {0, 0, 0, 0}; 30 | int v = async::run([&]() -> async::result { 31 | int i = 0; 32 | co_return co_await async::sequence( 33 | [&]() -> async::result { 34 | steps[0] = i; 35 | i++; 36 | co_return; 37 | }(), 38 | [&]() -> async::result { 39 | steps[1] = i; 40 | i++; 41 | co_return; 42 | }(), 43 | [&]() -> async::result { 44 | steps[2] = i; 45 | i++; 46 | co_return; 47 | }(), 48 | [&]() -> async::result { 49 | steps[3] = i; 50 | i++; 51 | co_return i * 10; 52 | }() 53 | ); 54 | }()); 55 | 56 | std::cout << v << " " << steps[0] << steps[1] << steps[2] << steps[3] << std::endl; 57 | ``` 58 | 59 | Output: 60 | ``` 61 | 40 0123 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/transform.md: -------------------------------------------------------------------------------- 1 | # transform 2 | 3 | `transform` is an operation that starts the given sender, and upon completion 4 | applies the functor to it's return value. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | sender transform(Sender ds, F f); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | `Sender` is a sender and `F` is a functor that accepts the return value of the 16 | sender as an argument. 17 | 18 | ### Arguments 19 | 20 | - `ds` - the sender whose result value should be transformed. 21 | - `f` - the functor to use. 22 | 23 | ### Return value 24 | 25 | This function returns a sender of unspecified type. This sender returns the return 26 | value of the functor. 27 | 28 | ## Examples 29 | 30 | ```cpp 31 | auto coro = [] () -> async::result { 32 | co_return 5; 33 | }; 34 | 35 | std::cout << async::run(async::transform(coro(), [] (int i) { return i * 2; }) << std::endl; 36 | ``` 37 | 38 | Output: 39 | ``` 40 | 10 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/src/headers/algorithm/when_all.md: -------------------------------------------------------------------------------- 1 | # when_all 2 | 3 | `when_all` is an operation that starts all the given senders concurrently, and 4 | only completes when all of them complete. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | sender when_all(Senders... senders); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | Every type of `Senders` is a sender that doesn't return any value. 16 | 17 | ### Arguments 18 | 19 | - `senders` - the senders to start. 20 | 21 | ### Return value 22 | 23 | This function returns a sender of unspecified type. The sender does not return 24 | any value. 25 | 26 | ## Examples 27 | 28 | ```cpp 29 | async::run(async::when_all( 30 | [] () -> async::result { 31 | std::cout << "Hi 1" << std::endl; 32 | co_return; 33 | }(), 34 | [] () -> async::result { 35 | std::cout << "Hi 2" << std::endl; 36 | co_return; 37 | }() 38 | )); 39 | std::cout << "Done" << std::endl; 40 | ``` 41 | 42 | Possible output: 43 | ``` 44 | Hi 1 45 | Hi 2 46 | Done 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/headers/basic.md: -------------------------------------------------------------------------------- 1 | # basic 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | This header provides basic functionality. 8 | -------------------------------------------------------------------------------- /docs/src/headers/basic/any_receiver.md: -------------------------------------------------------------------------------- 1 | # any_receiver 2 | 3 | `any_receiver` is a wrapper type that wraps around any receiver type and handles 4 | calling `set_value` on it. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | struct any_receiver { 11 | template 12 | any_receiver(R receiver); // (1) 13 | 14 | void set_value(T); // (2) 15 | void set_value_noinline(T); // (2) 16 | } 17 | ``` 18 | 19 | 1. Constructs the object with the given receiver. 20 | 2. Forwards the value to the given receiver. 21 | 22 | ### Requirements 23 | 24 | `T` is any type, `R` is a receiver that accepts values of type `T`. `R` is trivially 25 | copyable, and is smaller or of the same size and alignment as a `void *`. 26 | 27 | ### Arguments 28 | 29 | - `receiver` - the receiver to wrap. 30 | 31 | ### Return values 32 | 33 | 1. N/A 34 | 2. These methods don't return any value. 35 | -------------------------------------------------------------------------------- /docs/src/headers/basic/co_awaits_to.md: -------------------------------------------------------------------------------- 1 | # co_awaits_to 2 | 3 | `co_awaits_to` is a concept that checks whether an expression of the 4 | given type is `co_await`-able, and awaits to a value of the specified 5 | type. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | concept co_awaits_to; 12 | ``` 13 | 14 | ### Arguments 15 | 16 | - `Awaitable` - the type to check. 17 | - `T` - the type the awaitable is supposed to await to. 18 | 19 | ## Examples 20 | 21 | ```cpp 22 | template 23 | concept foo = requires (T t) { 24 | { t.foo() } -> async::co_awaits_to; 25 | }; 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/src/headers/basic/detached.md: -------------------------------------------------------------------------------- 1 | # detached, detach and detach_with_allocator 2 | 3 | `detached` is a coroutine type used for detached coroutines. Detached coroutines 4 | cannot be awaited and they do not suspend the coroutine that started them. 5 | 6 | `detach` and `detach_with_allocator` are functions that take a sender and run them 7 | as if they were a detached coroutine. `detach` is a wrapper around `detach_with_allocator` 8 | that uses `operator new`/`operator delete` for allocating memory. The allocator 9 | is used to allocate a structure that holds the operation and continuation until 10 | the operation completes. 11 | 12 | ## Prototype 13 | 14 | ```cpp 15 | template 16 | void detach_with_allocator(Allocator allocator, S sender, Cont continuation); // (1) 17 | 18 | template 19 | void detach_with_allocator(Allocator allocator, S sender); // (2) 20 | 21 | template 22 | void detach(S sender); // (3) 23 | 24 | template 25 | void detach(S sender, Cont continuation); // (4) 26 | ``` 27 | 28 | 1. Detach a sender using an allocator, and call the continuation after it completes. 29 | 2. Same as (1) but without the continuation. 30 | 3. Same as (2) but without an allocator. 31 | 4. Same as (1) but without an allocator. 32 | 33 | ### Requirements 34 | 35 | `Allocator` is an allocator, `S` is a sender, `Cont` is a functor that takes no arguments. 36 | 37 | ### Arguments 38 | 39 | - `allocator` - the allocator to use. 40 | - `sender` - the sender to start. 41 | - `continuation` - the functor to call on completion. 42 | 43 | ### Return value 44 | 45 | These functions don't return any value. 46 | 47 | ## Examples 48 | 49 | ```cpp 50 | async::oneshot_event ev; 51 | 52 | async::run([] (async::oneshot_event &ev) -> async::detached { 53 | std::cout << "Coroutine 1" << std::endl; 54 | co_await ev.wait(); 55 | std::cout << "Coroutine 2" << std::endl; 56 | }(ev)); 57 | 58 | std::cout << "Before event raise" << std::endl; 59 | ev.raise(); 60 | std::cout << "After event raise" << std::endl; 61 | ``` 62 | 63 | Output: 64 | ``` 65 | Coroutine 1 66 | Before event raise 67 | Coroutine 2 68 | After event raise 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/src/headers/basic/run.md: -------------------------------------------------------------------------------- 1 | # run and run_forever 2 | 3 | `run` and `run_forever` are top-level functions used for running coroutines. `run` 4 | runs a coroutine until it completes, while `run_forever` runs coroutines indefinitely 5 | via the IO service. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | void run_forever(IoService ios); // (1) 12 | 13 | template 14 | Sender::value_type run(Sender s); // (2) 15 | 16 | template 17 | Sender::value_type run(Sender s, IoService ios); // (3) 18 | ``` 19 | 20 | 1. Run the IO service indefinitely 21 | 2. Start the sender and wait until it completes. The sender **must** complete 22 | inline as there's no way to wait for it to complete. 23 | 3. Same as (2) but the sender can complete not-inline. 24 | 25 | ### Requirements 26 | 27 | `IoService` is an [IO service](io-service.md), and `Sender` is a sender. 28 | 29 | ### Arguments 30 | 31 | - `IoService` - the IO service to use to wait for completion. 32 | - `Sender` - the sender to start. 33 | 34 | ### Return value 35 | 36 | 1. This function does not return. 37 | 2. This function returns the result value obtained from the sender. 38 | 3. Same as (2). 39 | 40 | ## Examples 41 | 42 | ```cpp 43 | int i = async::run([] () -> async::result { 44 | co_return 1; 45 | }()); 46 | std::cout << i << std::endl; 47 | ``` 48 | 49 | Output: 50 | ``` 51 | 1 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/src/headers/basic/sender_awaiter.md: -------------------------------------------------------------------------------- 1 | # sender_awaiter and make_awaiter 2 | 3 | `sender_awaiter` is a coroutine promise type that allows for awaiting a sender. 4 | It connects an internal receiver to the given sender, and starts the resulting 5 | operation when the result is awaited. 6 | 7 | `make_awaiter` is a function that obtains the awaiter associated with a sender. 8 | It does that by getting the result of `operator co_await`. 9 | 10 | ## Prototype 11 | 12 | ```cpp 13 | template 14 | struct [[nodiscard]] sender_awaiter { 15 | sender_awaiter(S sender); // (1) 16 | 17 | bool await_ready(); // (2) 18 | bool await_suspend(std::coroutine_handle<>); // (2) 19 | T await_resume(); // (2) 20 | } 21 | 22 | template 23 | auto make_awaiter(S &&s); // (3) 24 | ``` 25 | 26 | 1. Constructs the object with the given sender. 27 | 2. Coroutine promise methods. 28 | 3. Get the awaiter for the given sender. 29 | 30 | ### Requirements 31 | 32 | `S` is a sender and `T` is `S::value_type` or a type convertible to it. 33 | 34 | ### Arguments 35 | 36 | - `sender` - the sender to await. 37 | 38 | ### Return values 39 | 40 | 1. N/A 41 | 2. These methods return implementation-specific values. 42 | 3. This function returns the result object of `co_await`ing the given object 43 | (without performing the actual asynchronous await operation). 44 | -------------------------------------------------------------------------------- /docs/src/headers/basic/spawn.md: -------------------------------------------------------------------------------- 1 | # spawn_with_allocator 2 | 3 | `spawn_with_allocator` is a function that takes a sender and a receiver, connects 4 | them and detaches in a way similar to [`detach_with_allocator`](headers/basic/detached.md). 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | void spawn_with_allocator(Allocator allocator, S sender, R receiver); 11 | ``` 12 | 13 | ### Requirements 14 | 15 | `Allocator` is an allocator, `S` is a sender, `R` is a receiver. 16 | 17 | ### Arguments 18 | 19 | - `allocator` - the allocator to use. 20 | - `sender` - the sender to start. 21 | - `receiver` - the receiver to use. 22 | 23 | ### Return value 24 | 25 | This function doesn't return any value. 26 | 27 | ## Examples 28 | 29 | ```cpp 30 | struct my_receiver { 31 | void set_value_inline(int value) { 32 | std::cout << "Value: " << value << std::endl; 33 | } 34 | 35 | void set_value_noinline(int value) { 36 | std::cout << "Value: " << value << std::endl; 37 | } 38 | }; 39 | 40 | async::oneshot_event ev; 41 | 42 | async::spawn_with_allocator(frg::stl_allocator{}, 43 | [] (async::oneshot_event &ev) -> async::result { 44 | std::cout << "Start sender" << std::endl; 45 | co_await ev.wait(); 46 | co_return 1; 47 | }(ev), my_receiver{}); 48 | 49 | std::cout << "Before event raise" << std::endl; 50 | ev.raise(); 51 | std::cout << "After event raise" << std::endl; 52 | ``` 53 | 54 | Output: 55 | ``` 56 | Start sender 57 | Before event raise 58 | Value: 1 59 | After event raise 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/src/headers/cancellation.md: -------------------------------------------------------------------------------- 1 | # cancellation 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | This header provides facilities to request and handle cancellation of operations. 8 | -------------------------------------------------------------------------------- /docs/src/headers/cancellation/cancellation_callback.md: -------------------------------------------------------------------------------- 1 | # cancellation_callback and cancellation_observer 2 | 3 | `cancellation_callback` registers a callback that will be invoked when 4 | cancellation occurs. 5 | 6 | `cancellation_observer` checks for cancellation, potentially invoking the handler 7 | and returns whether cancellation occured. 8 | 9 | ## Prototype 10 | 11 | ```cpp 12 | template 13 | struct cancellation_callback { 14 | cancellation_callback(cancellation_token ct, F functor = {}); // (1) 15 | 16 | cancellation_callback(const cancellation_callback &) = delete; 17 | cancellation_callback(cancellation_callback &&) = delete; 18 | 19 | cancellation_callback &operator=(const cancellation_callback &) = delete; 20 | cancellation_callback &operator=(cancellation_callback &&) = delete; 21 | 22 | void unbind(); // (2) 23 | }; 24 | 25 | template 26 | struct cancellation_observer { 27 | cancellation_observer(F functor = {}); // (3) 28 | 29 | cancellation_observer(const cancellation_observer &) = delete; 30 | cancellation_observer(cancellation_observer &&) = delete; 31 | 32 | cancellation_observer &operator=(const cancellation_observer &) = delete; 33 | cancellation_observer &operator=(cancellation_observer &&) = delete; 34 | 35 | bool try_set(cancellation_token ct); // (4) 36 | 37 | bool try_reset(); // (5) 38 | }; 39 | ``` 40 | 41 | 1. Constructs and attaches the cancellation callback to the given token. 42 | 2. Detaches the cancellation callback. 43 | 3. Constructs a cancellation observer. 44 | 4. Attaches the cancellation observer to the given token and checks for 45 | cancellation without invoking the functor. 46 | 5. Checks for cancellation, potentially invoking the functor, and detaches the 47 | cancellation observer. 48 | 49 | ### Requirements 50 | 51 | `F` is a functor that takes no arguments. 52 | 53 | ### Arguments 54 | 55 | - `ct` - the cancellation token to attach to. 56 | - `functor` - the functor to call on cancellation. 57 | 58 | ### Return values 59 | 60 | 1. N/A 61 | 2. This method doesn't return any value. 62 | 3. N/A 63 | 4. Returns `false` if cancellation was already requested and the observer hasn't been 64 | attached, `true` otherwise. 65 | 5. Same as (4). 66 | -------------------------------------------------------------------------------- /docs/src/headers/cancellation/cancellation_event.md: -------------------------------------------------------------------------------- 1 | # cancellation_event and cancellation_token 2 | 3 | `cancellation_event` is a type that is used to request cancellation of an 4 | asynchronous operation. 5 | 6 | `cancellation_token` is a type constructible from a `cancellation_event` that's 7 | used by operations to check for cancellation. 8 | 9 | ## Prototype 10 | 11 | ```cpp 12 | struct cancellation_event { 13 | cancellation_event(const cancellation_event &) = delete; 14 | cancellation_event(cancellation_event &&) = delete; 15 | 16 | cancellation_event &operator=(const cancellation_event &) = delete; 17 | cancellation_event &operator=(cancellation_event &&) = delete; 18 | 19 | void cancel(); // (1) 20 | void reset(); // (2) 21 | }; 22 | 23 | struct cancellation_token { 24 | cancellation_token(); // (3) 25 | cancellation_token(cancellation_event &ev); // (4) 26 | 27 | bool is_cancellation_requested(); // (5) 28 | }; 29 | ``` 30 | 31 | 1. Requests cancellation. 32 | 2. Resets the object to prepare it for reuse. 33 | 3. Default constructs a cancellation token. 34 | 4. Constructs a cancellation token from a cancellation event. 35 | 5. Checks whether cancellation is requested. 36 | 37 | ### Arguments 38 | 39 | - `ev` - the cancellation event to use. 40 | 41 | ### Return values 42 | 43 | 1. This method doesn't return any value. 44 | 2. Same as (1). 45 | 3. N/A 46 | 4. N/A 47 | 5. Returns `true` if cancellation was requested, `false` otherwise. 48 | 49 | ## Example 50 | 51 | ```cpp 52 | async::run([] () -> async::result { 53 | async::queue q; 54 | 55 | async::cancellation_event ce; 56 | ce.cancel(); // Request cancellation immediately 57 | 58 | auto v = q.async_get(ce); 59 | if (!v) 60 | std::cout << "Cancelled" << std::endl; 61 | else 62 | std::cout << "Completed: " << *v << std::endl; 63 | }()); 64 | ``` 65 | 66 | Output: 67 | 68 | ``` 69 | Cancelled 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/src/headers/cancellation/suspend_indefinitely.md: -------------------------------------------------------------------------------- 1 | # suspend_indefinitely 2 | 3 | `suspend_indefinitely` is an operation that suspends until it is cancelled. 4 | 5 | ## Prototype 6 | 7 | ```cpp 8 | sender suspend_indefinitely(cancellation_token cancellation); 9 | ``` 10 | 11 | ### Arguments 12 | 13 | - `cancellation` - the cancellation token to use. 14 | 15 | ### Return value 16 | 17 | This function returns a sender of unspecified type. The sender doesn't return any 18 | value, and completes when cancellation is requested. 19 | 20 | ## Examples 21 | 22 | ```cpp 23 | async::cancellation_event ce; 24 | 25 | auto coro = [] (async::cancellation_token ct) -> async::detached { 26 | std::cout << "Before await" << std::endl; 27 | co_await async::suspend_indefinitely(ct); 28 | std::cout << "After await" << std::endl; 29 | }; 30 | 31 | coro(ce); 32 | std::cout << "Before cancel" << std::endl; 33 | ce.cancel(); 34 | std::cout << "After cancel" << std::endl; 35 | ``` 36 | 37 | Output: 38 | ``` 39 | Before await 40 | Before cancel 41 | After await 42 | After cancel 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/src/headers/execution.md: -------------------------------------------------------------------------------- 1 | # execution 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | The following objects and type definitions are in the `async::execution` namespace. 8 | 9 | This header contains customization point objects (CPOs) for the following 10 | methods/functions: 11 | - `connect` (as a member or function), 12 | - `start` (as a member or function), 13 | - `start_inline` (as a member), 14 | - `set_value` (as a member), 15 | - `set_value_inline` (as a member), 16 | - `set_value_noinline` (as a member). 17 | 18 | In addition to that, it provides a convenience type definition for working with operations: 19 | ```cpp 20 | template 21 | using operation_t = std::invoke_result_t; 22 | ``` 23 | 24 | ## Examples 25 | 26 | ```cpp 27 | auto op = async::execution::connect(my_sender, my_receiver); 28 | bool finished_inline = async::execution::start_inline(op); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/src/headers/mutex.md: -------------------------------------------------------------------------------- 1 | # mutex 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | This header provides asynchronous mutex types. 8 | -------------------------------------------------------------------------------- /docs/src/headers/mutex/mutex.md: -------------------------------------------------------------------------------- 1 | # mutex 2 | 3 | `mutex` is a mutex which supports asynchronous acquisition. When the mutex is 4 | contended, the operation asynchronously blocks until the current holder 5 | releases it. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | struct mutex { 11 | sender async_lock(); // (1) 12 | 13 | bool try_lock(); // (2) 14 | 15 | void unlock(); // (3) 16 | }; 17 | ``` 18 | 19 | 1. Asynchronously acquire the mutex. 20 | 2. Synchronously try to acquire the mutex. 21 | 3. Release the mutex. 22 | 23 | ### Return values 24 | 1. This method returns a sender of unspecified type. The sender does not return 25 | any value, and completes once the mutex is acquired. 26 | 2. This method returns `true` if the mutex was successfully acquired, `false` otherwise. 27 | 3. This method doesn't return any value. 28 | 29 | ## Examples 30 | 31 | ```cpp 32 | async::mutex mtx; 33 | async::queue q; 34 | 35 | auto coro = [] (int i, auto &mtx, auto &q) -> async::detached { 36 | std::cout << i << ": taking" << std::endl; 37 | co_await mtx.async_lock(); 38 | std::cout << i << ": " << mtx.try_lock() << std::endl; 39 | co_await q.async_get(); 40 | std::cout << i << ": releasing" << std::endl; 41 | mtx.unlock(); 42 | }; 43 | 44 | coro(1, mtx, q); 45 | coro(2, mtx, q); 46 | 47 | q.put(1); 48 | q.put(2); 49 | ``` 50 | 51 | Output: 52 | ``` 53 | 1: taking 54 | 1: 0 55 | 2: taking 56 | 1: releasing 57 | 2: 0 58 | 2: releasing 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/src/headers/mutex/shared_mutex.md: -------------------------------------------------------------------------------- 1 | # shared_mutex 2 | 3 | `shared_mutex` is a mutex which supports asynchronous acquisition. It can be 4 | acquired in either shared mode, or exclusive mode. Acquiring in exclusive mode 5 | blocks until all shared owners, or the current exclusive owner release the mutex, 6 | and acquiring in shared mode only blocks if it's currently owned exclusively. 7 | When the mutex is contended, the operation asynchronously blocks until the current 8 | holder releases it. These are also known as read-write mutexes, where shared mode 9 | is a read, and exclusive mode is a write. 10 | 11 | ## Prototype 12 | 13 | ```cpp 14 | struct shared_mutex { 15 | sender async_lock(); // (1) 16 | 17 | sender async_lock_shared(); // (2) 18 | 19 | void unlock(); // (3) 20 | 21 | void unlock_shared(); // (4) 22 | }; 23 | ``` 24 | 25 | 1. Asynchronously acquire the mutex in exclusive mode. 26 | 1. Asynchronously acquire the mutex in shared mode. 27 | 3. Release the mutex (mutex must be in exclusive mode). 28 | 3. Release the mutex (mutex must be in shared mode). 29 | 30 | ### Return values 31 | 1. This method returns a sender of unspecified type. The sender does not return 32 | any value, and completes once the mutex is acquired. 33 | 2. Same as (1). 34 | 3. This method doesn't return any value. 35 | 4. Same as (3). 36 | 37 | ## Examples 38 | 39 | ```cpp 40 | async::shared_mutex mtx; 41 | async::queue q; 42 | 43 | auto coro_shared = [] (int i, auto &mtx, auto &q) -> async::detached { 44 | std::cout << i << ": taking shared" << std::endl; 45 | co_await mtx.async_lock_shared(); 46 | std::cout << i << ": acquired shared" << std::endl; 47 | co_await q.async_get(); 48 | std::cout << i << ": releasing shared" << std::endl; 49 | mtx.unlock_shared(); 50 | }; 51 | 52 | auto coro_exclusive = [] (int i, auto &mtx, auto &q) -> async::detached { 53 | std::cout << i << ": taking exclusive" << std::endl; 54 | co_await mtx.async_lock(); 55 | std::cout << i << ": acquired exclusive" << std::endl; 56 | co_await q.async_get(); 57 | std::cout << i << ": releasing exclusive" << std::endl; 58 | mtx.unlock(); 59 | }; 60 | 61 | coro_shared(1, mtx, q); 62 | coro_shared(2, mtx, q); 63 | coro_exclusive(3, mtx, q); 64 | coro_exclusive(4, mtx, q); 65 | 66 | q.put(1); 67 | q.put(2); 68 | q.put(3); 69 | q.put(4); 70 | ``` 71 | 72 | Output: 73 | ``` 74 | 1: taking shared 75 | 1: acquired shared 76 | 2: taking shared 77 | 2: acquired shared 78 | 3: taking exclusive 79 | 4: taking exclusive 80 | 1: releasing shared 81 | 2: releasing shared 82 | 3: acquired exclusive 83 | 3: releasing exclusive 84 | 4: acquired exclusive 85 | 4: releasing exclusive 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/src/headers/oneshot-event.md: -------------------------------------------------------------------------------- 1 | # oneshot_event 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `oneshot_event` is an event type that can be only raised once, and supports 8 | multiple waiters. After the initial raise, any further waits will complete 9 | immediately, and further raises will be a no-op. 10 | 11 | ## Prototype 12 | 13 | ```cpp 14 | struct oneshot_event { 15 | void raise(); // (1) 16 | 17 | sender wait(cancellation_token ct); // (2) 18 | sender wait(); // (3) 19 | }; 20 | ``` 21 | 22 | 1. Raises an event. 23 | 2. Returns a sender for the wait operation. The operation waits for the event 24 | to be raised. 25 | 3. Same as (2) but it cannot be cancelled. 26 | 27 | ### Arguments 28 | 29 | - `ct` - the cancellation token to use to listen for cancellation. 30 | 31 | ### Return values 32 | 33 | 1. This method doesn't return any value. 34 | 2. This method returns a sender of unspecified type. The sender completes with 35 | either `true` to indicate success, or `false` to indicate that the wait was cancelled. 36 | 3. Same as (2) except the sender completes without a value. 37 | 38 | ## Examples 39 | 40 | ```cpp 41 | async::oneshot_event ev; 42 | 43 | auto coro = [] (async::oneshot_event &ev) -> async::detached { 44 | std::cout << "Before wait" << std::endl; 45 | co_await ev.wait(); 46 | std::cout << "After wait" << std::endl; 47 | }; 48 | 49 | coro(ev); 50 | std::cout << "Before raise" << std::endl; 51 | ev.raise(); 52 | std::cout << "After raise" << std::endl; 53 | coro(ev); 54 | ``` 55 | 56 | Output: 57 | ``` 58 | Before wait 59 | Before raise 60 | After wait 61 | After raise 62 | Before wait 63 | After wait 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/src/headers/post-ack.md: -------------------------------------------------------------------------------- 1 | # post-ack 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | This header provides classes for a producer-consumer data structure that requires 8 | every consumer to acknowledge the value before the producer can continue. 9 | 10 | ## Examples 11 | 12 | ```cpp 13 | async::post_ack_mechanism mech; 14 | 15 | auto producer = [] (async::post_ack_mechanism &mech) -> async::detached { 16 | std::cout << "Posting 1" << std::endl; 17 | co_await mech.post(1); 18 | std::cout << "Posting 2" << std::endl; 19 | co_await mech.post(2); 20 | }; 21 | 22 | auto consumer = [] (async::post_ack_mechanism &mech) -> async::detached { 23 | async::post_ack_agent agent; 24 | agent.attach(&mech); 25 | 26 | std::cout << "Awaiting first value" << std::endl; 27 | auto handle = co_await agent.poll(); 28 | std::cout << *handle << ", acking first value" << std::endl; 29 | handle.ack(); 30 | 31 | std::cout << "Awaiting second value" << std::endl; 32 | handle = co_await agent.poll(); 33 | std::cout << *handle << ", acking second value" << std::endl; 34 | handle.ack(); 35 | 36 | agent.detach(); 37 | }; 38 | 39 | consumer(mech); 40 | consumer(mech); 41 | consumer(mech); 42 | 43 | producer(mech); 44 | ``` 45 | 46 | Output: 47 | ``` 48 | Awaiting first value 49 | Awaiting first value 50 | Awaiting first value 51 | Posting 1 52 | 1, acking first value 53 | Awaiting second value 54 | 1, acking first value 55 | Awaiting second value 56 | 1, acking first value 57 | Posting 2 58 | 2, acking second value 59 | 2, acking second value 60 | Awaiting second value 61 | 2, acking second value 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/src/headers/post-ack/post_ack_agent.md: -------------------------------------------------------------------------------- 1 | # post_ack_agent 2 | 3 | `post_ack_agent` is the consumer class of post-ack. It can asynchronously poll 4 | for a value produced by the observed [post_ack_mechanism](headers/post-ack/post_ack_mechanism.md). 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | struct post_ack_agent { 11 | void attach(post_ack_mechanism *mech); // (1) 12 | 13 | void detach(); // (2) 14 | 15 | sender poll(cancellation_token ct = {}); // (3) 16 | }; 17 | ``` 18 | 19 | 1. Attach the agent (consumer) to a mechanism (producer). 20 | 2. Detach the agent from the mechanism. 21 | 1. Asynchronously poll for a value. 22 | 23 | ### Requirements 24 | 25 | `T` is moveable. 26 | 27 | ### Arguments 28 | 29 | - `mech` - the mechanism to attach to. 30 | - `ct` - the cancellation token to use. 31 | 32 | ### Return values 33 | 34 | 1. This method doesn't return any value. 35 | 2. This method doesn't return any value. 36 | 3. This method returns a sender of unspecified type. The sender returns a value 37 | of type `post_ack_handle`. 38 | -------------------------------------------------------------------------------- /docs/src/headers/post-ack/post_ack_handle.md: -------------------------------------------------------------------------------- 1 | # post_ack_handle 2 | 3 | `post_ack_handle` is the value handle class of post-ack. It has methods to obtain 4 | a pointer to the value and to acknowledge the value. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | struct post_ack_handle { 11 | friend void swap(post_ack_handle &a, post_ack_handle &b); // (1) 12 | 13 | explicit post_ack_handle() = default; // (2) 14 | 15 | post_ack_handle(const post_ack_handle &) = delete; 16 | post_ack_handle(post_ack_handle &&); // (3) 17 | 18 | post_ack_handle &operator=(const post_ack_handle &) = delete; 19 | post_ack_handle &operator=(post_ack_handle &&); // (3) 20 | 21 | void ack(); // (4) 22 | 23 | explicit operator bool (); // (5) 24 | 25 | T *operator-> (); // (6) 26 | 27 | T &operator* (); // (6) 28 | }; 29 | ``` 30 | 31 | 1. Swaps two handles. 32 | 2. Constructs an empty handle. 33 | 3. Moves a handle. 34 | 4. Acknowledges the value. 35 | 5. Checks if the handle is valid. 36 | 6. Gets the stored object. 37 | 38 | ### Requirements 39 | 40 | `T` is moveable. 41 | 42 | ### Return values 43 | 44 | 1. This function doesn't return any value. 45 | 2. N/A 46 | 3. N/A for constructor, reference to the handle for assignment operator. 47 | 4. This method doesn't return any value. 48 | 5. This method returns `true` if the handle isn't empty, `false` otherwise. 49 | 6. These methods return a pointer/reference to the handle. 50 | -------------------------------------------------------------------------------- /docs/src/headers/post-ack/post_ack_mechanism.md: -------------------------------------------------------------------------------- 1 | # post_ack_mechanism 2 | 3 | `post_ack_mechanism` is the producer class of post-ack. It can asynchronously 4 | post a value, which will only complete once all consumers acknowledge the value. 5 | 6 | ## Prototype 7 | 8 | ```cpp 9 | template 10 | struct post_ack_mechanism { 11 | sender post(T object); // (1) 12 | }; 13 | ``` 14 | 15 | 1. Asynchronously post a value. 16 | 17 | ### Requirements 18 | 19 | `T` is moveable. 20 | 21 | ### Arguments 22 | 23 | - `object` - the value to post. 24 | 25 | ### Return values 26 | 27 | 1. This method returns a sender of unspecified type. The sender doesn't return any value. 28 | -------------------------------------------------------------------------------- /docs/src/headers/promise.md: -------------------------------------------------------------------------------- 1 | # promise 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | This header provides promise and future types. 8 | 9 | ## Examples 10 | 11 | ```cpp 12 | async::future future; 13 | { 14 | async::promise promise; 15 | future = promise.get_future(); 16 | 17 | promise.set_value(3); 18 | } 19 | 20 | std::cout << *async::run(future.get()) << std::endl; 21 | ``` 22 | 23 | Output: 24 | ``` 25 | 3 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/src/headers/promise/future.md: -------------------------------------------------------------------------------- 1 | # future 2 | 3 | `future` is a type that can be used to await a value from the promise. The 4 | stored data is only destroyed when the promise, and all associated futures 5 | go out of scope. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | struct future { 12 | friend void swap(future &a, future &b); // (1) 13 | 14 | future(Allocator allocator = {}); // (2) 15 | 16 | future(const future &); // (3) 17 | future(future &&); // (4) 18 | 19 | future &operator=(const future &); // (3) 20 | future &operator=(future &&); // (4) 21 | 22 | sender get(cancellation_token ct); // (5) 23 | sender get(); // (6) 24 | 25 | bool valid(); // (7) 26 | operator bool (); // (7) 27 | }; 28 | ``` 29 | 30 | 1. Swaps two futures. 31 | 2. Constructs an empty future. 32 | 3. Copies a future. The copy of the future refers to the same state as the 33 | original object in a manner similar to a `shared_ptr`. 34 | 4. Moves a future. 35 | 5. Asynchronously obtains the value. 36 | 6. Same as (5). 37 | 7. Checks whether the future has valid state. 38 | 39 | ### Requirements 40 | 41 | `T` is any type. `Allocator` is an allocator. 42 | 43 | ### Arguments 44 | 45 | - `ct` - the cancellation token to use. 46 | 47 | ### Return values 48 | 49 | 1. This function doesn't return any value. 50 | 2. N/A 51 | 3. N/A for constructor, reference to this object for assignment operator. 52 | 4. Same as (3). 53 | 5. This method returns a sender of unspecified type. If `T` is not `void`, the 54 | sender returns a value of type `frg::optional`. It returns the pointer if 55 | the value was obtained successfully, `frg::null_opt` if the operation was cancelled. 56 | If `T` is `void`, the sender returns `true` if the value was obtained successfully, 57 | `false` if the operation was cancelled. 58 | 6. This method returns a sender of unspecified type. If `T` is not `void`, the 59 | sender returns a pointer to the stored value. If `T` is `void`, it doesn't return anything. 60 | 7. These methods return `true` if the future has valid state, `false` otherwise. 61 | -------------------------------------------------------------------------------- /docs/src/headers/promise/promise.md: -------------------------------------------------------------------------------- 1 | # promise 2 | 3 | `promise` is a type that can be used to set a value that can be awaited via a `future`. 4 | The stored data is only destroyed when the promise, and all associated futures go out 5 | of scope. A promise is movable and non-copyable. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | struct promise { 12 | future get_future(); // (1) 13 | 14 | template 15 | void set_value(U &&v); // (2) 16 | void set_value(); // (3) 17 | }; 18 | ``` 19 | 20 | 1. Obtains the future associated with this promise. This can be called multiple times to 21 | get multiple futures. 22 | 2. Emplaces a value into the promise (overload for `T` other than `void`). 23 | 3. Same as (2), except `T` must be `void`. 24 | 25 | ### Requirements 26 | 27 | `T` is any type. `Allocator` is an allocator. `T` is constructible from `U`. 28 | 29 | ### Arguments 30 | 31 | - `v` - the value to emplace. 32 | 33 | ### Return values 34 | 35 | 1. This method returns a future object associated with the promise. 36 | 2. This method doesn't return any value. 37 | 3. Same as (2). 38 | -------------------------------------------------------------------------------- /docs/src/headers/queue.md: -------------------------------------------------------------------------------- 1 | # queue 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `queue` is a type which provides a queue on which you can asynchronously wait 8 | for items to appear. 9 | 10 | ## Prototype 11 | 12 | ```cpp 13 | template 14 | struct queue { 15 | queue(Allocator allocator = {}); // (1) 16 | 17 | void put(T item); // (2) 18 | 19 | template 20 | void emplace(Ts &&...ts); // (3) 21 | 22 | sender async_get(cancellation_token ct = {}); // (4) 23 | 24 | frg::optional maybe_get() // (5) 25 | }; 26 | ``` 27 | 28 | 1. Constructs a queue with the given allocator. 29 | 2. Inserts an item into the queue. 30 | 3. Emplaces an item into the queue. 31 | 4. Returns a sender for the get operation. The operation waits for an item to be 32 | inserted and returns it. 33 | 5. Pops and returns the top item if it exists, or `frg::null_opt` otherwise. 34 | 35 | ### Requirements 36 | 37 | `T` is moveable. `Allocator` is an allocator. 38 | 39 | 3. `T` is constructible with `Ts`. 40 | 41 | ### Arguments 42 | - `allocator` - the allocator to use. 43 | - `item` - the item to insert into the queue. 44 | - `ts` - the arguments to pass to the constructor of `T` when inserting it into the queue. 45 | - `ct` - the cancellation token to use. 46 | 47 | ### Return values 48 | 49 | 1. N/A 50 | 2. This method doesn't return any value. 51 | 3. Same as (2). 52 | 4. This method returns a sender of unspecified type. The sender returns a 53 | `frg::optional` and completes with the value, or `frg::null_opt` if the 54 | operation was cancelled. 55 | 5. This method returns a value of type `frg::optional`. It returns a value 56 | from the queue, or `frg::null_opt` if the queue is empty. 57 | 58 | ## Examples 59 | 60 | ```cpp 61 | auto coro = [] (async::queue &q) -> async::detached { 62 | std::cout << "Got " << *(co_await q.async_get()) << std::endl; 63 | std::cout << "Got " << *(co_await q.async_get()) << std::endl; 64 | }; 65 | 66 | async::queue q; 67 | 68 | coro(q); 69 | 70 | q.put(1); 71 | q.put(2); 72 | ``` 73 | 74 | Output: 75 | ``` 76 | 1 77 | 2 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/src/headers/recurring-event.md: -------------------------------------------------------------------------------- 1 | # recurring_event 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `recurring_event` is an event type that can be raised multiple times, and supports 8 | multiple waiters. On raise, all waiters are woken up sequentially. 9 | 10 | ## Prototype 11 | 12 | ```cpp 13 | struct recurring_event { 14 | void raise(); // (1) 15 | 16 | template 17 | sender async_wait_if(C cond, cancellation_token ct = {}); // (2) 18 | sender async_wait(cancellation_token ct = {}); // (3) 19 | }; 20 | ``` 21 | 22 | 1. Raises an event. 23 | 2. Returns a sender for the wait operation. The operation checks the condition, and 24 | if it's true, waits for the event to be raised. 25 | 3. Same as (2) but without the condition. 26 | 27 | ### Requirements 28 | 29 | `C` is a functor that accepts no arguments and returns a truthy or falsy value. 30 | 31 | ### Arguments 32 | 33 | - `ct` - the cancellation token to use to listen for cancellation. 34 | 35 | ### Return values 36 | 37 | 1. This method doesn't return any value. 38 | 2. This method returns a sender of unspecified type. The sender completes with either 39 | `true` to indicate success, or `false` to indicate that the wait was cancelled, or that 40 | the condition was false. 41 | 3. Same as (2) 42 | 43 | ## Examples 44 | 45 | ```cpp 46 | async::recurring_event ev; 47 | 48 | auto coro = [] (int i, async::recurring_event &ev) -> async::detached { 49 | std::cout << i << ": Before wait" << std::endl; 50 | co_await ev.async_wait(); 51 | std::cout << i << ": After wait" << std::endl; 52 | }; 53 | 54 | coro(1, ev); 55 | coro(2, ev); 56 | std::cout << "Before raise" << std::endl; 57 | ev.raise(); 58 | std::cout << "After raise" << std::endl; 59 | coro(3, ev); 60 | ``` 61 | 62 | Possible output: 63 | ``` 64 | 1: Before wait 65 | 2: Before wait 66 | Before raise 67 | 1: After wait 68 | 2: After wait 69 | After raise 70 | 3: Before wait 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/src/headers/result.md: -------------------------------------------------------------------------------- 1 | # result 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `result` is a generic coroutine promise and sender type. It it used for coroutines 8 | for which you need to await the result of. 9 | 10 | ## Prototype 11 | 12 | ```cpp 13 | template 14 | struct result; 15 | ``` 16 | 17 | ### Requirements 18 | 19 | `T` is the type of the value returned by the coroutine. 20 | 21 | ### Examples 22 | 23 | ```cpp 24 | async::result coro1(int i) { 25 | co_return i + 1; 26 | } 27 | 28 | async::result coro2(int i) { 29 | co_return i * co_await coro1(i); 30 | } 31 | 32 | int main() { 33 | std::cout << async::run(coro2(5)) << std::endl; 34 | } 35 | ``` 36 | 37 | Output: 38 | ``` 39 | 30 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/src/headers/sequenced-event.md: -------------------------------------------------------------------------------- 1 | # sequenced_event 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `sequenced_event` is an event type that can be raised multiple times, and supports 8 | multiple waiters. On raise, all waiters are woken up sequentially. The event 9 | maintains a sequence counter to detect missed raises. 10 | 11 | ## Prototype 12 | 13 | ```cpp 14 | struct sequenced_event { 15 | void raise(); // (1) 16 | 17 | uint64_t next_sequence(); // (2) 18 | 19 | sender async_wait(uint64_t in_seq, async::cancellation_token ct = {}); // (3) 20 | }; 21 | ``` 22 | 23 | 1. Raises an event. 24 | 2. Returns the next sequence number for the event (the sequence number after the 25 | event is raised). 26 | 3. Returns a sender for the wait operation. The operation checks whether the input 27 | sequence is equal to the event sequeence, and if it's true, waits for the event to be raised. 28 | 29 | ### Arguments 30 | 31 | - `in_seq` - input sequence number to compare against the event sequence number. 32 | - `ct` - the cancellation token to use to listen for cancellation. 33 | 34 | ### Return values 35 | 36 | 1. This method doesn't return any value. 37 | 2. This method returns the next sequence number for the event (the sequence number 38 | after the event is raised). 39 | 3. This method returns a sender of unspecified type. The sender completes with the 40 | current event sequence number. 41 | 42 | ## Examples 43 | 44 | ```cpp 45 | async::sequenced_event ev; 46 | 47 | std::cout << ev.next_sequence() << std::endl; 48 | ev.raise(); 49 | std::cout << ev.next_sequence() << std::endl; 50 | 51 | auto seq = async::run( 52 | [] (async::sequenced_event &ev) -> async::result { 53 | // Current sequence is 1, so waiting at sequence 0 will immediately complete. 54 | co_return co_await ev.async_wait(0); 55 | }(ev)); 56 | 57 | std::cout << seq << std::endl; 58 | ``` 59 | 60 | Output: 61 | ``` 62 | 1 63 | 2 64 | 1 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/src/headers/wait-group.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managarm/libasync/4b85bfffd77507f57e2cea765d849003b7395952/docs/src/headers/wait-group.html -------------------------------------------------------------------------------- /docs/src/headers/wait-group/wait_group.md: -------------------------------------------------------------------------------- 1 | # wait\_group 2 | 3 | ```cpp 4 | #include 5 | ``` 6 | 7 | `wait_group` is a synchronization primitive that waits for a counter (that can 8 | be incremented) to reach zero. It conceptually maps to a group of related work 9 | being done in parallel, and a few consumers waiting for that work to be done. 10 | The amount of work is increased by calls to `add()` and decreased by calls to 11 | `done()`. 12 | 13 | This struct also implements 14 | [BasicLockable](https://en.cppreference.com/w/cpp/named_req/BasicLockable) so 15 | that it can be used with `std::unique_lock`. 16 | 17 | ## Prototype 18 | 19 | ```cpp 20 | struct wait_group { 21 | void done(); // (1) 22 | void add(int n); // (2) 23 | 24 | sender wait(cancellation_token ct); // (3) 25 | sender wait(); // (4) 26 | 27 | void lock(); // (5) 28 | void unlock(); // (6) 29 | }; 30 | ``` 31 | 32 | 1. "Finishes" a work (decrements the work count). 33 | 2. "Adds" more work (increments the work count by `n`). 34 | 3. Returns a sender for the wait operation. The operation waits for the counter 35 | to drop to zero. 36 | 4. Same as (3) but it cannot be cancelled. 37 | 5. Equivalent to `add(1)`. 38 | 5. Equivalent to `done()`. 39 | 40 | ### Arguments 41 | 42 | - `n` - amount of work to "add" to this work group 43 | - `ct` - the cancellation token to use to listen for cancellation. 44 | 45 | ### Return values 46 | 47 | 1. This method doesn't return any value. 48 | 2. This method doesn't return any value. 49 | 3. This method returns a sender of unspecified type. The sender completes with 50 | either `true` to indicate success, or `false` to indicate that the wait was cancelled. 51 | 4. Same as (3) except the sender completes without a value. 52 | 53 | ## Examples 54 | 55 | ```cpp 56 | async::wait_group wg { 3 }; 57 | 58 | ([&wg] () -> async::detached { 59 | std::cout << "before wait" << std::endl; 60 | co_await wg.wait(); 61 | std::cout << "after wait" << std::endl; 62 | })(); 63 | 64 | auto done = [&wg] () { 65 | std::cout << "before done" << std::endl; 66 | wg.done(); 67 | std::cout << "after done" << std::endl; 68 | }; 69 | 70 | done(); 71 | done(); 72 | std::cout << "before add" << std::endl; 73 | wg.add(2); 74 | std::cout << "after add" << std::endl; 75 | done(); 76 | done(); 77 | done(); 78 | ``` 79 | 80 | Output: 81 | 82 | ``` 83 | before wait 84 | before done 85 | after done 86 | before done 87 | after done 88 | before add 89 | after add 90 | before done 91 | after done 92 | before done 93 | after done 94 | before done 95 | after wait 96 | after done 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/src/headers/wait-group/wait_in_group.md: -------------------------------------------------------------------------------- 1 | # wait\_in\_group 2 | 3 | `wait_in_group(wg, S)` takes a sender `S` and adds it to the work group `wg` 4 | (calls `wg.add(1)`) immediately before it's started and marks it as done 5 | (calls `wg.done()`) immediately after. 6 | 7 | ## Prototype 8 | 9 | ```cpp 10 | template 11 | sender wait_in_group(wait_group &wg, S sender); 12 | ``` 13 | 14 | ## Requirements 15 | `S` is a sender. 16 | 17 | ## Arguments 18 | - `wg` - wait group to wait in 19 | - `sender` - sender to wrap in the wait group 20 | 21 | ## Return value 22 | The value produced by the `sender`. 23 | 24 | ## Examples 25 | 26 | ```cpp 27 | bool should_run() { 28 | /* ... */ 29 | } 30 | async::result handle_conn(tcp_socket conn) { 31 | /* ... */ 32 | } 33 | 34 | /* ... */ 35 | 36 | tcp_socket server; 37 | server.bind(":80"); 38 | server.listen(32); 39 | async::wait_group handlers { 0 }; 40 | while (should_run()) { 41 | auto conn = socket.accept(); 42 | async::detach(async::wait_in_group(handlers, handle_conn(std::move(conn)))); 43 | } 44 | 45 | /* wait for all connections to terminate */ 46 | handlers.wait(); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # libasync 2 | libasync is an async primitives library for C++20. It is one of the libraries 3 | powering the [managarm](https://managarm.org) project. 4 | 5 | libasync is built to be portable to different platforms, hosted or freestanding. 6 | 7 | Docs permalink: 8 | 9 | ## Projects using libasync 10 | 11 | - [managarm](https://managarm.org) - Pragmatic microkernel-based OS with fully asynchronous I/O. libasync is used both in user-space, and in the kernel. 12 | -------------------------------------------------------------------------------- /docs/src/io-service.md: -------------------------------------------------------------------------------- 1 | # IO service 2 | 3 | The IO service is an user-provided class which manages waiting for events and 4 | waking up coroutines/operations based on them. 5 | 6 | The IO service must provide one method: `void wait()`. This method is called when 7 | there is no more work to do currently. It waits for any event to happen, and wakes 8 | up the appropriate coroutine/operation which awaited the event. 9 | 10 | **Note:** `async::run` and `async::run_forever` (see [here](headers/basic/run.md#prototype)) 11 | take the IO service by value, not by reference. 12 | 13 | ## Example 14 | 15 | The following example shows the approximate call graph executing an event-loop-driven coroutine would take: 16 | 17 | - `async::run(my_sender, my_io_service)` 18 | - `my_operation = async::execution::connect(my_sender, internal_receiver)` 19 | - `async::execution::start_inline(my_operation)` 20 | - `my_operation` starts running... 21 | - `co_await some_ev` 22 | - `some_ev` operation is started 23 | - `my_io_service.add_waiter(this)` 24 | - (`async::execution::start_inline` returns `false`) 25 | - `my_io_service.wait()` 26 | - IO service waits for event to happen... 27 | - `waiters_.front()->complete()` 28 | - `some_ev` operation completes 29 | - `my_operation` resumes 30 | - `co_return 2` 31 | - `async::execution::set_value_noinline(internal_receiver, 2)` 32 | - `return internal_receiver.value` 33 | - (`async::run` returns `2`) 34 | -------------------------------------------------------------------------------- /docs/src/sender-receiver.md: -------------------------------------------------------------------------------- 1 | # Senders, receivers, and operations 2 | 3 | libasync is in part built around the concept of senders and receivers. Using senders 4 | and receiver allows for allocation-free operation where otherwise an allocation 5 | would be necessary for the coroutine frame. For example, all of the algorithms 6 | and other asynchronous operations are written using them. 7 | 8 | ## Concepts 9 | 10 | ### Sender 11 | 12 | A sender is an object that holds all the necessary arguments and knows how to 13 | start an asynchronous operation. It is moveable. 14 | 15 | Every sender must have an appropriate `connect` member method or function overload 16 | that accepts a receiver and is used to form the operation. 17 | 18 | ### Operation 19 | 20 | An operation is an object that stores the necessary state and handles the actual 21 | asynchronous operation. It is immovable, and as such pointers to it will remain 22 | valid for as long as the operation exists. When the operation is finished, it 23 | notifies the receiver and optionally passes it a result value. 24 | 25 | Every operation must either have a `void start()` or a `bool start_inline()` method 26 | that is invoked when the operation is first started. `void start()` is equivalent to 27 | `bool start_inline()` with `return false;` at the end. 28 | 29 | #### Inline and no-inline completion 30 | 31 | Operations that complete synchronously can signal inline completion. If an operation 32 | completes inline, it sets the value using `set_value_inline`, and returns `true` from 33 | `start_inline` (Operations that have a `start` method cannot complete inline). Inline 34 | completion allows for certain optimizations, like avoiding suspending the coroutine 35 | if the operation completed synchronously. 36 | 37 | ### Receiver 38 | 39 | A receiver is an object that knows what to do after an operation finishes (e.g. how to 40 | resume the coroutine). It optionally receives a result value from the operation. 41 | It is moveable. 42 | 43 | Every receiver must have `void set_value_inline(...)` and `void set_value_noinline(...)` 44 | methods that are invoked by the operation when it completes. 45 | -------------------------------------------------------------------------------- /docs/src/wait-group.md: -------------------------------------------------------------------------------- 1 | # wait-group 2 | 3 | This header includes utilities related to the `wait_group` primitive. 4 | 5 | ```cpp 6 | #include 7 | ``` 8 | 9 | Wait groups are synchronization primitives that wait for a counter to reach 10 | zero. This counter is conceptually bound to a group of related work that the 11 | consumer would like to simultaneously wait for (hence the name wait groups). 12 | -------------------------------------------------------------------------------- /docs/src/your-own-sender.md: -------------------------------------------------------------------------------- 1 | --- 2 | short-description: Brief overview of a custom sender and operation 3 | ... 4 | 5 | # Writing your own sender and operation 6 | 7 | While libasync provides a veriaty of existing types, one may wish to write their 8 | own senders and operations. The most common reason for writing a custom sender 9 | and operation is wrapping around an existing callback or event based API while 10 | avoiding extra costs induced by allocating coroutine frames. The following section 11 | goes into detail on how to implement them by implementing a simple wrapper for libuv's `uv_write`. 12 | 13 | ## End effect 14 | 15 | The sender and operation we'll implement here will allow us to do the following: 16 | ```cpp 17 | auto result = co_await write(my_handle, my_bufs, n_my_bufs); 18 | if (result < 0) 19 | /* report error */ 20 | ``` 21 | 22 | ## The implementation 23 | 24 | ### The sender 25 | 26 | As explained in [](sender-receiver.md), the sender is an object that stores the 27 | neceessary state to start an operation. Let's start off by looking at the 28 | prototype for `uv_write`: 29 | ```cpp 30 | int uv_write(uv_write_t *req, uv_stream_t *handle, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb); 31 | ``` 32 | 33 | The function takes some object that stores the state (not to be confused with our 34 | operation object), a handle to the stream, buffers to write and a callback. The 35 | callback is also given a status code, which we will propagate back to the user. 36 | The state and callback will be handled by our operation, which leaves only the 37 | handle, buffers and status code. 38 | 39 | Let's start by writing a simple class: 40 | ```cpp 41 | struct [[nodiscard]] write_sender { 42 | using value_type = int; // Status code 43 | 44 | uv_stream_t *handle; 45 | const uv_buf_t *bufs; 46 | size_t nbufs; 47 | }; 48 | ``` 49 | 50 | As can be seen, the sender class is quite simple. The `nodiscard` attribute only 51 | helps catch errors caused by accidentally ignoring the sender without awaiting 52 | it and can be omitted. 53 | 54 | Next we add a simple function used to construct the sender: 55 | ```cpp 56 | write_sender write(uv_stream_t *handle, const uv_buf_t *bufs, size_t nbufs) { 57 | return write_sender{handle, bufs, nbufs}; 58 | } 59 | ``` 60 | 61 | In addition to that, we need the `connect` overload: 62 | ```cpp 63 | template 64 | write_operation connect(write_sender s, Receiver r) { 65 | return {s, std::move(r)}; 66 | } 67 | ``` 68 | 69 | `connect` simply constructs an operation from the sender and receiver. 70 | 71 | We also add an implementation of `operator co_await` for our class so that we 72 | can `co_await` it inside of a coroutine: 73 | ```cpp 74 | async::sender_awaiter 75 | operator co_await(write_sender s) { 76 | return {s}; 77 | } 78 | ``` 79 | 80 | [`async::sender_awaiter`](headers/basic/sender_awaiter.md) is a special type 81 | that can suspend and resume the coroutine, and internally connects a receiver 82 | to our sender. 83 | 84 | ### The operation 85 | 86 | With the sender done, what remains to be written is the operation. As noted earlier, 87 | the operation is constructed using the sender and receiver, and it stores the 88 | operation state. As such, we want each call to `write` to have an unique operation 89 | object. Let's start by writing a skeleton for the class: 90 | ```cpp 91 | template 92 | struct write_operation { 93 | write_operation(write_sender s, Receiver r) 94 | : req_{}, handle_{s.handle}, bufs_{s.bufs}, nbufs_{s.nbufs}, r_{std::move(r)} { } 95 | 96 | write_operation(const write_operation &) = delete; 97 | write_operation &operator=(const write_operation &) = delete; 98 | write_operation(write_operation &&) = delete; 99 | write_operation &operator=(write_operation &&) = delete; 100 | 101 | private: 102 | uv_write_t req_; 103 | uv_stream_t *handle_; 104 | const uv_buf_t *bufs_; 105 | size_t nbufs_; 106 | 107 | Receiver r_; 108 | }; 109 | ``` 110 | 111 | The operation stores all the necessary state, and is templated on the receiver 112 | type in order to support any receiver type. 113 | 114 | The operation is also made immovable and non-copyable so that pointers to it can 115 | safely be taken without worrying that they may become invalid at some point. 116 | 117 | Next, we add a `start_inline` method: 118 | ```cpp 119 | bool start_inline() { 120 | auto result = uv_write(&req_, handle_, bufs_, nbufs_, [] (uv_write_t *req, int status) { 121 | /* TODO */ 122 | }); 123 | 124 | if (result < 0) { 125 | async::execution::set_value_inline(r_, result); 126 | return true; // Completed inline 127 | } 128 | 129 | return false; // Did not complete inline 130 | } 131 | ``` 132 | 133 | We use `start_inline` here in order to notify the user of any immediate errors 134 | synchronously. We use functions defined inside of `async::execution` to set the 135 | value, because they properly detect which method should be called on the receiver. 136 | 137 | Now, let's implement the actual asynchronous completion: 138 | ```cpp 139 | ... 140 | handle_->data = this; 141 | auto result = uv_write(&req_, handle_, bufs_, nbufs_, [] (uv_write_t *req, int status) { 142 | auto op = static_cast(req->handle->data); 143 | op->complete(status); 144 | }); 145 | ... 146 | ``` 147 | 148 | We use the handle's user data field to store a pointer to this instance of the operation 149 | in order to be able to access it later. This is necessary as otherwise we'd have no way 150 | of knowing which operation caused our callback to be entered. Do note that this way 151 | of implementing it means that only one write operation may be in progress at once. 152 | One way to solve this would be to have a wrapper object that manages the handle and 153 | has a map of `req` to `write_operation`, but that's beyond the scope of this example. 154 | 155 | **Note:** Due to how libuv operates (it hides the actual event loop and instead 156 | dispatches callbacks directly), the [](io-service.md) will not have any logic apart 157 | from invoking `uv_run` to do one iteration of the event loop. 158 | 159 | Finally, we add our `complete` method: 160 | ```cpp 161 | private: 162 | void complete(int status) { 163 | async::execution::set_value_noinline(r_, status); 164 | } 165 | ``` 166 | 167 | On `complete`, we use `async::execution::set_value_noinline` to set the result 168 | value and notify the receiver that the operation is complete (so that it can 169 | for example resume the suspended coroutine, like the `async::sender_awaiter` receiver). 170 | 171 | ### Full code 172 | 173 | All of this put together gives us the following code: 174 | ```cpp 175 | // ---------------------------------------------- 176 | // Sender 177 | // ---------------------------------------------- 178 | 179 | struct [[nodiscard]] write_sender { 180 | using value_type = int; // Status code 181 | 182 | uv_stream_t *handle; 183 | const uv_buf_t *bufs; 184 | size_t nbufs; 185 | }; 186 | 187 | write_sender write(uv_stream_t *handle, const uv_buf_t *bufs, size_t nbufs) { 188 | return write_sender{handle, bufs, nbufs}; 189 | } 190 | 191 | template 192 | write_operation conneect(write_sender s, Receiver r) { 193 | return {s, std::move(r)}; 194 | } 195 | 196 | async::sender_awaiter 197 | operator co_await(write_sender s) { 198 | return {s}; 199 | } 200 | 201 | // ---------------------------------------------- 202 | // Operation 203 | // ---------------------------------------------- 204 | 205 | template 206 | struct write_operation { 207 | write_operation(write_sender s, Receiver r) 208 | : req_{}, handle_{s.handle}, bufs_{s.bufs}, nbufs_{s.nbufs}, r_{std::move(r)} { } 209 | 210 | write_operation(const write_operation &) = delete; 211 | write_operation &operator=(const write_operation &) = delete; 212 | write_operation(write_operation &&) = delete; 213 | write_operation &operator=(write_operation &&) = delete; 214 | 215 | bool start_inline() { 216 | handle_->data = this; 217 | auto result = uv_write(&req_, handle_, bufs_, nbufs_, [] (uv_write_t *req, int status) { 218 | auto op = static_cast(req->handle->data); 219 | op->complete(status); 220 | }); 221 | 222 | if (result < 0) { 223 | async::execution::set_value_inline(r_, result); 224 | return true; // Completed inline 225 | } 226 | 227 | return false; // Did not complete inline 228 | } 229 | 230 | private: 231 | void complete(int status) { 232 | async::execution::set_value_noinline(r_, status); 233 | } 234 | 235 | uv_write_t req_; 236 | uv_stream_t *handle_; 237 | const uv_buf_t *bufs_; 238 | size_t nbufs_; 239 | 240 | Receiver r_; 241 | }; 242 | ``` 243 | -------------------------------------------------------------------------------- /include/async/algorithm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace async { 11 | 12 | template 13 | struct connect_helper { 14 | using operation = execution::operation_t; 15 | 16 | operator operation () { 17 | return execution::connect(std::move(s), std::move(r)); 18 | } 19 | 20 | Sender s; 21 | Receiver r; 22 | }; 23 | 24 | template 25 | connect_helper make_connect_helper(Sender s, Receiver r) { 26 | return {std::move(s), std::move(r)}; 27 | } 28 | 29 | //--------------------------------------------------------------------------------------- 30 | // invocable() 31 | //--------------------------------------------------------------------------------------- 32 | 33 | template 34 | struct [[nodiscard]] invocable_operation { 35 | invocable_operation(F f, R r) 36 | : f_{std::move(f)}, r_{std::move(r)} { } 37 | 38 | invocable_operation(const invocable_operation &) = delete; 39 | 40 | invocable_operation &operator= (const invocable_operation &) = delete; 41 | 42 | bool start_inline() { 43 | if constexpr (std::is_same_v, void>) { 44 | f_(); 45 | execution::set_value_inline(r_); 46 | }else{ 47 | execution::set_value_inline(r_, f_()); 48 | } 49 | return true; 50 | } 51 | 52 | private: 53 | F f_; 54 | R r_; 55 | }; 56 | 57 | template 58 | struct [[nodiscard]] invocable_sender { 59 | using value_type = std::invoke_result_t; 60 | 61 | template 62 | invocable_operation connect(R r) { 63 | return {std::move(f), std::move(r)}; 64 | } 65 | 66 | sender_awaiter operator co_await() { 67 | return {*this}; 68 | } 69 | 70 | F f; 71 | }; 72 | 73 | template 74 | invocable_sender invocable(F f) { 75 | return {std::move(f)}; 76 | } 77 | 78 | //--------------------------------------------------------------------------------------- 79 | // transform() 80 | //--------------------------------------------------------------------------------------- 81 | 82 | template 83 | struct value_transform_receiver { 84 | value_transform_receiver(Receiver dr, F f) 85 | : dr_{std::move(dr)}, f_{std::move(f)} { } 86 | 87 | template 88 | void set_value_inline(X value) { 89 | if constexpr (std::is_same_v, void>) { 90 | f_(std::move(value)); 91 | execution::set_value_inline(dr_); 92 | }else{ 93 | execution::set_value_inline(dr_, f_(std::move(value))); 94 | } 95 | } 96 | 97 | template 98 | void set_value_noinline(X value) { 99 | if constexpr (std::is_same_v, void>) { 100 | f_(std::move(value)); 101 | execution::set_value_noinline(dr_); 102 | }else{ 103 | execution::set_value_noinline(dr_, f_(std::move(value))); 104 | } 105 | } 106 | 107 | private: 108 | Receiver dr_; // Downstream receiver. 109 | [[no_unique_address]] F f_; 110 | }; 111 | 112 | template 113 | struct void_transform_receiver { 114 | void_transform_receiver(Receiver dr, F f) 115 | : dr_{std::move(dr)}, f_{std::move(f)} { } 116 | 117 | void set_value_inline() { 118 | if constexpr (std::is_same_v, void>) { 119 | f_(); 120 | execution::set_value_inline(dr_); 121 | }else{ 122 | execution::set_value_inline(dr_, f_()); 123 | } 124 | } 125 | 126 | void set_value_noinline() { 127 | if constexpr (std::is_same_v, void>) { 128 | f_(); 129 | execution::set_value_noinline(dr_); 130 | }else{ 131 | execution::set_value_noinline(dr_, f_()); 132 | } 133 | } 134 | 135 | private: 136 | Receiver dr_; // Downstream receiver. 137 | [[no_unique_address]] F f_; 138 | }; 139 | 140 | template 141 | struct [[nodiscard]] transform_sender; 142 | 143 | template 144 | requires (!std::is_same_v) 145 | struct [[nodiscard]] transform_sender { 146 | using value_type = std::invoke_result_t; 147 | 148 | template 149 | friend auto connect(transform_sender s, Receiver dr) { 150 | return execution::connect(std::move(s.ds), 151 | value_transform_receiver{std::move(dr), std::move(s.f)}); 152 | } 153 | 154 | sender_awaiter operator co_await () { 155 | return {std::move(*this)}; 156 | } 157 | 158 | Sender ds; // Downstream sender. 159 | F f; 160 | }; 161 | 162 | template 163 | requires std::is_same_v 164 | struct [[nodiscard]] transform_sender { 165 | using value_type = std::invoke_result_t; 166 | 167 | template 168 | friend auto connect(transform_sender s, Receiver dr) { 169 | return execution::connect(std::move(s.ds), 170 | void_transform_receiver{std::move(dr), std::move(s.f)}); 171 | } 172 | 173 | sender_awaiter operator co_await () { 174 | return {std::move(*this)}; 175 | } 176 | 177 | Sender ds; // Downstream sender. 178 | F f; 179 | }; 180 | 181 | template 182 | transform_sender transform(Sender ds, F f) { 183 | return {std::move(ds), std::move(f)}; 184 | } 185 | 186 | //--------------------------------------------------------------------------------------- 187 | // ite() 188 | //--------------------------------------------------------------------------------------- 189 | 190 | template 191 | struct [[nodiscard]] ite_operation { 192 | ite_operation(C cond, ST then_s, SE else_s, R dr) 193 | : cond_{std::move(cond)}, then_s_{std::move(then_s)}, else_s_{std::move(else_s)}, 194 | dr_{std::move(dr)} { } 195 | 196 | ite_operation(const ite_operation &) = delete; 197 | 198 | ite_operation &operator= (const ite_operation &) = delete; 199 | 200 | ~ite_operation() { 201 | if(then_op_.valid()) 202 | then_op_.destruct(); 203 | if(else_op_.valid()) 204 | else_op_.destruct(); 205 | } 206 | 207 | bool start_inline() { 208 | if(cond_()) { 209 | then_op_.construct_with([&] { 210 | return execution::connect(std::move(then_s_), std::move(dr_)); 211 | }); 212 | return execution::start_inline(*then_op_); 213 | }else{ 214 | else_op_.construct_with([&] { 215 | return execution::connect(std::move(else_s_), std::move(dr_)); 216 | }); 217 | return execution::start_inline(*else_op_); 218 | } 219 | } 220 | 221 | C cond_; 222 | ST then_s_; 223 | SE else_s_; 224 | R dr_; 225 | frg::manual_box> then_op_; 226 | frg::manual_box> else_op_; 227 | }; 228 | 229 | template 230 | struct [[nodiscard]] ite_sender { 231 | using value_type = typename ST::value_type; 232 | 233 | ite_sender(C cond, ST then_s, SE else_s) 234 | : cond_{std::move(cond)}, then_s_{std::move(then_s)}, else_s_{std::move(else_s)} { } 235 | 236 | template 237 | ite_operation connect(R dr) { 238 | return {std::move(cond_), std::move(then_s_), std::move(else_s_), std::move(dr)}; 239 | } 240 | 241 | sender_awaiter operator co_await () { 242 | return {std::move(*this)}; 243 | } 244 | 245 | private: 246 | C cond_; 247 | ST then_s_; 248 | SE else_s_; 249 | }; 250 | 251 | template 252 | ite_sender ite(C cond, ST then_s, SE else_s) { 253 | return {std::move(cond), std::move(then_s), std::move(else_s)}; 254 | } 255 | 256 | //--------------------------------------------------------------------------------------- 257 | // repeat_while() 258 | //--------------------------------------------------------------------------------------- 259 | 260 | template 261 | struct [[nodiscard]] repeat_while_operation { 262 | using sender_type = std::invoke_result_t; 263 | 264 | repeat_while_operation(C cond, SF factory, R dr) 265 | : cond_{std::move(cond)}, factory_{std::move(factory)}, dr_{std::move(dr)} { } 266 | 267 | repeat_while_operation(const repeat_while_operation &) = delete; 268 | 269 | repeat_while_operation &operator=(const repeat_while_operation &) = delete; 270 | 271 | bool start_inline() { 272 | if(loop_()) { 273 | execution::set_value_inline(dr_); 274 | return true; 275 | } 276 | 277 | return false; 278 | } 279 | 280 | private: 281 | // Returns true if repeat_while() completes. 282 | bool loop_() { 283 | while(cond_()) { 284 | box_.construct_with([&] { 285 | return execution::connect(factory_(), receiver{this}); 286 | }); 287 | if(!execution::start_inline(*box_)) 288 | return false; 289 | box_.destruct(); 290 | } 291 | 292 | return true; 293 | } 294 | 295 | struct receiver { 296 | receiver(repeat_while_operation *self) 297 | : self_{self} { } 298 | 299 | void set_value_inline() { 300 | // Do nothing. 301 | } 302 | 303 | void set_value_noinline() { 304 | auto s = self_; // box_.destruct() will destruct this. 305 | s->box_.destruct(); 306 | if(s->loop_()) 307 | execution::set_value_noinline(s->dr_); 308 | } 309 | 310 | private: 311 | repeat_while_operation *self_; 312 | }; 313 | 314 | C cond_; 315 | SF factory_; 316 | R dr_; // Downstream receiver. 317 | frg::manual_box> box_; 318 | }; 319 | 320 | template 321 | struct repeat_while_sender { 322 | using value_type = void; 323 | 324 | sender_awaiter operator co_await() { 325 | return {std::move(*this)}; 326 | } 327 | 328 | template 329 | repeat_while_operation connect(R receiver) { 330 | return {std::move(cond), std::move(factory), std::move(receiver)}; 331 | } 332 | 333 | C cond; 334 | SF factory; 335 | }; 336 | 337 | template 338 | repeat_while_sender repeat_while(C cond, SF factory) { 339 | return {std::move(cond), std::move(factory)}; 340 | } 341 | 342 | //--------------------------------------------------------------------------------------- 343 | // race_and_cancel() 344 | //--------------------------------------------------------------------------------------- 345 | 346 | template 347 | struct race_and_cancel_operation; 348 | 349 | template 350 | struct race_and_cancel_sender { 351 | using value_type = void; 352 | 353 | template 354 | friend race_and_cancel_operation, 355 | std::index_sequence_for> 356 | connect(race_and_cancel_sender s, Receiver r) { 357 | return {std::move(s), std::move(r)}; 358 | } 359 | 360 | frg::tuple fs; 361 | }; 362 | 363 | template 364 | struct race_and_cancel_operation, std::index_sequence> { 365 | private: 366 | using functor_tuple = frg::tuple; 367 | 368 | template 369 | struct internal_receiver { 370 | internal_receiver(race_and_cancel_operation *self) 371 | : self_{self} { } 372 | 373 | void set_value_inline() { 374 | } 375 | 376 | void set_value_noinline() { 377 | auto n = self_->n_done_.fetch_add(1, std::memory_order_acq_rel); 378 | if(!n) { 379 | for(unsigned int j = 0; j < sizeof...(Is); ++j) 380 | if(j != I) 381 | self_->cs_[j].cancel(); 382 | } 383 | if(n + 1 == sizeof...(Is)) 384 | execution::set_value_noinline(self_->r_); 385 | } 386 | 387 | private: 388 | race_and_cancel_operation *self_; 389 | }; 390 | 391 | template 392 | using internal_sender = std::invoke_result_t< 393 | typename std::tuple_element::type, 394 | cancellation_token 395 | >; 396 | 397 | template 398 | using internal_operation = execution::operation_t, internal_receiver>; 399 | 400 | using operation_tuple = frg::tuple...>; 401 | 402 | auto make_operations_tuple(race_and_cancel_sender s) { 403 | return frg::make_tuple( 404 | make_connect_helper( 405 | (s.fs.template get())(cancellation_token{cs_[Is]}), 406 | internal_receiver{this} 407 | )... 408 | ); 409 | } 410 | 411 | public: 412 | race_and_cancel_operation(race_and_cancel_sender s, Receiver r) 413 | : r_{std::move(r)}, ops_{make_operations_tuple(std::move(s))}, n_sync_{0}, n_done_{0} { } 414 | 415 | bool start_inline() { 416 | unsigned int n_sync = 0; 417 | 418 | ((execution::start_inline(ops_.template get()) 419 | ? n_sync++ : 0), ...); 420 | 421 | if (n_sync) { 422 | auto n = n_done_.fetch_add(n_sync, std::memory_order_acq_rel); 423 | 424 | if (!n) { 425 | for(unsigned int j = 0; j < sizeof...(Is); ++j) 426 | cs_[j].cancel(); 427 | } 428 | 429 | if ((n + n_sync) == sizeof...(Is)) { 430 | execution::set_value_inline(r_); 431 | return true; 432 | } 433 | } 434 | 435 | return false; 436 | } 437 | 438 | private: 439 | Receiver r_; 440 | operation_tuple ops_; 441 | cancellation_event cs_[sizeof...(Is)]; 442 | std::atomic n_sync_; 443 | std::atomic n_done_; 444 | }; 445 | 446 | template 447 | sender_awaiter> 448 | operator co_await(race_and_cancel_sender s) { 449 | return {std::move(s)}; 450 | } 451 | 452 | template 453 | race_and_cancel_sender race_and_cancel(Functors... fs) { 454 | return {{fs...}}; 455 | } 456 | 457 | //--------------------------------------------------------------------------------------- 458 | // let() 459 | //--------------------------------------------------------------------------------------- 460 | 461 | template 462 | struct let_operation { 463 | using imm_type = std::invoke_result_t; 464 | using sender_type = std::invoke_result_t>; 465 | using value_type = typename sender_type::value_type; 466 | 467 | let_operation(Pred pred, Func func, Receiver r) 468 | : pred_{std::move(pred)}, func_{std::move(func)}, imm_{}, r_{std::move(r)} { } 469 | 470 | let_operation(const let_operation &) = delete; 471 | let_operation &operator=(const let_operation &) = delete; 472 | 473 | ~let_operation() { 474 | op_.destruct(); 475 | } 476 | 477 | public: 478 | bool start_inline() { 479 | imm_ = std::move(pred_()); 480 | op_.construct_with([&]{ return execution::connect(func_(imm_), std::move(r_)); }); 481 | return execution::start_inline(*op_); 482 | } 483 | 484 | private: 485 | Pred pred_; 486 | Func func_; 487 | imm_type imm_; 488 | Receiver r_; 489 | frg::manual_box> op_; 490 | }; 491 | 492 | template 493 | struct [[nodiscard]] let_sender { 494 | using imm_type = std::invoke_result_t; 495 | using value_type = typename std::invoke_result_t>::value_type; 496 | 497 | template 498 | friend let_operation 499 | connect(let_sender s, Receiver r) { 500 | return {std::move(s.pred), std::move(s.func), std::move(r)}; 501 | } 502 | 503 | Pred pred; 504 | Func func; 505 | }; 506 | 507 | template 508 | sender_awaiter, typename let_sender::value_type> 509 | operator co_await(let_sender s) { 510 | return {std::move(s)}; 511 | } 512 | 513 | template 514 | let_sender let(Pred pred, Func func) { 515 | return {std::move(pred), std::move(func)}; 516 | } 517 | 518 | //--------------------------------------------------------------------------------------- 519 | // sequence() 520 | //--------------------------------------------------------------------------------------- 521 | 522 | template requires (sizeof...(Senders) > 0) 523 | struct [[nodiscard]] sequence_operation { 524 | sequence_operation(frg::tuple senders, R dr) 525 | : senders_{std::move(senders)}, dr_{std::move(dr)} { } 526 | 527 | sequence_operation(const sequence_operation &) = delete; 528 | 529 | sequence_operation &operator=(const sequence_operation &) = delete; 530 | 531 | bool start_inline() { 532 | return do_step<0, true>(); 533 | } 534 | 535 | private: 536 | template 537 | using nth_sender = std::tuple_element_t>; 538 | 539 | template 540 | requires (InlinePath) 541 | bool do_step() { 542 | using operation_type = execution::operation_t, 543 | receiver>; 544 | 545 | auto op = new (box_.buffer) operation_type{ 546 | execution::connect(std::move(senders_.template get()), 547 | receiver{this}) 548 | }; 549 | 550 | if(execution::start_inline(*op)) { 551 | if constexpr (Index == sizeof...(Senders) - 1) { 552 | return true; 553 | }else{ 554 | return do_step(); 555 | } 556 | } 557 | return false; 558 | } 559 | 560 | // Same as above but since we are not on the InlinePath, we do not care about the return value. 561 | template 562 | requires (!InlinePath) 563 | void do_step() { 564 | using operation_type = execution::operation_t, 565 | receiver>; 566 | 567 | auto op = new (box_.buffer) operation_type{ 568 | execution::connect(std::move(senders_.template get()), 569 | receiver{this}) 570 | }; 571 | 572 | if(execution::start_inline(*op)) { 573 | if constexpr (Index != sizeof...(Senders) - 1) 574 | do_step(); 575 | } 576 | } 577 | 578 | template 579 | struct receiver { 580 | using value_type = typename nth_sender::value_type; 581 | static_assert((Index == sizeof...(Senders) - 1) || std::is_same_v, 582 | "All but the last sender must return void"); 583 | 584 | receiver(sequence_operation *self) 585 | : self_{self} { } 586 | 587 | void set_value_inline() requires (Index < sizeof...(Senders) - 1) { 588 | using operation_type = execution::operation_t, 589 | receiver>; 590 | auto op = std::launder(reinterpret_cast(self_->box_.buffer)); 591 | op->~operation_type(); 592 | 593 | // Do nothing: execution continues in do_step(). 594 | } 595 | 596 | void set_value_noinline() requires (Index < sizeof...(Senders) - 1) { 597 | using operation_type = execution::operation_t, 598 | receiver>; 599 | auto s = self_; // following lines will destruct this. 600 | auto op = std::launder(reinterpret_cast(s->box_.buffer)); 601 | op->~operation_type(); 602 | 603 | // Leave the inline path. 604 | s->template do_step(); 605 | } 606 | 607 | void set_value_inline() 608 | requires ((Index == sizeof...(Senders) - 1) 609 | && (std::is_same_v)) { 610 | using operation_type = execution::operation_t, 611 | receiver>; 612 | auto s = self_; // following lines will destruct this. 613 | auto op = std::launder(reinterpret_cast(s->box_.buffer)); 614 | op->~operation_type(); 615 | 616 | if(InlinePath) { 617 | execution::set_value_inline(s->dr_); 618 | }else{ 619 | execution::set_value_noinline(s->dr_); 620 | } 621 | } 622 | 623 | void set_value_noinline() 624 | requires ((Index == sizeof...(Senders) - 1) 625 | && (std::is_same_v)) { 626 | using operation_type = execution::operation_t, 627 | receiver>; 628 | auto s = self_; // following lines will destruct this. 629 | auto op = std::launder(reinterpret_cast(s->box_.buffer)); 630 | op->~operation_type(); 631 | 632 | execution::set_value_noinline(s->dr_); 633 | } 634 | 635 | template 636 | void set_value_inline(T value) 637 | requires ((Index == sizeof...(Senders) - 1) 638 | && (!std::is_same_v) 639 | && (std::is_same_v)) { 640 | using operation_type = execution::operation_t, 641 | receiver>; 642 | auto s = self_; // following lines will destruct this. 643 | auto op = std::launder(reinterpret_cast(s->box_.buffer)); 644 | op->~operation_type(); 645 | 646 | if(InlinePath) { 647 | execution::set_value_inline(s->dr_, std::move(value)); 648 | }else{ 649 | execution::set_value_noinline(s->dr_, std::move(value)); 650 | } 651 | } 652 | 653 | template 654 | void set_value_noinline(T value) 655 | requires ((Index == sizeof...(Senders) - 1) 656 | && (!std::is_same_v) 657 | && (std::is_same_v)) { 658 | using operation_type = execution::operation_t, 659 | receiver>; 660 | auto s = self_; // following lines will destruct this. 661 | auto op = std::launder(reinterpret_cast(s->box_.buffer)); 662 | op->~operation_type(); 663 | 664 | execution::set_value_noinline(s->dr_, std::move(value)); 665 | } 666 | 667 | private: 668 | sequence_operation *self_; 669 | }; 670 | 671 | frg::tuple senders_; 672 | R dr_; // Downstream receiver. 673 | 674 | static constexpr size_t max_operation_size = [](std::index_sequence) { 675 | return std::max({sizeof(execution::operation_t, receiver>)..., 676 | sizeof(execution::operation_t, receiver>)...}); 677 | }(std::make_index_sequence{}); 678 | 679 | static constexpr size_t max_operation_alignment = [](std::index_sequence) { 680 | return std::max({alignof(execution::operation_t, receiver>)..., 681 | alignof(execution::operation_t, receiver>)...}); 682 | }(std::make_index_sequence{}); 683 | 684 | frg::aligned_storage box_; 685 | }; 686 | 687 | template requires (sizeof...(Senders) > 0) 688 | struct [[nodiscard]] sequence_sender { 689 | using value_type = typename std::tuple_element_t>::value_type; 690 | 691 | template 692 | friend sequence_operation 693 | connect(sequence_sender s, Receiver r) { 694 | return {std::move(s.senders), std::move(r)}; 695 | } 696 | 697 | frg::tuple senders; 698 | }; 699 | 700 | template requires (sizeof...(Senders) > 0) 701 | sequence_sender sequence(Senders ...senders) { 702 | return {frg::tuple{std::move(senders)...}}; 703 | } 704 | 705 | template 706 | sender_awaiter, typename sequence_sender::value_type> 707 | operator co_await(sequence_sender s) { 708 | return {std::move(s)}; 709 | } 710 | 711 | //--------------------------------------------------------------------------------------- 712 | // when_all() 713 | //--------------------------------------------------------------------------------------- 714 | 715 | template 716 | struct when_all_operation { 717 | private: 718 | struct receiver { 719 | receiver(when_all_operation *self) 720 | : self_{self} { } 721 | 722 | void set_value_inline() { 723 | // Simply do nothing. 724 | } 725 | 726 | void set_value_noinline() { 727 | auto c = self_->ctr_.fetch_sub(1, std::memory_order_acq_rel); 728 | assert(c > 0); 729 | if(c == 1) 730 | execution::set_value_noinline(self_->dr_); 731 | } 732 | 733 | private: 734 | when_all_operation *self_; 735 | }; 736 | 737 | template 738 | auto make_operations_tuple(std::index_sequence, frg::tuple senders) { 739 | return frg::make_tuple( 740 | make_connect_helper( 741 | std::move(senders.template get()), 742 | receiver{this} 743 | )... 744 | ); 745 | } 746 | 747 | public: 748 | when_all_operation(frg::tuple senders, Receiver dr) 749 | : dr_{std::move(dr)}, 750 | ops_{make_operations_tuple(std::index_sequence_for{}, std::move(senders))}, 751 | ctr_{sizeof...(Senders)} { } 752 | 753 | bool start_inline() { 754 | int n_fast = 0; 755 | [&] (std::index_sequence) { 756 | ([&] () { 757 | if(execution::start_inline(ops_.template get())) 758 | ++n_fast; 759 | }.template operator()(), ...); 760 | }(std::index_sequence_for{}); 761 | 762 | auto c = ctr_.fetch_sub(n_fast, std::memory_order_acq_rel); 763 | assert(c > 0); 764 | if(c == n_fast) { 765 | execution::set_value_inline(dr_); 766 | return true; 767 | } 768 | return false; 769 | } 770 | 771 | Receiver dr_; // Downstream receiver. 772 | frg::tuple...> ops_; 773 | std::atomic ctr_; 774 | }; 775 | 776 | template requires (sizeof...(Senders) > 0) 777 | struct [[nodiscard]] when_all_sender { 778 | using value_type = void; 779 | 780 | template 781 | friend when_all_operation 782 | connect(when_all_sender s, Receiver r) { 783 | return {std::move(s.senders), std::move(r)}; 784 | } 785 | 786 | frg::tuple senders; 787 | }; 788 | 789 | template requires (sizeof...(Senders) > 0) 790 | when_all_sender when_all(Senders ...senders) { 791 | return {frg::tuple{std::move(senders)...}}; 792 | } 793 | 794 | template 795 | sender_awaiter> 796 | operator co_await(when_all_sender s) { 797 | return {std::move(s)}; 798 | } 799 | 800 | } // namespace async 801 | -------------------------------------------------------------------------------- /include/async/barrier.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace async { 6 | 7 | struct barrier { 8 | using arrival_token = uint64_t; 9 | 10 | barrier(ptrdiff_t expected) 11 | : expected_{expected} { } 12 | 13 | arrival_token arrive(ptrdiff_t n = 1) { 14 | return do_arrive(n, 0); 15 | } 16 | 17 | arrival_token arrive_and_join(ptrdiff_t n = 1) { 18 | return do_arrive(n, n); 19 | } 20 | 21 | arrival_token arrive_and_drop(ptrdiff_t n = 1) { 22 | return do_arrive(0, -n); 23 | } 24 | 25 | auto async_wait(arrival_token s) { 26 | return evt_.async_wait_if([this, s] () -> bool { 27 | return seq_.load(std::memory_order_relaxed) == s; 28 | }); 29 | } 30 | 31 | private: 32 | arrival_token do_arrive(ptrdiff_t n, ptrdiff_t delta) { 33 | uint64_t s; 34 | bool advance = false; 35 | { 36 | frg::unique_lock lock{mutex_}; 37 | 38 | s = seq_.load(std::memory_order_relaxed); 39 | assert(expected_ + delta >= 0); 40 | 41 | counter_ += n; 42 | expected_ += delta; 43 | 44 | if (counter_ == expected_) { 45 | advance = true; 46 | seq_.store(s + 1, std::memory_order_relaxed); 47 | counter_ = 0; 48 | } else { 49 | assert(counter_ < expected_); 50 | } 51 | } 52 | if (advance) 53 | evt_.raise(); 54 | 55 | return s; 56 | } 57 | 58 | platform::mutex mutex_; 59 | // Sequence number. Increased after each barrier. 60 | // Write-protected by mutex_. Can be read even without holding mutex_. 61 | std::atomic seq_{0}; 62 | // Expected number of arrivals. 63 | // Protected by mutex_. 64 | ptrdiff_t expected_; 65 | // Arrival count. Reset to zero on each barrier. 66 | // Protected by mutex_. 67 | ptrdiff_t counter_{0}; 68 | 69 | async::recurring_event evt_; 70 | }; 71 | 72 | } // namespace async 73 | -------------------------------------------------------------------------------- /include/async/basic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef LIBASYNC_CUSTOM_PLATFORM 14 | #include 15 | #include 16 | #include 17 | 18 | namespace async::platform { 19 | using mutex = std::mutex; 20 | 21 | [[noreturn]] inline void panic(const char *str) { 22 | std::cerr << str << std::endl; 23 | std::terminate(); 24 | } 25 | } // namespace async::platform 26 | #else 27 | #include 28 | #endif 29 | 30 | #if __has_include() && !defined(LIBASYNC_FORCE_USE_EXPERIMENTAL) 31 | #include 32 | namespace corons = std; 33 | #else 34 | #include 35 | namespace corons = std::experimental; 36 | #endif 37 | 38 | namespace async { 39 | 40 | template 41 | requires requires(E &&e) { operator co_await(std::forward(e)); } 42 | auto make_awaiter(E &&e) { 43 | return operator co_await(std::forward(e)); 44 | } 45 | 46 | template 47 | requires requires(E &&e) { std::forward(e).operator co_await(); } 48 | auto make_awaiter(E &&e) { 49 | return std::forward(e).operator co_await(); 50 | } 51 | 52 | template 53 | concept co_awaits_to = requires (Awaitable &&a) { 54 | { make_awaiter(std::forward(a)).await_resume() } -> std::same_as; 55 | }; 56 | 57 | // ---------------------------------------------------------------------------- 58 | // sender_awaiter template. 59 | // ---------------------------------------------------------------------------- 60 | 61 | template 62 | struct [[nodiscard]] sender_awaiter { 63 | private: 64 | struct receiver { 65 | void set_value_inline(T result) { 66 | p_->result_.emplace(std::move(result)); 67 | } 68 | 69 | void set_value_noinline(T result) { 70 | p_->result_.emplace(std::move(result)); 71 | p_->h_.resume(); 72 | } 73 | 74 | sender_awaiter *p_; 75 | }; 76 | 77 | public: 78 | sender_awaiter(S sender) 79 | : operation_{execution::connect(std::move(sender), receiver{this})} { 80 | } 81 | 82 | bool await_ready() { 83 | return false; 84 | } 85 | 86 | bool await_suspend(corons::coroutine_handle<> h) { 87 | h_ = h; 88 | return !execution::start_inline(operation_); 89 | } 90 | 91 | T await_resume() { 92 | return std::move(*result_); 93 | } 94 | 95 | execution::operation_t operation_; 96 | corons::coroutine_handle<> h_; 97 | frg::optional result_; 98 | }; 99 | 100 | // Specialization of sender_awaiter for void return types. 101 | template 102 | struct [[nodiscard]] sender_awaiter { 103 | private: 104 | struct receiver { 105 | void set_value_inline() { 106 | // Do nothing. 107 | } 108 | 109 | void set_value_noinline() { 110 | p_->h_.resume(); 111 | } 112 | 113 | sender_awaiter *p_; 114 | }; 115 | 116 | public: 117 | sender_awaiter(S sender) 118 | : operation_{execution::connect(std::move(sender), receiver{this})} { 119 | } 120 | 121 | bool await_ready() { 122 | return false; 123 | } 124 | 125 | bool await_suspend(corons::coroutine_handle<> h) { 126 | h_ = h; 127 | return !execution::start_inline(operation_); 128 | } 129 | 130 | void await_resume() { 131 | // Do nothing. 132 | } 133 | 134 | execution::operation_t operation_; 135 | corons::coroutine_handle<> h_; 136 | }; 137 | 138 | // ---------------------------------------------------------------------------- 139 | // any_receiver. 140 | // ---------------------------------------------------------------------------- 141 | 142 | // This form of any_receiver is a broken concept: because it directly forwards 143 | // the value of the set_value() function, it requires a virtual call even 144 | // if we add an inline return path. 145 | 146 | template 147 | struct any_receiver { 148 | template 149 | any_receiver(R receiver) { 150 | static_assert(std::is_trivially_copyable_v); 151 | static_assert(sizeof(R) <= sizeof(void *)); 152 | static_assert(alignof(R) <= alignof(void *)); 153 | 154 | new (stor_) R(receiver); 155 | set_value_fptr_ = [] (void *p, T value) { 156 | auto *rp = static_cast(p); 157 | execution::set_value_noinline(*rp, std::move(value)); 158 | }; 159 | } 160 | 161 | void set_value(T value) { 162 | set_value_fptr_(stor_, std::move(value)); 163 | } 164 | 165 | void set_value_noinline(T value) { 166 | set_value_fptr_(stor_, std::move(value)); 167 | } 168 | 169 | private: 170 | alignas(alignof(void *)) char stor_[sizeof(void *)]; 171 | void (*set_value_fptr_) (void *, T); 172 | }; 173 | 174 | template<> 175 | struct any_receiver { 176 | template 177 | any_receiver(R receiver) { 178 | static_assert(std::is_trivially_copyable_v); 179 | new (stor_) R(receiver); 180 | set_value_fptr_ = [] (void *p) { 181 | auto *rp = static_cast(p); 182 | execution::set_value_noinline(*rp); 183 | }; 184 | } 185 | 186 | void set_value() { 187 | set_value_fptr_(stor_); 188 | } 189 | 190 | void set_value_noinline() { 191 | set_value_fptr_(stor_); 192 | } 193 | 194 | private: 195 | alignas(alignof(void *)) char stor_[sizeof(void *)]; 196 | void (*set_value_fptr_) (void *); 197 | }; 198 | 199 | // ---------------------------------------------------------------------------- 200 | // Legacy utilities. 201 | // ---------------------------------------------------------------------------- 202 | 203 | template 204 | struct callback; 205 | 206 | template 207 | struct callback { 208 | private: 209 | using storage = frg::aligned_storage; 210 | 211 | template 212 | static R invoke(storage object, Args... args) { 213 | return (*reinterpret_cast(&object))(std::move(args)...); 214 | } 215 | 216 | public: 217 | callback() 218 | : _function(nullptr) { } 219 | 220 | template::value 223 | && std::is_trivially_destructible::value>> 224 | callback(F functor) 225 | : _function(&invoke) { 226 | new (&_object) F{std::move(functor)}; 227 | } 228 | 229 | explicit operator bool () { 230 | return static_cast(_function); 231 | } 232 | 233 | R operator() (Args... args) { 234 | return _function(_object, std::move(args)...); 235 | } 236 | 237 | private: 238 | R (*_function)(storage, Args...); 239 | frg::aligned_storage _object; 240 | }; 241 | 242 | // ---------------------------------------------------------------------------- 243 | // run_queue implementation. 244 | // ---------------------------------------------------------------------------- 245 | 246 | struct run_queue; 247 | 248 | run_queue *get_current_queue(); 249 | 250 | struct run_queue_item { 251 | friend struct run_queue; 252 | friend struct current_queue_token; 253 | friend struct run_queue_token; 254 | 255 | run_queue_item() = default; 256 | 257 | run_queue_item(const run_queue_item &) = delete; 258 | 259 | run_queue_item &operator= (const run_queue_item &) = delete; 260 | 261 | void arm(callback cb) { 262 | assert(!_cb && "run_queue_item is already armed"); 263 | assert(cb && "cannot arm run_queue_item with a null callback"); 264 | _cb = cb; 265 | } 266 | 267 | private: 268 | callback _cb; 269 | frg::default_list_hook _hook; 270 | }; 271 | 272 | struct run_queue_token { 273 | run_queue_token(run_queue *rq) 274 | : rq_{rq} { } 275 | 276 | void run_iteration(); 277 | bool is_drained(); 278 | 279 | private: 280 | run_queue *rq_; 281 | }; 282 | 283 | struct run_queue { 284 | friend struct current_queue_token; 285 | friend struct run_queue_token; 286 | 287 | run_queue_token run_token() { 288 | return {this}; 289 | } 290 | 291 | void post(run_queue_item *node); 292 | 293 | private: 294 | frg::intrusive_list< 295 | run_queue_item, 296 | frg::locate_member< 297 | run_queue_item, 298 | frg::default_list_hook, 299 | &run_queue_item::_hook 300 | > 301 | > _run_list; 302 | }; 303 | 304 | // ---------------------------------------------------------------------------- 305 | // Top-level execution functions. 306 | // ---------------------------------------------------------------------------- 307 | 308 | template 309 | void run_forever(IoService ios) { 310 | while(true) { 311 | ios.wait(); 312 | } 313 | } 314 | 315 | template 316 | std::enable_if_t, void> 317 | run(Sender s) { 318 | struct receiver { 319 | void set_value_inline() { } 320 | 321 | void set_value_noinline() { } 322 | }; 323 | 324 | auto operation = execution::connect(std::move(s), receiver{}); 325 | if(execution::start_inline(operation)) 326 | return; 327 | 328 | platform::panic("libasync: Operation hasn't completed and we don't know how to wait"); 329 | } 330 | 331 | template 332 | std::enable_if_t, 333 | typename Sender::value_type> 334 | run(Sender s) { 335 | struct state { 336 | frg::optional value; 337 | }; 338 | 339 | struct receiver { 340 | receiver(state *stp) 341 | : stp_{stp} { } 342 | 343 | void set_value_inline(typename Sender::value_type value) { 344 | stp_->value.emplace(std::move(value)); 345 | } 346 | 347 | void set_value_noinline(typename Sender::value_type value) { 348 | stp_->value.emplace(std::move(value)); 349 | } 350 | 351 | private: 352 | state *stp_; 353 | }; 354 | 355 | state st; 356 | 357 | auto operation = execution::connect(std::move(s), receiver{&st}); 358 | if (execution::start_inline(operation)) 359 | return std::move(*st.value); 360 | 361 | platform::panic("libasync: Operation hasn't completed and we don't know how to wait"); 362 | } 363 | 364 | template 365 | std::enable_if_t, void> 366 | run(Sender s, IoService ios) { 367 | struct state { 368 | bool done = false; 369 | }; 370 | 371 | struct receiver { 372 | receiver(state *stp) 373 | : stp_{stp} { } 374 | 375 | void set_value_inline() { 376 | stp_->done = true; 377 | } 378 | 379 | void set_value_noinline() { 380 | stp_->done = true; 381 | } 382 | 383 | private: 384 | state *stp_; 385 | }; 386 | 387 | state st; 388 | 389 | auto operation = execution::connect(std::move(s), receiver{&st}); 390 | if(execution::start_inline(operation)) 391 | return; 392 | 393 | while(!st.done) { 394 | ios.wait(); 395 | } 396 | } 397 | 398 | template 399 | std::enable_if_t, 400 | typename Sender::value_type> 401 | run(Sender s, IoService ios) { 402 | struct state { 403 | bool done = false; 404 | frg::optional value; 405 | }; 406 | 407 | struct receiver { 408 | receiver(state *stp) 409 | : stp_{stp} { } 410 | 411 | void set_value_inline(typename Sender::value_type value) { 412 | stp_->value.emplace(std::move(value)); 413 | stp_->done = true; 414 | } 415 | 416 | void set_value_noinline(typename Sender::value_type value) { 417 | stp_->value.emplace(std::move(value)); 418 | stp_->done = true; 419 | } 420 | 421 | private: 422 | state *stp_; 423 | }; 424 | 425 | state st; 426 | 427 | auto operation = execution::connect(std::move(s), receiver{&st}); 428 | if(execution::start_inline(operation)) 429 | return std::move(*st.value); 430 | 431 | while(!st.done) { 432 | ios.wait(); 433 | } 434 | 435 | return std::move(*st.value); 436 | } 437 | 438 | // ---------------------------------------------------------------------------- 439 | // Detached coroutines. 440 | // ---------------------------------------------------------------------------- 441 | 442 | struct detached { 443 | struct promise_type { 444 | detached get_return_object() { 445 | return {}; 446 | } 447 | 448 | corons::suspend_never initial_suspend() { 449 | return {}; 450 | } 451 | 452 | corons::suspend_never final_suspend() noexcept { 453 | return {}; 454 | } 455 | 456 | void return_void() { 457 | // Nothing to do here. 458 | } 459 | 460 | void unhandled_exception() { 461 | platform::panic("libasync: Unhandled exception in coroutine"); 462 | } 463 | }; 464 | }; 465 | 466 | namespace detach_details_ { 467 | template 468 | struct control_block; 469 | 470 | template 471 | void finalize(control_block *cb); 472 | 473 | template 474 | struct final_receiver { 475 | final_receiver(control_block *cb) 476 | : cb_{cb} { } 477 | 478 | void set_value_inline() { 479 | finalize(cb_); 480 | } 481 | 482 | void set_value_noinline() { 483 | finalize(cb_); 484 | } 485 | 486 | private: 487 | control_block *cb_; 488 | }; 489 | 490 | // Heap-allocate data structure that holds the operation. 491 | // We cannot directly put the operation onto the heap as it is non-movable. 492 | template 493 | struct control_block { 494 | friend void finalize(control_block *cb) { 495 | auto allocator = std::move(cb->allocator); 496 | auto continuation = std::move(cb->continuation); 497 | frg::destruct(allocator, cb); 498 | continuation(); 499 | } 500 | 501 | control_block(Allocator allocator, S sender, Cont continuation) 502 | : allocator{std::move(allocator)}, 503 | operation{execution::connect( 504 | std::move(sender), final_receiver{this})}, 505 | continuation{std::move(continuation)} { } 506 | 507 | Allocator allocator; 508 | execution::operation_t> operation; 509 | Cont continuation; 510 | }; 511 | } 512 | 513 | template 514 | void detach_with_allocator(Allocator allocator, S sender, Cont continuation) { 515 | auto p = frg::construct>(allocator, 516 | allocator, std::move(sender), std::move(continuation)); 517 | execution::start_inline(p->operation); 518 | } 519 | 520 | template 521 | void detach_with_allocator(Allocator allocator, S sender) { 522 | detach_with_allocator(std::move(allocator), std::move(sender), [] { }); 523 | } 524 | 525 | template 526 | void detach(S sender) { 527 | return detach_with_allocator(frg::stl_allocator{}, std::move(sender)); 528 | } 529 | 530 | template 531 | void detach(S sender, Cont continuation) { 532 | return detach_with_allocator(frg::stl_allocator{}, std::move(sender), std::move(continuation)); 533 | } 534 | 535 | namespace spawn_details_ { 536 | template 537 | struct control_block; 538 | 539 | template 540 | void finalize(control_block *cb); 541 | 542 | template 543 | struct final_receiver { 544 | final_receiver(control_block *cb) 545 | : cb_{cb} { } 546 | 547 | template 548 | void set_value_inline(Args &&... args) { 549 | cb_->dr.set_value_inline(std::forward(args)...); 550 | finalize(cb_); 551 | } 552 | 553 | template 554 | void set_value_noinline(Args &&... args) { 555 | cb_->dr.set_value_noinline(std::forward(args)...); 556 | finalize(cb_); 557 | } 558 | 559 | private: 560 | control_block *cb_; 561 | }; 562 | 563 | // Heap-allocate data structure that holds the operation. 564 | // We cannot directly put the operation onto the heap as it is non-movable. 565 | template 566 | struct control_block { 567 | friend void finalize(control_block *cb) { 568 | auto allocator = std::move(cb->allocator); 569 | frg::destruct(allocator, cb); 570 | } 571 | 572 | control_block(Allocator allocator, S sender, R dr) 573 | : allocator{std::move(allocator)}, 574 | operation{execution::connect( 575 | std::move(sender), final_receiver{this})}, 576 | dr{std::move(dr)} { } 577 | 578 | Allocator allocator; 579 | execution::operation_t> operation; 580 | R dr; // Downstream receiver. 581 | }; 582 | } 583 | 584 | template 585 | void spawn_with_allocator(Allocator allocator, S sender, R receiver) { 586 | auto p = frg::construct>(allocator, 587 | allocator, std::move(sender), std::move(receiver)); 588 | execution::start_inline(p->operation); 589 | } 590 | 591 | } // namespace async 592 | -------------------------------------------------------------------------------- /include/async/cancellation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "basic.hpp" 5 | 6 | namespace async::detail { 7 | 8 | struct abstract_cancellation_callback { 9 | friend struct cancellation_event; 10 | 11 | protected: 12 | virtual ~abstract_cancellation_callback() = default; 13 | 14 | private: 15 | virtual void call() = 0; 16 | 17 | frg::default_list_hook _hook; 18 | }; 19 | 20 | struct cancellation_event { 21 | friend struct cancellation_token; 22 | 23 | template 24 | friend struct cancellation_callback; 25 | 26 | template 27 | friend struct cancellation_observer; 28 | 29 | cancellation_event() 30 | : _was_requested{false} { }; 31 | 32 | cancellation_event(const cancellation_event &) = delete; 33 | cancellation_event(cancellation_event &&) = delete; 34 | 35 | ~cancellation_event() { 36 | assert(_cbs.empty() && "all callbacks must be destructed before" 37 | " cancellation_event is destructed"); 38 | } 39 | 40 | cancellation_event &operator= (const cancellation_event &) = delete; 41 | cancellation_event &operator= (cancellation_event &&) = delete; 42 | 43 | void cancel(); 44 | 45 | void reset(); 46 | 47 | private: 48 | platform::mutex _mutex; 49 | 50 | bool _was_requested; 51 | 52 | frg::intrusive_list< 53 | abstract_cancellation_callback, 54 | frg::locate_member< 55 | abstract_cancellation_callback, 56 | frg::default_list_hook, 57 | &abstract_cancellation_callback::_hook 58 | > 59 | > _cbs; 60 | }; 61 | 62 | struct cancellation_token { 63 | template 64 | friend struct cancellation_callback; 65 | 66 | template 67 | friend struct cancellation_observer; 68 | 69 | cancellation_token() 70 | : _event{nullptr} { } 71 | 72 | cancellation_token(cancellation_event &event_ref) 73 | : _event{&event_ref} { } 74 | 75 | bool is_cancellation_requested() const { 76 | if(!_event) 77 | return false; 78 | frg::unique_lock guard{_event->_mutex}; 79 | return _event->_was_requested; 80 | } 81 | 82 | private: 83 | cancellation_event *_event; 84 | }; 85 | 86 | template 87 | struct cancellation_callback final : private abstract_cancellation_callback { 88 | cancellation_callback(cancellation_token token, F functor) 89 | : _event{token._event}, _functor{std::move(functor)} { 90 | if(!_event) 91 | return; 92 | frg::unique_lock guard{_event->_mutex}; 93 | if(_event->_was_requested) { 94 | _functor(); 95 | }else{ 96 | _event->_cbs.push_back(this); 97 | } 98 | } 99 | 100 | cancellation_callback(const cancellation_callback &) = delete; 101 | cancellation_callback(cancellation_callback &&) = delete; 102 | 103 | ~cancellation_callback() { 104 | if(!_event) 105 | return; 106 | frg::unique_lock guard{_event->_mutex}; 107 | if(!_event->_was_requested) { 108 | auto it = _event->_cbs.iterator_to(this); 109 | _event->_cbs.erase(it); 110 | } 111 | } 112 | 113 | cancellation_callback &operator= (const cancellation_callback &) = delete; 114 | cancellation_callback &operator= (cancellation_callback &&) = delete; 115 | 116 | void unbind() { 117 | if(!_event) 118 | return; 119 | frg::unique_lock guard{_event->_mutex}; 120 | if(!_event->_was_requested) { 121 | auto it = _event->_cbs.iterator_to(this); 122 | _event->_cbs.erase(it); 123 | } 124 | } 125 | 126 | private: 127 | void call() override { 128 | _functor(); 129 | } 130 | 131 | cancellation_event *_event; 132 | F _functor; 133 | }; 134 | 135 | template 136 | struct cancellation_observer final : private abstract_cancellation_callback { 137 | cancellation_observer(F functor = F{}) 138 | : _event{nullptr}, _functor{std::move(functor)} { 139 | } 140 | 141 | cancellation_observer(const cancellation_observer &) = delete; 142 | cancellation_observer(cancellation_observer &&) = delete; 143 | 144 | // TODO: we could do some sanity checking of the state in the destructor. 145 | ~cancellation_observer() = default; 146 | 147 | cancellation_observer &operator= (const cancellation_observer &) = delete; 148 | cancellation_observer &operator= (cancellation_observer &&) = delete; 149 | 150 | bool try_set(cancellation_token token) { 151 | assert(!_event); 152 | if(!token._event) 153 | return true; 154 | _event = token._event; 155 | 156 | frg::unique_lock guard{_event->_mutex}; 157 | if(!_event->_was_requested) { 158 | _event->_cbs.push_back(this); 159 | return true; 160 | } 161 | return false; 162 | } 163 | 164 | // TODO: provide a set() method that calls the handler inline if cancellation is requested. 165 | 166 | bool try_reset() { 167 | if(!_event) 168 | return true; 169 | 170 | frg::unique_lock guard{_event->_mutex}; 171 | if(!_event->_was_requested) { 172 | auto it = _event->_cbs.iterator_to(this); 173 | _event->_cbs.erase(it); 174 | return true; 175 | } 176 | return false; 177 | } 178 | 179 | // TODO: provide a reset() method that waits until the cancellation handler completed. 180 | // This can be done by spinning on an atomic variable in cancellation_event 181 | // that determines whether handlers are currently running. 182 | 183 | private: 184 | void call() override { 185 | _functor(); 186 | } 187 | 188 | cancellation_event *_event; 189 | F _functor; 190 | }; 191 | 192 | inline void cancellation_event::cancel() { 193 | frg::intrusive_list< 194 | abstract_cancellation_callback, 195 | frg::locate_member< 196 | abstract_cancellation_callback, 197 | frg::default_list_hook, 198 | &abstract_cancellation_callback::_hook 199 | > 200 | > pending; 201 | 202 | { 203 | frg::unique_lock guard{_mutex}; 204 | _was_requested = true; 205 | pending.splice(pending.begin(), _cbs); 206 | } 207 | 208 | while (!pending.empty()) { 209 | auto cb = pending.front(); 210 | pending.pop_front(); 211 | cb->call(); 212 | } 213 | } 214 | 215 | inline void cancellation_event::reset() { 216 | frg::unique_lock guard{_mutex}; 217 | _was_requested = false; 218 | } 219 | 220 | } // namespace async::detail 221 | 222 | namespace async { 223 | 224 | using detail::cancellation_event; 225 | using detail::cancellation_token; 226 | using detail::cancellation_callback; 227 | using detail::cancellation_observer; 228 | 229 | template 230 | struct suspend_indefinitely_operation { 231 | private: 232 | struct functor { 233 | functor(suspend_indefinitely_operation *op) 234 | : op_{op} { } 235 | 236 | void operator() () { 237 | execution::set_value(op_->r_); 238 | } 239 | 240 | private: 241 | suspend_indefinitely_operation *op_; 242 | }; 243 | 244 | public: 245 | suspend_indefinitely_operation(cancellation_token cancellation, Receiver r) 246 | : cancellation_{cancellation}, r_{r}, obs_{this} { } 247 | 248 | void start() { 249 | if(!obs_.try_set(cancellation_)) 250 | execution::set_value(r_); 251 | } 252 | 253 | private: 254 | cancellation_token cancellation_; 255 | Receiver r_; 256 | cancellation_observer obs_; 257 | bool started_ = false; 258 | }; 259 | 260 | struct suspend_indefinitely_sender { 261 | using value_type = void; 262 | 263 | cancellation_token cancellation; 264 | }; 265 | 266 | template 267 | suspend_indefinitely_operation connect(suspend_indefinitely_sender s, Receiver r) { 268 | return {s.cancellation, std::move(r)}; 269 | } 270 | 271 | inline sender_awaiter operator co_await(suspend_indefinitely_sender s) { 272 | return {s}; 273 | } 274 | 275 | inline suspend_indefinitely_sender suspend_indefinitely(cancellation_token cancellation) { 276 | return {cancellation}; 277 | } 278 | 279 | } // namespace async 280 | -------------------------------------------------------------------------------- /include/async/execution.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace async { 7 | 8 | // Detection pattern boilerplate. 9 | 10 | template 11 | using void_t = void; 12 | 13 | template 14 | constexpr bool dependent_false_t = false; 15 | 16 | template