├── .github └── workflows │ ├── ci.yml │ ├── publish-dry-run.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── executor.rs ├── examples ├── borrow.rs ├── parallel.rs └── priority.rs ├── src └── lib.rs └── tests ├── different_executors.rs ├── drop.rs └── local_queue.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | - cron: "50 4 * * *" 10 | workflow_dispatch: 11 | 12 | env: 13 | rust_toolchain: stable 14 | 15 | jobs: 16 | compile: 17 | name: Compile 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Setup | Checkout 21 | uses: actions/checkout@v2 22 | - name: Setup | Rust 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: ${{ env.rust_toolchain }} 26 | components: rustfmt, clippy 27 | - name: Setup 28 | run: rustup default ${{ env.rust_toolchain }} 29 | - name: Add targets 30 | run: rustup target add riscv32imc-unknown-none-elf 31 | - name: Build | Fmt Check 32 | run: cargo fmt -- --check 33 | - name: Add wasm target 34 | run: rustup target add wasm32-unknown-unknown 35 | - name: Build | Clippy 36 | run: cargo clippy --features std --no-deps -- -Dwarnings 37 | - name: Build | Compile 38 | run: cargo build 39 | - name: Build | Compile no_std 40 | run: cargo build --no-default-features --features portable-atomic,heapless,critical-section --target riscv32imc-unknown-none-elf 41 | - name: Build | Test 42 | run: cargo test 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-dry-run.yml: -------------------------------------------------------------------------------- 1 | name: PublishDryRun 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | env: 7 | rust_toolchain: stable 8 | 9 | jobs: 10 | publishdryrun: 11 | name: Publish Dry Run 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup | Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup | Rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: ${{ env.rust_toolchain }} 20 | - name: Setup 21 | run: rustup default ${{ env.rust_toolchain }} 22 | - name: Build | Publish Dry Run 23 | run: cargo publish --dry-run 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | env: 7 | rust_toolchain: stable 8 | CRATE_NAME: edge-executor 9 | 10 | jobs: 11 | publish: 12 | name: Publish 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Setup | Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup | Rust 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: ${{ env.rust_toolchain }} 21 | - name: Setup 22 | run: rustup default ${{ env.rust_toolchain }} 23 | - name: Login 24 | run: cargo login ${{ secrets.crates_io_token }} 25 | - name: Build | Publish 26 | run: cargo publish 27 | - name: Get the crate version from cargo 28 | run: | 29 | version=$(cargo metadata --format-version=1 --no-deps | jq -r ".packages[] | select(.name == \"${{env.CRATE_NAME}}\") | .version") 30 | echo "crate_version=$version" >> $GITHUB_ENV 31 | echo "${{env.CRATE_NAME}} version: $version" 32 | - name: Tag the new release 33 | uses: rickstaa/action-create-tag@v1 34 | with: 35 | tag: v${{env.crate_version}} 36 | message: "Release v${{env.crate_version}}" 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /target 3 | Cargo.lock 4 | /.vscode 5 | /.espressif 6 | /.embuild 7 | **/*.rs.bk 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.4.1] - 2023-11-09 9 | * Update to heapless 0.8, and use heapless `MpMcQueue` for targets that do not have atomics, as `crossbeam` does not support such targets yet 10 | 11 | ## [0.4.0] - 2023-10-17 12 | * Crate goals clarified 13 | * Crate redesigned to follow the API of `async-executor` 14 | * Re-implemented as a completely portable executor which is itself a `Future` (platform/OS is expected to provide a `block_on` or "spawn a `Future` onto an event loop" primitive, but the executor is unaware of it) 15 | * Documentation, examples, tests 16 | * More user-friendly README 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "edge-executor" 3 | version = "0.4.1" 4 | authors = ["Ivan Markov "] 5 | edition = "2021" 6 | categories = ["embedded", "hardware-support"] 7 | keywords = ["embedded", "async", "executor"] 8 | description = "Async executor suitable for embedded environments." 9 | repository = "https://github.com/ivmarkov/edge-executor" 10 | license = "MIT OR Apache-2.0" 11 | readme = "README.md" 12 | 13 | [features] 14 | default = ["std"] 15 | std = ["futures-lite/std", "once_cell/std"] 16 | critical-section = ["once_cell/critical-section", "portable-atomic?/critical-section"] 17 | portable-atomic = ["heapless", "dep:portable-atomic", "portable-atomic-util", "heapless?/portable-atomic", "atomic-waker/portable-atomic", "async-task/portable-atomic"] 18 | unbounded = [] 19 | 20 | [dependencies] 21 | heapless = { version = "0.8", default-features = false, optional = true } 22 | portable-atomic = { version = "1.4", optional = true } 23 | portable-atomic-util = { version = "0.1", default-features = false, features = ["alloc"], optional = true } 24 | crossbeam-queue = { version = "0.3", default-features = false, features = ["alloc"] } 25 | async-task = { version = "4.4.5", default-features = false } 26 | atomic-waker = { version = "1", default-features = false } 27 | futures-lite = { version = "1", default-features = false } 28 | once_cell = { version = "1.18", default-features = false } 29 | 30 | [dev-dependencies] 31 | async-channel = "1.4.1" 32 | async-io = "1.1.9" 33 | criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } 34 | easy-parallel = "3.1.0" 35 | fastrand = "2.0.0" 36 | once_cell = "1.18.0" 37 | 38 | # [[bench]] 39 | # name = "executor" 40 | # harness = false 41 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2019-2020 Contributors to xtensa-lx6-rt 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edge-executor 2 | 3 | [![CI](https://github.com/ivmarkov/edge-executor/actions/workflows/ci.yml/badge.svg)](https://github.com/ivmarkov/edge-executor/actions/workflows/ci.yml) 4 | ![crates.io](https://img.shields.io/crates/v/edge-executor.svg) 5 | [![Documentation](https://docs.rs/edge-executor/badge.svg)](https://docs.rs/edge-executor) 6 | 7 | This crate ships a minimal async executor suitable for microcontrollers and embedded systems in general. 8 | 9 | A `no_std` drop-in replacement for [smol](https://github.com/smol-rs/smol)'s [async-executor](https://github.com/smol-rs/async-executor), with the implementation being a thin wrapper around [smol](https://github.com/smol-rs/smol)'s [async-task](https://github.com/smol-rs/async-task) as well. 10 | 11 | ## Examples 12 | 13 | ```rust 14 | // ESP-IDF example, local execution, local borrows. 15 | // With STD enabled, you can also just use `edge_executor::block_on` 16 | // instead of `esp_idf_svc::hal::task::block_on`. 17 | 18 | use edge_executor::LocalExecutor; 19 | use esp_idf_svc::hal::task::block_on; 20 | 21 | fn main() { 22 | let local_ex: LocalExecutor = Default::default(); 23 | 24 | // Borrowed by `&mut` inside the future spawned on the executor 25 | let mut data = 3; 26 | 27 | let data = &mut data; 28 | 29 | let task = local_ex.spawn(async move { 30 | *data += 1; 31 | 32 | *data 33 | }); 34 | 35 | let res = block_on(local_ex.run(async { task.await * 2 })); 36 | 37 | assert_eq!(res, 8); 38 | } 39 | ``` 40 | 41 | ```rust 42 | // STD example, work-stealing execution. 43 | 44 | use async_channel::unbounded; 45 | use easy_parallel::Parallel; 46 | 47 | use edge_executor::{Executor, block_on}; 48 | 49 | fn main() { 50 | let ex: Executor = Default::default(); 51 | let (signal, shutdown) = unbounded::<()>(); 52 | 53 | Parallel::new() 54 | // Run four executor threads. 55 | .each(0..4, |_| block_on(ex.run(shutdown.recv()))) 56 | // Run the main future on the current thread. 57 | .finish(|| block_on(async { 58 | println!("Hello world!"); 59 | drop(signal); 60 | })); 61 | } 62 | ``` 63 | 64 | ```rust 65 | // WASM example. 66 | 67 | use log::{info, Level}; 68 | 69 | use edge_executor::LocalExecutor; 70 | 71 | use static_cell::StaticCell; 72 | use wasm_bindgen_futures::spawn_local; 73 | 74 | use gloo_timers::future::TimeoutFuture; 75 | 76 | static LOCAL_EX: StaticCell = StaticCell::new(); 77 | 78 | fn main() { 79 | console_log::init_with_level(Level::Info).unwrap(); 80 | 81 | // Local executor (futures can be `!Send`) yet `'static` 82 | let local_ex = &*LOCAL_EX.init(Default::default()); 83 | 84 | local_ex 85 | .spawn(async { 86 | loop { 87 | info!("Tick"); 88 | TimeoutFuture::new(1000).await; 89 | } 90 | }) 91 | .detach(); 92 | 93 | spawn_local(local_ex.run(core::future::pending::<()>())); 94 | } 95 | ``` 96 | 97 | ## Highlights 98 | 99 | - `no_std` (but does need `alloc`): 100 | - The executor uses allocations in a controlled way: only when a new task is being spawn, as well as during the construction of the executor itself; 101 | - For a `no_std` *and* "no_alloc" executor, look at [embassy-executor](https://github.com/embassy-rs/embassy/tree/main/embassy-executor), which statically pre-allocates all tasks. 102 | - Works on targets which have no `core::sync::atomic` support, thanks to [portable-atomic](https://github.com/taiki-e/portable-atomic); 103 | - Does not assume an RTOS and can run completely bare-metal too; 104 | - Lockless, atomic-based, bounded task queue by default, which works well for waking the executor directly from an ISR on e.g. FreeRTOS or ESP-IDF (unbounded also an option with feature `unbounded`, yet that might mean potential allocations in an ISR context, which should be avoided). 105 | 106 | ### Great features carried over from [async-executor](https://github.com/smol-rs/async-executor): 107 | 108 | - Stack borrows: futures spawned on the executor need to live only as long as the executor itself. No `F: Future + 'static` constraints; 109 | - Completely portable and async. `Executor::run` simply returns a `Future`. Polling this future runs the executor, i.e. `block_on(executor.run(core::future:pending::<()>()))`; 110 | - `const new` constructor function. 111 | 112 | --- 113 | **NOTE**: 114 | To compile on `no_std` targets that do **not** have atomics in Rust `core` (i.e. `riscv32imc-unknown-none-elf` and similar single-core MCUs), 115 | enable features `portable-atomic` and `critical-section`. I.e.: 116 | ```sh 117 | cargo build --features portable-atomic,critical-section --no-default-features --target 118 | ``` 119 | --- 120 | 121 | -------------------------------------------------------------------------------- /benches/executor.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::thread::available_parallelism; 3 | 4 | use criterion::{criterion_group, criterion_main, Criterion}; 5 | use edge_executor::Executor; 6 | use futures_lite::{future, prelude::*}; 7 | 8 | const TASKS: usize = 300; 9 | const STEPS: usize = 300; 10 | const LIGHT_TASKS: usize = 25_000; 11 | 12 | static EX: Executor<'_, 30000> = Executor::new(); 13 | 14 | fn run(f: impl FnOnce(), multithread: bool) { 15 | let limit = if multithread { 16 | available_parallelism().unwrap().get() 17 | } else { 18 | 1 19 | }; 20 | 21 | let (s, r) = async_channel::bounded::<()>(1); 22 | easy_parallel::Parallel::new() 23 | .each(0..limit, |_| future::block_on(EX.run(r.recv()))) 24 | .finish(move || { 25 | let _s = s; 26 | f() 27 | }); 28 | } 29 | 30 | fn create(c: &mut Criterion) { 31 | c.bench_function("executor::create", |b| { 32 | b.iter(|| { 33 | let ex: Executor = Default::default(); 34 | let task = ex.spawn(async {}); 35 | future::block_on(ex.run(task)); 36 | }) 37 | }); 38 | } 39 | 40 | fn running_benches(c: &mut Criterion) { 41 | for (group_name, multithread) in [("single_thread", false), ("multi_thread", true)].iter() { 42 | let mut group = c.benchmark_group(group_name.to_string()); 43 | 44 | group.bench_function("executor::spawn_one", |b| { 45 | run( 46 | || { 47 | b.iter(|| { 48 | future::block_on(async { EX.spawn(async {}).await }); 49 | }); 50 | }, 51 | *multithread, 52 | ); 53 | }); 54 | 55 | group.bench_function("executor::spawn_many_local", |b| { 56 | run( 57 | || { 58 | b.iter(move || { 59 | future::block_on(async { 60 | let mut tasks = Vec::new(); 61 | for _ in 0..LIGHT_TASKS { 62 | tasks.push(EX.spawn(async {})); 63 | } 64 | for task in tasks { 65 | task.await; 66 | } 67 | }); 68 | }); 69 | }, 70 | *multithread, 71 | ); 72 | }); 73 | 74 | group.bench_function("executor::spawn_recursively", |b| { 75 | #[allow(clippy::manual_async_fn)] 76 | fn go(i: usize) -> impl Future + Send + 'static { 77 | async move { 78 | if i != 0 { 79 | EX.spawn(async move { 80 | let fut = go(i - 1).boxed(); 81 | fut.await; 82 | }) 83 | .await; 84 | } 85 | } 86 | } 87 | 88 | run( 89 | || { 90 | b.iter(move || { 91 | future::block_on(async { 92 | let mut tasks = Vec::new(); 93 | for _ in 0..TASKS { 94 | tasks.push(EX.spawn(go(STEPS))); 95 | } 96 | for task in tasks { 97 | task.await; 98 | } 99 | }); 100 | }); 101 | }, 102 | *multithread, 103 | ); 104 | }); 105 | 106 | group.bench_function("executor::yield_now", |b| { 107 | run( 108 | || { 109 | b.iter(move || { 110 | future::block_on(async { 111 | let mut tasks = Vec::new(); 112 | for _ in 0..TASKS { 113 | tasks.push(EX.spawn(async move { 114 | for _ in 0..STEPS { 115 | future::yield_now().await; 116 | } 117 | })); 118 | } 119 | for task in tasks { 120 | task.await; 121 | } 122 | }); 123 | }); 124 | }, 125 | *multithread, 126 | ); 127 | }); 128 | } 129 | } 130 | 131 | criterion_group!(benches, create, running_benches); 132 | 133 | criterion_main!(benches); 134 | -------------------------------------------------------------------------------- /examples/borrow.rs: -------------------------------------------------------------------------------- 1 | use edge_executor::{block_on, LocalExecutor}; 2 | 3 | fn main() { 4 | let local_ex: LocalExecutor = Default::default(); 5 | 6 | // Borrowed by `&mut` inside the future spawned on the executor 7 | let mut data = 3; 8 | 9 | let data = &mut data; 10 | 11 | let task = local_ex.spawn(async move { 12 | *data += 1; 13 | 14 | *data 15 | }); 16 | 17 | let res = block_on(local_ex.run(async { task.await * 2 })); 18 | 19 | assert_eq!(res, 8); 20 | } 21 | -------------------------------------------------------------------------------- /examples/parallel.rs: -------------------------------------------------------------------------------- 1 | use async_channel::unbounded; 2 | use easy_parallel::Parallel; 3 | 4 | use edge_executor::{block_on, Executor}; 5 | 6 | fn main() { 7 | let ex: Executor = Default::default(); 8 | let (signal, shutdown) = unbounded::<()>(); 9 | 10 | Parallel::new() 11 | // Run four executor threads. 12 | .each(0..4, |_| block_on(ex.run(shutdown.recv()))) 13 | // Run the main future on the current thread. 14 | .finish(|| { 15 | block_on(async { 16 | println!("Hello world!"); 17 | drop(signal); 18 | }) 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /examples/priority.rs: -------------------------------------------------------------------------------- 1 | //! An executor with task priorities. 2 | 3 | use std::future::Future; 4 | use std::thread; 5 | 6 | use edge_executor::{Executor, Task}; 7 | use futures_lite::{future, prelude::*}; 8 | 9 | /// Task priority. 10 | #[repr(usize)] 11 | #[derive(Debug, Clone, Copy)] 12 | enum Priority { 13 | High = 0, 14 | Medium = 1, 15 | Low = 2, 16 | } 17 | 18 | /// An executor with task priorities. 19 | /// 20 | /// Tasks with lower priorities only get polled when there are no tasks with higher priorities. 21 | struct PriorityExecutor<'a> { 22 | ex: [Executor<'a>; 3], 23 | } 24 | 25 | impl<'a> PriorityExecutor<'a> { 26 | /// Creates a new executor. 27 | const fn new() -> PriorityExecutor<'a> { 28 | PriorityExecutor { 29 | ex: [Executor::new(), Executor::new(), Executor::new()], 30 | } 31 | } 32 | 33 | /// Spawns a task with the given priority. 34 | fn spawn( 35 | &self, 36 | priority: Priority, 37 | future: impl Future + Send + 'a, 38 | ) -> Task { 39 | self.ex[priority as usize].spawn(future) 40 | } 41 | 42 | /// Runs the executor forever. 43 | async fn run(&self) { 44 | loop { 45 | for _ in 0..200 { 46 | let t0 = self.ex[0].tick(); 47 | let t1 = self.ex[1].tick(); 48 | let t2 = self.ex[2].tick(); 49 | 50 | // Wait until one of the ticks completes, trying them in order from highest 51 | // priority to lowest priority. 52 | t0.or(t1).or(t2).await; 53 | } 54 | 55 | // Yield every now and then. 56 | future::yield_now().await; 57 | } 58 | } 59 | } 60 | 61 | fn main() { 62 | static EX: PriorityExecutor<'_> = PriorityExecutor::new(); 63 | 64 | // Spawn a thread running the executor forever. 65 | thread::spawn(|| future::block_on(EX.run())); 66 | 67 | let mut tasks = Vec::new(); 68 | 69 | for _ in 0..20 { 70 | // Choose a random priority. 71 | let choice = [Priority::High, Priority::Medium, Priority::Low]; 72 | let priority = choice[fastrand::usize(..choice.len())]; 73 | 74 | // Spawn a task with this priority. 75 | tasks.push(EX.spawn(priority, async move { 76 | println!("{:?}", priority); 77 | future::yield_now().await; 78 | println!("{:?}", priority); 79 | })); 80 | } 81 | 82 | for task in tasks { 83 | future::block_on(task); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | #[cfg(all(feature = "heapless", feature = "unbounded"))] 4 | compile_error!("Feature `heapless` is not compatible with feature `unbounded`."); 5 | 6 | use core::future::{poll_fn, Future}; 7 | use core::marker::PhantomData; 8 | use core::task::{Context, Poll}; 9 | 10 | extern crate alloc; 11 | 12 | use alloc::rc::Rc; 13 | 14 | use async_task::Runnable; 15 | 16 | pub use async_task::{FallibleTask, Task}; 17 | 18 | use atomic_waker::AtomicWaker; 19 | use futures_lite::FutureExt; 20 | 21 | #[cfg(not(feature = "portable-atomic"))] 22 | use alloc::sync::Arc; 23 | #[cfg(feature = "portable-atomic")] 24 | use portable_atomic_util::Arc; 25 | 26 | use once_cell::sync::OnceCell; 27 | 28 | #[cfg(feature = "std")] 29 | pub use futures_lite::future::block_on; 30 | 31 | /// An async executor. 32 | /// 33 | /// # Examples 34 | /// 35 | /// A multi-threaded executor: 36 | /// 37 | /// ```ignore 38 | /// use async_channel::unbounded; 39 | /// use easy_parallel::Parallel; 40 | /// 41 | /// use edge_executor::{Executor, block_on}; 42 | /// 43 | /// let ex: Executor = Default::default(); 44 | /// let (signal, shutdown) = unbounded::<()>(); 45 | /// 46 | /// Parallel::new() 47 | /// // Run four executor threads. 48 | /// .each(0..4, |_| block_on(ex.run(shutdown.recv()))) 49 | /// // Run the main future on the current thread. 50 | /// .finish(|| block_on(async { 51 | /// println!("Hello world!"); 52 | /// drop(signal); 53 | /// })); 54 | /// ``` 55 | pub struct Executor<'a, const C: usize = 64> { 56 | state: OnceCell>>, 57 | _invariant: PhantomData>, 58 | } 59 | 60 | impl<'a, const C: usize> Executor<'a, C> { 61 | /// Creates a new executor. 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// use edge_executor::Executor; 67 | /// 68 | /// let ex: Executor = Default::default(); 69 | /// ``` 70 | pub const fn new() -> Self { 71 | Self { 72 | state: OnceCell::new(), 73 | _invariant: PhantomData, 74 | } 75 | } 76 | 77 | /// Spawns a task onto the executor. 78 | /// 79 | /// # Examples 80 | /// 81 | /// ``` 82 | /// use edge_executor::Executor; 83 | /// 84 | /// let ex: Executor = Default::default(); 85 | /// 86 | /// let task = ex.spawn(async { 87 | /// println!("Hello world"); 88 | /// }); 89 | /// ``` 90 | /// 91 | /// Note that if the executor's queue size is equal to the number of currently 92 | /// spawned and running tasks, spawning this additional task might cause the executor to panic 93 | /// later, when the task is scheduled for polling. 94 | pub fn spawn(&self, fut: F) -> Task 95 | where 96 | F: Future + Send + 'a, 97 | F::Output: Send + 'a, 98 | { 99 | unsafe { self.spawn_unchecked(fut) } 100 | } 101 | 102 | /// Attempts to run a task if at least one is scheduled. 103 | /// 104 | /// Running a scheduled task means simply polling its future once. 105 | /// 106 | /// # Examples 107 | /// 108 | /// ``` 109 | /// use edge_executor::Executor; 110 | /// 111 | /// let ex: Executor = Default::default(); 112 | /// assert!(!ex.try_tick()); // no tasks to run 113 | /// 114 | /// let task = ex.spawn(async { 115 | /// println!("Hello world"); 116 | /// }); 117 | /// assert!(ex.try_tick()); // a task was found 118 | /// ``` 119 | pub fn try_tick(&self) -> bool { 120 | if let Some(runnable) = self.try_runnable() { 121 | runnable.run(); 122 | 123 | true 124 | } else { 125 | false 126 | } 127 | } 128 | 129 | /// Runs a single task asynchronously. 130 | /// 131 | /// Running a task means simply polling its future once. 132 | /// 133 | /// If no tasks are scheduled when this method is called, it will wait until one is scheduled. 134 | /// 135 | /// # Examples 136 | /// 137 | /// ``` 138 | /// use edge_executor::{Executor, block_on}; 139 | /// 140 | /// let ex: Executor = Default::default(); 141 | /// 142 | /// let task = ex.spawn(async { 143 | /// println!("Hello world"); 144 | /// }); 145 | /// block_on(ex.tick()); // runs the task 146 | /// ``` 147 | pub async fn tick(&self) { 148 | self.runnable().await.run(); 149 | } 150 | 151 | /// Runs the executor asynchronously until the given future completes. 152 | /// 153 | /// # Examples 154 | /// 155 | /// ``` 156 | /// use edge_executor::{Executor, block_on}; 157 | /// 158 | /// let ex: Executor = Default::default(); 159 | /// 160 | /// let task = ex.spawn(async { 1 + 2 }); 161 | /// let res = block_on(ex.run(async { task.await * 2 })); 162 | /// 163 | /// assert_eq!(res, 6); 164 | /// ``` 165 | pub async fn run(&self, fut: F) -> F::Output 166 | where 167 | F: Future + Send + 'a, 168 | { 169 | unsafe { self.run_unchecked(fut).await } 170 | } 171 | 172 | /// Waits for the next runnable task to run. 173 | async fn runnable(&self) -> Runnable { 174 | poll_fn(|ctx| self.poll_runnable(ctx)).await 175 | } 176 | 177 | /// Polls the first task scheduled for execution by the executor. 178 | fn poll_runnable(&self, ctx: &Context<'_>) -> Poll { 179 | self.state().waker.register(ctx.waker()); 180 | 181 | if let Some(runnable) = self.try_runnable() { 182 | Poll::Ready(runnable) 183 | } else { 184 | Poll::Pending 185 | } 186 | } 187 | 188 | /// Pops the first task scheduled for execution by the executor. 189 | /// 190 | /// Returns 191 | /// - `None` - if no task was scheduled for execution 192 | /// - `Some(Runnnable)` - the first task scheduled for execution. Calling `Runnable::run` will 193 | /// execute the task. In other words, it will poll its future. 194 | fn try_runnable(&self) -> Option { 195 | let runnable; 196 | 197 | #[cfg(not(feature = "heapless"))] 198 | { 199 | runnable = self.state().queue.pop(); 200 | } 201 | 202 | #[cfg(feature = "heapless")] 203 | { 204 | runnable = self.state().queue.dequeue(); 205 | } 206 | 207 | runnable 208 | } 209 | 210 | unsafe fn spawn_unchecked(&self, fut: F) -> Task 211 | where 212 | F: Future, 213 | { 214 | let schedule = { 215 | let state = self.state().clone(); 216 | 217 | move |runnable| { 218 | #[cfg(all(not(feature = "heapless"), feature = "unbounded"))] 219 | { 220 | state.queue.push(runnable); 221 | } 222 | 223 | #[cfg(all(not(feature = "heapless"), not(feature = "unbounded")))] 224 | { 225 | state.queue.push(runnable).unwrap(); 226 | } 227 | 228 | #[cfg(feature = "heapless")] 229 | { 230 | state.queue.enqueue(runnable).unwrap(); 231 | } 232 | 233 | if let Some(waker) = state.waker.take() { 234 | waker.wake(); 235 | } 236 | } 237 | }; 238 | 239 | let (runnable, task) = unsafe { async_task::spawn_unchecked(fut, schedule) }; 240 | 241 | runnable.schedule(); 242 | 243 | task 244 | } 245 | 246 | async unsafe fn run_unchecked(&self, fut: F) -> F::Output 247 | where 248 | F: Future, 249 | { 250 | let run_forever = async { 251 | loop { 252 | self.tick().await; 253 | } 254 | }; 255 | 256 | run_forever.or(fut).await 257 | } 258 | 259 | /// Returns a reference to the inner state. 260 | fn state(&self) -> &Arc> { 261 | self.state.get_or_init(|| Arc::new(State::new())) 262 | } 263 | } 264 | 265 | impl<'a, const C: usize> Default for Executor<'a, C> { 266 | fn default() -> Self { 267 | Self::new() 268 | } 269 | } 270 | 271 | unsafe impl<'a, const C: usize> Send for Executor<'a, C> {} 272 | unsafe impl<'a, const C: usize> Sync for Executor<'a, C> {} 273 | 274 | /// A thread-local executor. 275 | /// 276 | /// The executor can only be run on the thread that created it. 277 | /// 278 | /// # Examples 279 | /// 280 | /// ``` 281 | /// use edge_executor::{LocalExecutor, block_on}; 282 | /// 283 | /// let local_ex: LocalExecutor = Default::default(); 284 | /// 285 | /// block_on(local_ex.run(async { 286 | /// println!("Hello world!"); 287 | /// })); 288 | /// ``` 289 | pub struct LocalExecutor<'a, const C: usize = 64> { 290 | executor: Executor<'a, C>, 291 | _not_send: PhantomData>>, 292 | } 293 | 294 | #[allow(clippy::missing_safety_doc)] 295 | impl<'a, const C: usize> LocalExecutor<'a, C> { 296 | /// Creates a single-threaded executor. 297 | /// 298 | /// # Examples 299 | /// 300 | /// ``` 301 | /// use edge_executor::LocalExecutor; 302 | /// 303 | /// let local_ex: LocalExecutor = Default::default(); 304 | /// ``` 305 | pub const fn new() -> Self { 306 | Self { 307 | executor: Executor::::new(), 308 | _not_send: PhantomData, 309 | } 310 | } 311 | 312 | /// Spawns a task onto the executor. 313 | /// 314 | /// # Examples 315 | /// 316 | /// ``` 317 | /// use edge_executor::LocalExecutor; 318 | /// 319 | /// let local_ex: LocalExecutor = Default::default(); 320 | /// 321 | /// let task = local_ex.spawn(async { 322 | /// println!("Hello world"); 323 | /// }); 324 | /// ``` 325 | /// 326 | /// Note that if the executor's queue size is equal to the number of currently 327 | /// spawned and running tasks, spawning this additional task might cause the executor to panic 328 | /// later, when the task is scheduled for polling. 329 | pub fn spawn(&self, fut: F) -> Task 330 | where 331 | F: Future + 'a, 332 | F::Output: 'a, 333 | { 334 | unsafe { self.executor.spawn_unchecked(fut) } 335 | } 336 | 337 | /// Attempts to run a task if at least one is scheduled. 338 | /// 339 | /// Running a scheduled task means simply polling its future once. 340 | /// 341 | /// # Examples 342 | /// 343 | /// ``` 344 | /// use edge_executor::LocalExecutor; 345 | /// 346 | /// let local_ex: LocalExecutor = Default::default(); 347 | /// assert!(!local_ex.try_tick()); // no tasks to run 348 | /// 349 | /// let task = local_ex.spawn(async { 350 | /// println!("Hello world"); 351 | /// }); 352 | /// assert!(local_ex.try_tick()); // a task was found 353 | /// ``` 354 | pub fn try_tick(&self) -> bool { 355 | self.executor.try_tick() 356 | } 357 | 358 | /// Runs a single task asynchronously. 359 | /// 360 | /// Running a task means simply polling its future once. 361 | /// 362 | /// If no tasks are scheduled when this method is called, it will wait until one is scheduled. 363 | /// 364 | /// # Examples 365 | /// 366 | /// ``` 367 | /// use edge_executor::{LocalExecutor, block_on}; 368 | /// 369 | /// let local_ex: LocalExecutor = Default::default(); 370 | /// 371 | /// let task = local_ex.spawn(async { 372 | /// println!("Hello world"); 373 | /// }); 374 | /// block_on(local_ex.tick()); // runs the task 375 | /// ``` 376 | pub async fn tick(&self) { 377 | self.executor.tick().await 378 | } 379 | 380 | /// Runs the executor asynchronously until the given future completes. 381 | /// 382 | /// # Examples 383 | /// 384 | /// ``` 385 | /// use edge_executor::{LocalExecutor, block_on}; 386 | /// 387 | /// let local_ex: LocalExecutor = Default::default(); 388 | /// 389 | /// let task = local_ex.spawn(async { 1 + 2 }); 390 | /// let res = block_on(local_ex.run(async { task.await * 2 })); 391 | /// 392 | /// assert_eq!(res, 6); 393 | /// ``` 394 | pub async fn run(&self, fut: F) -> F::Output 395 | where 396 | F: Future, 397 | { 398 | unsafe { self.executor.run_unchecked(fut) }.await 399 | } 400 | } 401 | 402 | impl<'a, const C: usize> Default for LocalExecutor<'a, C> { 403 | fn default() -> Self { 404 | Self::new() 405 | } 406 | } 407 | 408 | struct State { 409 | #[cfg(all(not(feature = "heapless"), feature = "unbounded"))] 410 | queue: crossbeam_queue::SegQueue, 411 | #[cfg(all(not(feature = "heapless"), not(feature = "unbounded")))] 412 | queue: crossbeam_queue::ArrayQueue, 413 | #[cfg(feature = "heapless")] 414 | queue: heapless::mpmc::MpMcQueue, 415 | waker: AtomicWaker, 416 | } 417 | 418 | impl State { 419 | fn new() -> Self { 420 | Self { 421 | #[cfg(all(not(feature = "heapless"), feature = "unbounded"))] 422 | queue: crossbeam_queue::SegQueue::new(), 423 | #[cfg(all(not(feature = "heapless"), not(feature = "unbounded")))] 424 | queue: crossbeam_queue::ArrayQueue::new(C), 425 | #[cfg(feature = "heapless")] 426 | queue: heapless::mpmc::MpMcQueue::new(), 427 | waker: AtomicWaker::new(), 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /tests/different_executors.rs: -------------------------------------------------------------------------------- 1 | use edge_executor::LocalExecutor; 2 | use futures_lite::future::{block_on, pending, poll_once}; 3 | use futures_lite::pin; 4 | use std::cell::Cell; 5 | 6 | #[test] 7 | fn shared_queue_slot() { 8 | block_on(async { 9 | let was_polled = Cell::new(false); 10 | let future = async { 11 | was_polled.set(true); 12 | pending::<()>().await; 13 | }; 14 | 15 | let ex1: LocalExecutor = Default::default(); 16 | let ex2: LocalExecutor = Default::default(); 17 | 18 | // Start the futures for running forever. 19 | let (run1, run2) = (ex1.run(pending::<()>()), ex2.run(pending::<()>())); 20 | pin!(run1); 21 | pin!(run2); 22 | assert!(poll_once(run1.as_mut()).await.is_none()); 23 | assert!(poll_once(run2.as_mut()).await.is_none()); 24 | 25 | // Spawn the future on executor one and then poll executor two. 26 | ex1.spawn(future).detach(); 27 | assert!(poll_once(run2).await.is_none()); 28 | assert!(!was_polled.get()); 29 | 30 | // Poll the first one. 31 | assert!(poll_once(run1).await.is_none()); 32 | assert!(was_polled.get()); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /tests/drop.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | //use std::panic::catch_unwind; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | use std::sync::Mutex; 5 | use std::task::{Poll, Waker}; 6 | 7 | use edge_executor::{Executor, Task}; 8 | use futures_lite::future; 9 | use once_cell::sync::Lazy; 10 | 11 | // #[test] 12 | // fn executor_cancels_everything() { 13 | // static DROP: AtomicUsize = AtomicUsize::new(0); 14 | // static WAKER: Lazy>> = Lazy::new(Default::default); 15 | 16 | // let ex: Executor = Default::default(); 17 | 18 | // let task = ex.spawn(async { 19 | // let _guard = CallOnDrop(|| { 20 | // DROP.fetch_add(1, Ordering::SeqCst); 21 | // }); 22 | 23 | // future::poll_fn(|cx| { 24 | // *WAKER.lock().unwrap() = Some(cx.waker().clone()); 25 | // Poll::Pending::<()> 26 | // }) 27 | // .await; 28 | // }); 29 | 30 | // future::block_on(ex.tick()); 31 | // assert!(WAKER.lock().unwrap().is_some()); 32 | // assert_eq!(DROP.load(Ordering::SeqCst), 0); 33 | 34 | // drop(ex); 35 | // assert_eq!(DROP.load(Ordering::SeqCst), 1); 36 | 37 | // assert!(catch_unwind(|| future::block_on(task)).is_err()); 38 | // assert_eq!(DROP.load(Ordering::SeqCst), 1); 39 | // } 40 | 41 | #[test] 42 | fn leaked_executor_leaks_everything() { 43 | static DROP: AtomicUsize = AtomicUsize::new(0); 44 | static WAKER: Lazy>> = Lazy::new(Default::default); 45 | 46 | let ex: Executor = Default::default(); 47 | 48 | let task = ex.spawn(async { 49 | let _guard = CallOnDrop(|| { 50 | DROP.fetch_add(1, Ordering::SeqCst); 51 | }); 52 | 53 | future::poll_fn(|cx| { 54 | *WAKER.lock().unwrap() = Some(cx.waker().clone()); 55 | Poll::Pending::<()> 56 | }) 57 | .await; 58 | }); 59 | 60 | future::block_on(ex.tick()); 61 | assert!(WAKER.lock().unwrap().is_some()); 62 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 63 | 64 | mem::forget(ex); 65 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 66 | 67 | assert!(future::block_on(future::poll_once(task)).is_none()); 68 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 69 | } 70 | 71 | #[test] 72 | fn await_task_after_dropping_executor() { 73 | let s: String = "hello".into(); 74 | 75 | let ex: Executor = Default::default(); 76 | let task: Task<&str> = ex.spawn(async { &*s }); 77 | assert!(ex.try_tick()); 78 | 79 | drop(ex); 80 | assert_eq!(future::block_on(task), "hello"); 81 | drop(s); 82 | } 83 | 84 | #[test] 85 | fn drop_executor_and_then_drop_finished_task() { 86 | static DROP: AtomicUsize = AtomicUsize::new(0); 87 | 88 | let ex: Executor = Default::default(); 89 | let task = ex.spawn(async { 90 | CallOnDrop(|| { 91 | DROP.fetch_add(1, Ordering::SeqCst); 92 | }) 93 | }); 94 | assert!(ex.try_tick()); 95 | 96 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 97 | drop(ex); 98 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 99 | drop(task); 100 | assert_eq!(DROP.load(Ordering::SeqCst), 1); 101 | } 102 | 103 | #[test] 104 | fn drop_finished_task_and_then_drop_executor() { 105 | static DROP: AtomicUsize = AtomicUsize::new(0); 106 | 107 | let ex: Executor = Default::default(); 108 | let task = ex.spawn(async { 109 | CallOnDrop(|| { 110 | DROP.fetch_add(1, Ordering::SeqCst); 111 | }) 112 | }); 113 | assert!(ex.try_tick()); 114 | 115 | assert_eq!(DROP.load(Ordering::SeqCst), 0); 116 | drop(task); 117 | assert_eq!(DROP.load(Ordering::SeqCst), 1); 118 | drop(ex); 119 | assert_eq!(DROP.load(Ordering::SeqCst), 1); 120 | } 121 | 122 | struct CallOnDrop(F); 123 | 124 | impl Drop for CallOnDrop { 125 | fn drop(&mut self) { 126 | (self.0)(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/local_queue.rs: -------------------------------------------------------------------------------- 1 | use edge_executor::Executor; 2 | use futures_lite::{future, pin}; 3 | 4 | #[test] 5 | fn two_queues() { 6 | future::block_on(async { 7 | // Create an executor with two runners. 8 | let ex: Executor = Default::default(); 9 | let (run1, run2) = ( 10 | ex.run(future::pending::<()>()), 11 | ex.run(future::pending::<()>()), 12 | ); 13 | let mut run1 = Box::pin(run1); 14 | pin!(run2); 15 | 16 | // Poll them both. 17 | assert!(future::poll_once(run1.as_mut()).await.is_none()); 18 | assert!(future::poll_once(run2.as_mut()).await.is_none()); 19 | 20 | // Drop the first one, which should leave the local queue in the `None` state. 21 | drop(run1); 22 | assert!(future::poll_once(run2.as_mut()).await.is_none()); 23 | }); 24 | } 25 | --------------------------------------------------------------------------------