├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE_APACHE ├── LICENSE_BOOST ├── LICENSE_MIT ├── README.md ├── examples ├── basic.rs ├── bench.rs ├── mpsc.rs ├── no-std │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── reuse.rs ├── tokio.rs └── weak.rs └── src ├── channel.rs ├── lib.rs ├── mutex.rs ├── queue.rs └── wake_list.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: tests 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest, windows-latest] 11 | tc: [1.70.0, stable, beta, nightly] 12 | ar: [--all --no-default-features -- --nocapture, --all --all-features -- --nocapture] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: ${{ matrix.tc }} 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: test 23 | args: ${{ matrix.ar }} 24 | cross-compile: 25 | runs-on: ${{ matrix.os }} 26 | strategy: 27 | matrix: 28 | os: [ubuntu-latest] 29 | tc: [1.70.0] 30 | cc: 31 | - aarch64-linux-android 32 | - i686-pc-windows-gnu 33 | - i686-unknown-freebsd 34 | - i686-unknown-linux-gnu 35 | - wasm32-unknown-unknown 36 | - x86_64-apple-darwin 37 | - x86_64-unknown-redox 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: ${{ matrix.tc }} 44 | target: ${{ matrix.cc }} 45 | override: true 46 | - uses: actions-rs/cargo@v1 47 | with: 48 | command: build 49 | args: --all-features --target=${{ matrix.cc }} 50 | cross-compile-ios: 51 | runs-on: ${{ matrix.os }} 52 | strategy: 53 | matrix: 54 | os: [macos-latest] 55 | tc: [1.70.0] 56 | cc: [aarch64-apple-ios] 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | profile: minimal 62 | toolchain: ${{ matrix.tc }} 63 | target: ${{ matrix.cc }} 64 | override: true 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: build 68 | args: --all-features --target=${{ matrix.cc }} 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Don't deviate too much, just reduce columns and extra organization enforcement 2 | edition = "2021" 3 | unstable_features = true 4 | max_width = 80 5 | reorder_impl_items = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Crate" 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to `whisk` will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://github.com/AldaronLau/semver). 6 | 7 | ## [0.13.0] - 2024-06-19 8 | ### Changed 9 | - Switched out `pasts` for `event_iterator` 10 | - Renamed `futures-core` feature to `futures_core_3` 11 | - Bumped MSRV to 1.70 12 | 13 | ## [0.12.1] - 2023-06-02 14 | ### Fixed 15 | - Weirdness with `Cargo.toml`'s `[[example]]` section and no-std example in CI. 16 | 17 | ## [0.12.0] - 2023-06-02 18 | ### Fixed 19 | - Default features being pulled in for `pasts` and `futures-core` optional 20 | dependencies when enabled 21 | 22 | ## [0.11.0] - 2023-02-19 23 | ### Changed 24 | - Update pasts to 0.14.x 25 | 26 | ## [0.10.0] - 2023-01-18 27 | ### Changed 28 | - Update pasts to 0.13.x 29 | 30 | ## [0.9.1] - 2022-11-26 31 | ### Fixed 32 | - Use after free bug (drop order) 33 | 34 | ## [0.9.0] - 2022-11-25 35 | ### Added 36 | - Implement `From` converting `Arc` to `Channel` 37 | - Implement `From` converting `Channel` to `Arc` 38 | - Separate `Queue` and `Channel` types 39 | - `Channel::with()` for storing additional data within the internal `Arc` 40 | - `Queue::with()` for storing additional data within 41 | - `Deref` implementation for accessing additional use data within `Channel` 42 | - `Deref` implementation for accessing additional use data within `Queue` 43 | 44 | ### Changed 45 | - Changed to lockless implementation (no more spinlocks) 46 | - Old `Channel` type is now called `Queue` 47 | - `Future`, `Notifier` and `Stream` are now implemented directly on `Channel` 48 | rather than on a shared reference. 49 | - Bump MSRV to Rust 1.65 50 | 51 | ### Removed 52 | - `Chan`, `Stream`, `WeakChan` and `WeakStream` type aliases 53 | 54 | ## [0.8.0] - 2022-09-28 55 | ### Added 56 | - `Chan`, `Stream`, `WeakChan`, `WeakStream` type aliases 57 | 58 | ### Removed 59 | - `Weak`, no longer necessary 60 | 61 | ### Changed 62 | - MSRV bumped to 1.64.0 63 | - `Channel::new()` no longer contains an `Arc`, so it's no longer `Clone`. The 64 | usage of `Arc` is now up to the user of the library. 65 | - `Channel::new()` is now a `const fn` 66 | 67 | ## [0.7.0] - 2022-08-19 68 | ### Changed 69 | - `Weak` and `Channel` now have a default for generics: `T = ()` 70 | 71 | ## [0.6.0] - 2022-08-14 72 | ### Added 73 | - `Weak::try_send()` 74 | - `Weak::try_recv()` 75 | 76 | ### Changed 77 | - `Channel::send()` back to `async fn` 78 | - `Channel::recv()` back to `async fn` 79 | 80 | ## [0.5.0] - 2022-08-06 81 | ### Added 82 | - `Weak` reference to a `Channel` 83 | - Implement `Future` for `&Channel` 84 | - Implement `Notifier` for `&Channel` 85 | - Implement `Stream` for `&Channel` 86 | 87 | ### Changed 88 | - Updated pasts to 0.12 89 | - `Channel::recv()` from an `async fn` to a `fn() -> impl Future` 90 | - `Channel::send()` from returning `Message` to returning `impl Future` 91 | - `Channel::recv()` no longer requires a mutable reference 92 | - `Channel` now supports `T: !Unpin` 93 | 94 | ### Removed 95 | - `Message` 96 | - Const generic arguments on `Channel` 97 | - Panic on uncompleted message send 98 | 99 | ### Fixed 100 | - Bug with wakers when using MPMC functionality that could possibly trigger UB 101 | 102 | ## [0.4.1] - 2022-07-23 103 | ### Fixed 104 | - Error in documentation 105 | 106 | ## [0.4.0] - 2022-07-22 107 | ### Added 108 | - **`pasts`** feature for `Channel` to implement `Notifier` 109 | - **`futures-core`** feature for `Channel>` to implement `Stream` 110 | - `Message` future type 111 | - `Channel::new()` for creating a channel 112 | - `Channel::send()` for sending a message on a channel 113 | - `Channel::recv()` for receiving a message from a channel 114 | 115 | ### Changed 116 | - Channels are now MPMC instead of SPSC 117 | - Less unsafe code, simpler implementation 118 | - Channels are now symmetrical 119 | 120 | ### Removed 121 | - `Sender` - Functionality built into `Channel` now 122 | - `Receiver` - Functionality built into `Message` now 123 | - `Worker` - Functionality built into `Message` now 124 | - `Tasker` - Functionality built into `Channel` now 125 | - **`std`** feature 126 | - `Channel::to_pair()` - Use `Channel::new()` instead 127 | - `Channel::pair()` - Use `Channel::new()` instead 128 | 129 | ## [0.3.0] - 2022-07-10 130 | ### Added 131 | - `Channel` struct, and `Channel::to_pair()` for reusing oneshot channels 132 | - `Sender` and `Receiver` oneshot-rendezvous channels 133 | - `Worker::stop()` to close a spsc channel 134 | 135 | ### Changed 136 | - Replaced `channel()`, with `Channel::pair()` 137 | - Renamed `Commander` to `Worker`, and `Messenger` to `Tasker` 138 | 139 | ### Removed 140 | - `Command`, `Message` types 141 | - `Commander::start()` and `Messenger::start()`, use `Worker::send()` and 142 | `Commander::recv_next()` instead 143 | 144 | ## [0.2.1] - 2022-04-19 145 | ### Changed 146 | - Small optimizations 147 | 148 | ## [0.2.0] - 2022-04-19 149 | ### Added 150 | - `start()` async methods on both `Commander` and `Messenger` for initiating 151 | the connection. 152 | 153 | ### Changed 154 | - `Commander` and `Messenger` changed from `Future`s to `Iterator`s. 155 | 156 | ### Removed 157 | - `close()` methods on both `Commander` and `Messenger`, now you can just rely 158 | on dropping to close channels. 159 | 160 | ### Fixed 161 | - Incorrect assumptions about wakers 162 | 163 | ## [0.1.1] - 2022-03-04 164 | ### Fixed 165 | - Deadlock condition caused by wrong drop order 166 | 167 | ## [0.1.0] - 2022-02-27 168 | ### Added 169 | - `Command` 170 | - `Commander` 171 | - `Message` 172 | - `Messenger` 173 | - `channel()` 174 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async_main" 22 | version = "0.4.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "9229c055d66cba2b97b10381b7a1c56db6bdd3234daa4bee21260e96081460a1" 25 | dependencies = [ 26 | "async_main_macro", 27 | "pasts", 28 | ] 29 | 30 | [[package]] 31 | name = "async_main_macro" 32 | version = "0.4.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "2c61a19d4ab2db23de9a52d0085948e6647c0abd46cf0361de57e6716b65c785" 35 | 36 | [[package]] 37 | name = "autocfg" 38 | version = "1.3.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 41 | 42 | [[package]] 43 | name = "backtrace" 44 | version = "0.3.73" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 47 | dependencies = [ 48 | "addr2line", 49 | "cc", 50 | "cfg-if", 51 | "libc", 52 | "miniz_oxide", 53 | "object", 54 | "rustc-demangle", 55 | ] 56 | 57 | [[package]] 58 | name = "bumpalo" 59 | version = "3.16.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 62 | 63 | [[package]] 64 | name = "cc" 65 | version = "1.0.99" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" 68 | 69 | [[package]] 70 | name = "cfg-if" 71 | version = "1.0.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 74 | 75 | [[package]] 76 | name = "dl_api" 77 | version = "0.4.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "8e8752073dc1bae5debb147497c1f0e92abe69b7680c97411e99bf77390967a3" 80 | dependencies = [ 81 | "winapi", 82 | ] 83 | 84 | [[package]] 85 | name = "equivalent" 86 | version = "1.0.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 89 | 90 | [[package]] 91 | name = "event_iterator" 92 | version = "0.1.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "eaf8de2b62324f0a6350d1a8c64d07066635ff1ee5a1470f2bbd4f9af02cfb69" 95 | 96 | [[package]] 97 | name = "flume" 98 | version = "0.11.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" 101 | dependencies = [ 102 | "futures-core", 103 | "futures-sink", 104 | "nanorand", 105 | "spin", 106 | ] 107 | 108 | [[package]] 109 | name = "futures" 110 | version = "0.3.30" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 113 | dependencies = [ 114 | "futures-channel", 115 | "futures-core", 116 | "futures-executor", 117 | "futures-io", 118 | "futures-sink", 119 | "futures-task", 120 | "futures-util", 121 | ] 122 | 123 | [[package]] 124 | name = "futures-channel" 125 | version = "0.3.30" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 128 | dependencies = [ 129 | "futures-core", 130 | "futures-sink", 131 | ] 132 | 133 | [[package]] 134 | name = "futures-core" 135 | version = "0.3.30" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 138 | 139 | [[package]] 140 | name = "futures-executor" 141 | version = "0.3.30" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 144 | dependencies = [ 145 | "futures-core", 146 | "futures-task", 147 | "futures-util", 148 | ] 149 | 150 | [[package]] 151 | name = "futures-io" 152 | version = "0.3.30" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 155 | 156 | [[package]] 157 | name = "futures-macro" 158 | version = "0.3.30" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "syn 2.0.66", 165 | ] 166 | 167 | [[package]] 168 | name = "futures-sink" 169 | version = "0.3.30" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 172 | 173 | [[package]] 174 | name = "futures-task" 175 | version = "0.3.30" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 178 | 179 | [[package]] 180 | name = "futures-util" 181 | version = "0.3.30" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 184 | dependencies = [ 185 | "futures-channel", 186 | "futures-core", 187 | "futures-io", 188 | "futures-macro", 189 | "futures-sink", 190 | "futures-task", 191 | "memchr", 192 | "pin-project-lite", 193 | "pin-utils", 194 | "slab", 195 | ] 196 | 197 | [[package]] 198 | name = "getrandom" 199 | version = "0.2.15" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 202 | dependencies = [ 203 | "cfg-if", 204 | "js-sys", 205 | "libc", 206 | "wasi", 207 | "wasm-bindgen", 208 | ] 209 | 210 | [[package]] 211 | name = "gimli" 212 | version = "0.29.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 215 | 216 | [[package]] 217 | name = "hashbrown" 218 | version = "0.14.5" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 221 | 222 | [[package]] 223 | name = "hermit-abi" 224 | version = "0.3.9" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 227 | 228 | [[package]] 229 | name = "indexmap" 230 | version = "2.2.6" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 233 | dependencies = [ 234 | "equivalent", 235 | "hashbrown", 236 | ] 237 | 238 | [[package]] 239 | name = "js-sys" 240 | version = "0.3.69" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 243 | dependencies = [ 244 | "wasm-bindgen", 245 | ] 246 | 247 | [[package]] 248 | name = "libc" 249 | version = "0.2.155" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 252 | 253 | [[package]] 254 | name = "libm" 255 | version = "0.2.8" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 258 | 259 | [[package]] 260 | name = "lock_api" 261 | version = "0.4.12" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 264 | dependencies = [ 265 | "autocfg", 266 | "scopeguard", 267 | ] 268 | 269 | [[package]] 270 | name = "log" 271 | version = "0.4.21" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 274 | 275 | [[package]] 276 | name = "memchr" 277 | version = "2.7.4" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 280 | 281 | [[package]] 282 | name = "miniz_oxide" 283 | version = "0.7.4" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 286 | dependencies = [ 287 | "adler", 288 | ] 289 | 290 | [[package]] 291 | name = "nanorand" 292 | version = "0.7.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 295 | dependencies = [ 296 | "getrandom", 297 | ] 298 | 299 | [[package]] 300 | name = "ntest" 301 | version = "0.9.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" 304 | dependencies = [ 305 | "ntest_test_cases", 306 | "ntest_timeout", 307 | ] 308 | 309 | [[package]] 310 | name = "ntest_test_cases" 311 | version = "0.9.3" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "syn 1.0.109", 318 | ] 319 | 320 | [[package]] 321 | name = "ntest_timeout" 322 | version = "0.9.3" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" 325 | dependencies = [ 326 | "proc-macro-crate", 327 | "proc-macro2", 328 | "quote", 329 | "syn 1.0.109", 330 | ] 331 | 332 | [[package]] 333 | name = "num_cpus" 334 | version = "1.16.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 337 | dependencies = [ 338 | "hermit-abi", 339 | "libc", 340 | ] 341 | 342 | [[package]] 343 | name = "object" 344 | version = "0.36.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" 347 | dependencies = [ 348 | "memchr", 349 | ] 350 | 351 | [[package]] 352 | name = "once_cell" 353 | version = "1.19.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 356 | 357 | [[package]] 358 | name = "pasts" 359 | version = "0.14.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "efcd36303871fb977a47dabc9af736c75c492bb32a92fa26262b2741531e97ce" 362 | 363 | [[package]] 364 | name = "pin-project-lite" 365 | version = "0.2.14" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 368 | 369 | [[package]] 370 | name = "pin-utils" 371 | version = "0.1.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 374 | 375 | [[package]] 376 | name = "proc-macro-crate" 377 | version = "3.1.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" 380 | dependencies = [ 381 | "toml_edit", 382 | ] 383 | 384 | [[package]] 385 | name = "proc-macro2" 386 | version = "1.0.85" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 389 | dependencies = [ 390 | "unicode-ident", 391 | ] 392 | 393 | [[package]] 394 | name = "quote" 395 | version = "1.0.36" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 398 | dependencies = [ 399 | "proc-macro2", 400 | ] 401 | 402 | [[package]] 403 | name = "rustc-demangle" 404 | version = "0.1.24" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 407 | 408 | [[package]] 409 | name = "scopeguard" 410 | version = "1.2.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 413 | 414 | [[package]] 415 | name = "slab" 416 | version = "0.4.9" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 419 | dependencies = [ 420 | "autocfg", 421 | ] 422 | 423 | [[package]] 424 | name = "spin" 425 | version = "0.9.8" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 428 | dependencies = [ 429 | "lock_api", 430 | ] 431 | 432 | [[package]] 433 | name = "syn" 434 | version = "1.0.109" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "unicode-ident", 441 | ] 442 | 443 | [[package]] 444 | name = "syn" 445 | version = "2.0.66" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 448 | dependencies = [ 449 | "proc-macro2", 450 | "quote", 451 | "unicode-ident", 452 | ] 453 | 454 | [[package]] 455 | name = "tokio" 456 | version = "1.38.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 459 | dependencies = [ 460 | "backtrace", 461 | "num_cpus", 462 | "pin-project-lite", 463 | "tokio-macros", 464 | ] 465 | 466 | [[package]] 467 | name = "tokio-macros" 468 | version = "2.3.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "syn 2.0.66", 475 | ] 476 | 477 | [[package]] 478 | name = "tokio-stream" 479 | version = "0.1.15" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" 482 | dependencies = [ 483 | "futures-core", 484 | "pin-project-lite", 485 | "tokio", 486 | ] 487 | 488 | [[package]] 489 | name = "toml_datetime" 490 | version = "0.6.6" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" 493 | 494 | [[package]] 495 | name = "toml_edit" 496 | version = "0.21.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" 499 | dependencies = [ 500 | "indexmap", 501 | "toml_datetime", 502 | "winnow", 503 | ] 504 | 505 | [[package]] 506 | name = "unicode-ident" 507 | version = "1.0.12" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 510 | 511 | [[package]] 512 | name = "wasi" 513 | version = "0.11.0+wasi-snapshot-preview1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 516 | 517 | [[package]] 518 | name = "wasm-bindgen" 519 | version = "0.2.92" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 522 | dependencies = [ 523 | "cfg-if", 524 | "wasm-bindgen-macro", 525 | ] 526 | 527 | [[package]] 528 | name = "wasm-bindgen-backend" 529 | version = "0.2.92" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 532 | dependencies = [ 533 | "bumpalo", 534 | "log", 535 | "once_cell", 536 | "proc-macro2", 537 | "quote", 538 | "syn 2.0.66", 539 | "wasm-bindgen-shared", 540 | ] 541 | 542 | [[package]] 543 | name = "wasm-bindgen-macro" 544 | version = "0.2.92" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 547 | dependencies = [ 548 | "quote", 549 | "wasm-bindgen-macro-support", 550 | ] 551 | 552 | [[package]] 553 | name = "wasm-bindgen-macro-support" 554 | version = "0.2.92" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 557 | dependencies = [ 558 | "proc-macro2", 559 | "quote", 560 | "syn 2.0.66", 561 | "wasm-bindgen-backend", 562 | "wasm-bindgen-shared", 563 | ] 564 | 565 | [[package]] 566 | name = "wasm-bindgen-shared" 567 | version = "0.2.92" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 570 | 571 | [[package]] 572 | name = "whisk" 573 | version = "0.13.0" 574 | dependencies = [ 575 | "async_main", 576 | "dl_api", 577 | "event_iterator", 578 | "flume", 579 | "futures", 580 | "futures-core", 581 | "libm", 582 | "ntest", 583 | "pasts", 584 | "tokio", 585 | "tokio-stream", 586 | ] 587 | 588 | [[package]] 589 | name = "winapi" 590 | version = "0.3.9" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 593 | dependencies = [ 594 | "winapi-i686-pc-windows-gnu", 595 | "winapi-x86_64-pc-windows-gnu", 596 | ] 597 | 598 | [[package]] 599 | name = "winapi-i686-pc-windows-gnu" 600 | version = "0.4.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 603 | 604 | [[package]] 605 | name = "winapi-x86_64-pc-windows-gnu" 606 | version = "0.4.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 609 | 610 | [[package]] 611 | name = "winnow" 612 | version = "0.5.40" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 615 | dependencies = [ 616 | "memchr", 617 | ] 618 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Whisk 2 | # Copyright © 2022-2024 Jeron Aldaron Lau. 3 | # 4 | # Licensed under any of: 5 | # - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) 6 | # - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) 7 | # - MIT License (https://mit-license.org/) 8 | # At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, 9 | # LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). 10 | 11 | [package] 12 | name = "whisk" 13 | version = "0.13.0" 14 | license = "Apache-2.0 OR BSL-1.0 OR MIT" 15 | description = "Simple and fast lockless async channels" 16 | repository = "https://github.com/ardaku/whisk" 17 | documentation = "https://docs.rs/whisk" 18 | homepage = "https://github.com/ardaku/whisk/blob/stable/CHANGELOG.md" 19 | include = [ 20 | "/LICENSE_APACHE", 21 | "/LICENSE_BOOST", 22 | "/LICENSE_MIT", 23 | "/README.md", 24 | "/src/*", 25 | "/examples/tokio.rs", 26 | ] 27 | categories = [ 28 | "asynchronous", 29 | "concurrency", 30 | "embedded", 31 | "hardware-support", 32 | "no-std", 33 | ] 34 | keywords = ["channel", "actor", "mpmc", "notifier", "event_iterator"] 35 | readme = "README.md" 36 | edition = "2021" 37 | rust-version = "1.70" 38 | 39 | [[example]] 40 | name = "tokio" 41 | required-features = ["futures_core_3"] 42 | 43 | [dependencies.futures_core_3] 44 | package = "futures-core" 45 | version = "0.3" 46 | optional = true 47 | default-features = false 48 | 49 | [dependencies.event_iterator] 50 | version = "0.1" 51 | optional = true 52 | 53 | [dev-dependencies] 54 | async_main = { version = "0.4", features = ["pasts"] } 55 | dl_api = "0.4" 56 | flume = "0.11" 57 | futures = "0.3" 58 | libm = "0.2" 59 | ntest = "0.9" 60 | tokio-stream = "0.1" 61 | 62 | [dev-dependencies.pasts] 63 | version = "0.14" 64 | default-features = false 65 | 66 | [dev-dependencies.tokio] 67 | version = "1.28" 68 | default-features = false 69 | features = ["rt-multi-thread", "macros"] 70 | 71 | [features] 72 | default = [] 73 | 74 | [profile.dev] 75 | panic = "abort" 76 | 77 | [profile.release] 78 | panic = "abort" 79 | 80 | [package.metadata.docs.rs] 81 | all-features = true 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE_BOOST: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whisk 2 | 3 | [![tests](https://github.com/ardaku/whisk/actions/workflows/ci.yml/badge.svg)](https://github.com/ardaku/whisk/actions/workflows/ci.yml) 4 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/ardaku/whisk)](https://github.com/ardaku/whisk/) 5 | [![GitHub contributors](https://img.shields.io/github/contributors/ardaku/whisk)](https://github.com/ardaku/whisk/graphs/contributors) 6 | [![Crates.io](https://img.shields.io/crates/v/whisk)](https://crates.io/crates/whisk) 7 | [![Crates.io](https://img.shields.io/crates/d/whisk)](https://crates.io/crates/whisk) 8 | [![Crates.io (recent)](https://img.shields.io/crates/dr/whisk)](https://crates.io/crates/whisk) 9 | [![Crates.io](https://img.shields.io/crates/l/whisk)](https://github.com/ardaku/whisk/search?l=Text&q=license) 10 | [![Docs.rs](https://docs.rs/whisk/badge.svg)](https://docs.rs/whisk/) 11 | 12 | #### Simple and fast lockless async channels 13 | 14 | Simple and fast async channels that can be used to implement futures, streams, 15 | notifiers, and actors. Whisk is purposely kept small, implemented in under 1000 16 | lines of Rust code, with zero dependencies (not including feature flags to 17 | enable implementation of traits from other crates) - and also works on `no_std`! 18 | 19 | ## Benchmarks 20 | 21 | Naïve benchmarks for v0.13.0 actor on pasts runtime (compared with dynamic 22 | library) with Rust 1.78.0: 23 | 24 | > ``` 25 | > Dynamic library: 12ns 26 | > Whisk (2-thread): 1.102µs 27 | > Flume (2-thread): 1.013µs 28 | > Whisk (1-thread): 1.151µs 29 | > Flume (1-thread): 1.532µs 30 | > ``` 31 | 32 | ## MSRV 33 | 34 | The current MSRV is Rust 1.70. 35 | 36 | MSRV is updated according to the [Ardaku MSRV guidelines]. 37 | 38 | ## License 39 | 40 | Copyright © 2022-2024 The Whisk Crate Contributor(s) 41 | 42 | Licensed under any of 43 | - Apache License, Version 2.0, ([LICENSE_APACHE] or 44 | ) 45 | - Boost Software License, Version 1.0, ([LICENSE_BOOST] or 46 | ) 47 | - MIT License, ([LICENSE_MIT] or ) 48 | 49 | at your option. 50 | 51 | ### Contribution 52 | 53 | Unless you explicitly state otherwise, any contribution intentionally submitted 54 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 55 | dual licensed as above, without any additional terms or conditions. 56 | 57 | ## Help 58 | 59 | If you want help using or contributing to this library, feel free to send me an 60 | email at . 61 | 62 | [Ardaku MSRV guidelines]: https://github.com/ardaku/.github/blob/v1/profile/MSRV.md 63 | [LICENSE_APACHE]: https://github.com/ardaku/whisk/blob/v0/LICENSE_APACHE 64 | [LICENSE_MIT]: https://github.com/ardaku/whisk/blob/v0/LICENSE_MIT 65 | [LICENSE_BOOST]: https://github.com/ardaku/whisk/blob/v0/LICENSE_BOOST 66 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use whisk::Channel; 2 | 3 | enum Cmd { 4 | /// Tell messenger to add 5 | Add(u32, u32, Channel), 6 | } 7 | 8 | async fn worker_main(commands: Channel>) { 9 | while let Some(command) = commands.recv().await { 10 | println!("Worker receiving command"); 11 | match command { 12 | Cmd::Add(a, b, s) => s.send(a + b).await, 13 | } 14 | } 15 | 16 | println!("Worker stopping…"); 17 | } 18 | 19 | // Call into executor of your choice 20 | #[async_main::async_main] 21 | async fn main(_spawner: impl async_main::Spawn) { 22 | // Create worker on new thread 23 | println!("Spawning worker…"); 24 | let channel = Channel::new(); 25 | let worker_task = worker_main(channel.clone()); 26 | let worker_thread = 27 | std::thread::spawn(|| pasts::Executor::default().block_on(worker_task)); 28 | 29 | // Do an addition 30 | println!("Sending command…"); 31 | let oneshot = Channel::new(); 32 | channel.send(Some(Cmd::Add(43, 400, oneshot.clone()))).await; 33 | println!("Receiving response…"); 34 | let response = oneshot.recv().await; 35 | assert_eq!(response, 443); 36 | 37 | // Tell worker to stop 38 | println!("Stopping worker…"); 39 | channel.send(None).await; 40 | println!("Waiting for worker to stop…"); 41 | 42 | worker_thread.join().unwrap(); 43 | println!("Worker thread joined"); 44 | } 45 | -------------------------------------------------------------------------------- /examples/bench.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, time::Instant}; 2 | 3 | use dl_api::manual::DlApi; 4 | use pasts::Executor; 5 | use whisk::Channel; 6 | 7 | enum Cmd { 8 | /// Tell messenger to get cosine 9 | Cos(f32, Channel), 10 | } 11 | 12 | async fn worker(channel: Channel>) { 13 | while let Some(command) = channel.recv().await { 14 | match command { 15 | Cmd::Cos(a, s) => s.send(libm::cosf(a)).await, 16 | } 17 | } 18 | } 19 | 20 | async fn worker_flume(tasker: flume::Receiver) { 21 | while let Ok(command) = tasker.recv_async().await { 22 | match command { 23 | Cmd::Cos(a, s) => s.send(libm::cosf(a)).await, 24 | } 25 | } 26 | } 27 | 28 | async fn tasker_multi() { 29 | // Create worker on new thread 30 | let chan = Channel::new(); 31 | let tasker = chan.clone(); 32 | let worker_thread = std::thread::spawn(move || { 33 | Executor::default().block_on(async move { worker(tasker).await }) 34 | }); 35 | let worker = chan; 36 | 37 | let channel = Channel::new(); 38 | for _ in 1..=1024 { 39 | worker.send(Some(Cmd::Cos(750.0, channel.clone()))).await; 40 | channel.recv().await; 41 | } 42 | let now = Instant::now(); 43 | for _ in 1..=1024 * 256 { 44 | worker.send(Some(Cmd::Cos(750.0, channel.clone()))).await; 45 | channel.recv().await; 46 | } 47 | let elapsed = now.elapsed() / (1024 * 256); 48 | println!("Whisk (2-thread): {:?}", elapsed); 49 | 50 | // Tell worker to stop 51 | worker.send(None).await; 52 | worker_thread.join().unwrap(); 53 | } 54 | 55 | async fn tasker_single(executor: &Executor) { 56 | // Create worker on new thread 57 | let chan = Channel::new(); 58 | let join = Channel::new(); 59 | executor.spawn_boxed({ 60 | let tasker = chan.clone(); 61 | let join = join.clone(); 62 | async move { 63 | worker(tasker).await; 64 | join.send(()).await; 65 | } 66 | }); 67 | let worker = chan; 68 | 69 | let channel = Channel::new(); 70 | for _ in 1..=1024 { 71 | worker.send(Some(Cmd::Cos(750.0, channel.clone()))).await; 72 | channel.recv().await; 73 | } 74 | let now = Instant::now(); 75 | for _ in 1..=1024 * 256 { 76 | worker.send(Some(Cmd::Cos(750.0, channel.clone()))).await; 77 | channel.recv().await; 78 | } 79 | let elapsed = now.elapsed() / (1024 * 256); 80 | println!("Whisk (1-thread): {:?}", elapsed); 81 | 82 | // Tell worker to stop 83 | worker.send(None).await; 84 | join.recv().await; 85 | } 86 | 87 | async fn flume_multi() { 88 | // Create worker on new thread 89 | let (worker, tasker) = flume::bounded(1); 90 | let worker_thread = std::thread::spawn(move || { 91 | Executor::default().block_on(async move { worker_flume(tasker).await }) 92 | }); 93 | 94 | let channel = Channel::new(); 95 | for _ in 1..=1024 { 96 | worker 97 | .send_async(Cmd::Cos(750.0, channel.clone())) 98 | .await 99 | .unwrap(); 100 | channel.recv().await; 101 | } 102 | let now = Instant::now(); 103 | for _ in 1..=1024 * 256 { 104 | worker 105 | .send_async(Cmd::Cos(750.0, channel.clone())) 106 | .await 107 | .unwrap(); 108 | channel.recv().await; 109 | } 110 | let elapsed = now.elapsed() / (1024 * 256); 111 | println!("Flume (2-thread): {:?}", elapsed); 112 | 113 | // Tell worker to stop 114 | drop(worker); 115 | worker_thread.join().unwrap(); 116 | } 117 | 118 | async fn flume_single(executor: &Executor) { 119 | // Create worker on new thread 120 | let join = Channel::new(); 121 | let (worker, tasker) = flume::bounded(1); 122 | executor.spawn_boxed({ 123 | let join = join.clone(); 124 | async move { 125 | worker_flume(tasker).await; 126 | join.send(()).await; 127 | } 128 | }); 129 | 130 | let channel = Channel::new(); 131 | for _ in 1..=1024 { 132 | worker 133 | .send_async(Cmd::Cos(750.0, channel.clone())) 134 | .await 135 | .unwrap(); 136 | channel.recv().await; 137 | } 138 | let now = Instant::now(); 139 | for _ in 1..=1024 * 256 { 140 | worker 141 | .send_async(Cmd::Cos(750.0, channel.clone())) 142 | .await 143 | .unwrap(); 144 | channel.recv().await; 145 | } 146 | let elapsed = now.elapsed() / (1024 * 256); 147 | println!("Flume (1-thread): {:?}", elapsed); 148 | 149 | // Tell worker to stop 150 | drop(worker); 151 | join.recv().await; 152 | } 153 | 154 | async fn dyn_lib() { 155 | let dl_api = 156 | DlApi::new(CStr::from_bytes_with_nul(b"libm.so.6\0").unwrap()).unwrap(); 157 | let cosf: unsafe extern "C" fn(f32) -> f32 = unsafe { 158 | std::mem::transmute( 159 | dl_api 160 | .get(CStr::from_bytes_with_nul(b"cosf\0").unwrap()) 161 | .unwrap(), 162 | ) 163 | }; 164 | 165 | for _ in 1..=1024 { 166 | unsafe { 167 | core::convert::identity(cosf(750.0)); 168 | } 169 | } 170 | let now = Instant::now(); 171 | for _ in 1..=1024 * 256 { 172 | unsafe { 173 | core::convert::identity(cosf(750.0)); 174 | } 175 | } 176 | let elapsed = now.elapsed() / (1024 * 256); 177 | println!("Dynamic library: {:?}", elapsed); 178 | } 179 | 180 | async fn tasker(executor: Executor) { 181 | dyn_lib().await; 182 | tasker_multi().await; 183 | flume_multi().await; 184 | tasker_single(&executor).await; 185 | flume_single(&executor).await; 186 | } 187 | 188 | // Call into executor of your choice 189 | fn main() { 190 | let executor = Executor::default(); 191 | executor.clone().block_on(tasker(executor)); 192 | } 193 | -------------------------------------------------------------------------------- /examples/mpsc.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, Ordering}, 3 | Arc, Weak, 4 | }; 5 | 6 | use whisk::{Channel, Queue}; 7 | 8 | static ONCE: AtomicBool = AtomicBool::new(true); 9 | 10 | fn main() { 11 | let executor = pasts::Executor::default(); 12 | let channel = Channel::new(); 13 | for _ in 0..24 { 14 | let channel = channel.clone(); 15 | std::thread::spawn(|| { 16 | pasts::Executor::default().block_on(async move { 17 | println!("Sending..."); 18 | channel.send(Some(1)).await; 19 | let weak: Weak> = Arc::downgrade(&channel.into()); 20 | if Weak::strong_count(&weak) == 1 21 | && ONCE.fetch_and(false, Ordering::Relaxed) 22 | { 23 | weak.upgrade().unwrap().send(None).await; 24 | } 25 | }) 26 | }); 27 | } 28 | executor.block_on(async move { 29 | let mut c = 0; 30 | while let Some(v) = channel.recv().await { 31 | println!("Received one."); 32 | c += v; 33 | } 34 | println!("Received all."); 35 | assert_eq!(c, 24); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /examples/no-std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "base64" 7 | version = "0.13.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "const-default" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" 22 | 23 | [[package]] 24 | name = "libc" 25 | version = "0.2.144" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 28 | 29 | [[package]] 30 | name = "no-std" 31 | version = "0.1.0" 32 | dependencies = [ 33 | "pasts", 34 | "rlsf", 35 | "whisk", 36 | ] 37 | 38 | [[package]] 39 | name = "pasts" 40 | version = "0.14.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "5f48fcb2261aeb31c8d00098235381aa8ff4fdd387e018e63aa4ffd24b0cfc8a" 43 | 44 | [[package]] 45 | name = "proc-macro2" 46 | version = "1.0.59" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" 49 | dependencies = [ 50 | "unicode-ident", 51 | ] 52 | 53 | [[package]] 54 | name = "quote" 55 | version = "1.0.28" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 58 | dependencies = [ 59 | "proc-macro2", 60 | ] 61 | 62 | [[package]] 63 | name = "rlsf" 64 | version = "0.2.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" 67 | dependencies = [ 68 | "cfg-if", 69 | "const-default", 70 | "libc", 71 | "svgbobdoc", 72 | ] 73 | 74 | [[package]] 75 | name = "svgbobdoc" 76 | version = "0.3.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" 79 | dependencies = [ 80 | "base64", 81 | "proc-macro2", 82 | "quote", 83 | "syn", 84 | "unicode-width", 85 | ] 86 | 87 | [[package]] 88 | name = "syn" 89 | version = "1.0.109" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 92 | dependencies = [ 93 | "proc-macro2", 94 | "quote", 95 | "unicode-ident", 96 | ] 97 | 98 | [[package]] 99 | name = "unicode-ident" 100 | version = "1.0.9" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 103 | 104 | [[package]] 105 | name = "unicode-width" 106 | version = "0.1.10" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 109 | 110 | [[package]] 111 | name = "whisk" 112 | version = "0.12.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "c06e9e73f3b58900ffc60cddfadbaba9c1cdb99b222870bbcea974a1addb7a2f" 115 | dependencies = [ 116 | "pasts", 117 | ] 118 | -------------------------------------------------------------------------------- /examples/no-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-std" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rlsf = "0.2" 8 | 9 | [dependencies.pasts] 10 | version = "0.14" 11 | default-features = false 12 | 13 | [dependencies.whisk] 14 | version = "0.12" 15 | features = ["event_iterator"] 16 | -------------------------------------------------------------------------------- /examples/no-std/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Use whisk on no-std, specifically targeting x86_64-unknown-linux-gnu (may 2 | //! work on others). Requires nightly for `eh_personality` lang item (tested 3 | //! with `rustc 1.72.0-nightly (871b59520 2023-05-31)`). 4 | //! 5 | //! ```shell 6 | //! cargo +nightly run 7 | //! ``` 8 | 9 | #![no_std] 10 | #![no_main] 11 | #![feature(lang_items)] 12 | 13 | extern crate alloc; 14 | 15 | #[global_allocator] 16 | static _GLOBAL_ALLOCATOR: rlsf::SmallGlobalTlsf = rlsf::SmallGlobalTlsf::new(); 17 | 18 | #[lang = "eh_personality"] 19 | extern "C" fn _eh_personality() {} 20 | 21 | #[no_mangle] 22 | extern "C" fn _Unwind_Resume() {} 23 | 24 | //// End no-std specific boilerplate //// 25 | 26 | use alloc::string::ToString; 27 | 28 | use pasts::{prelude::*, Executor}; 29 | use whisk::Channel; 30 | 31 | fn println(string: impl ToString) { 32 | #[link(name = "c")] 33 | extern "C" { 34 | fn puts(s: *const core::ffi::c_char) -> core::ffi::c_int; 35 | } 36 | 37 | let message = alloc::ffi::CString::new(string.to_string()).unwrap(); 38 | 39 | unsafe { puts(message.as_ptr()) }; 40 | } 41 | 42 | #[panic_handler] 43 | fn yeet(panic: &::core::panic::PanicInfo<'_>) -> ! { 44 | println(panic); 45 | 46 | loop {} 47 | } 48 | 49 | #[no_mangle] 50 | pub extern "C" fn main() -> ! { 51 | // Not an efficient executor for no-std, but it works 52 | let executor = Executor::default(); 53 | 54 | executor.clone().block_on(async move { 55 | println("Hello from a future"); 56 | 57 | let channel = Channel::new(); 58 | let mut channel_clone = channel.clone(); 59 | 60 | executor.spawn_boxed(async move { 61 | channel_clone.next().await; 62 | println("Received message"); 63 | }); 64 | 65 | executor.spawn_boxed(async move { 66 | println("Sending message"); 67 | channel.send(()).await; 68 | }); 69 | 70 | println("End of primary async block"); 71 | }); 72 | 73 | println("Exit pasts executor"); 74 | 75 | loop {} 76 | } 77 | -------------------------------------------------------------------------------- /examples/reuse.rs: -------------------------------------------------------------------------------- 1 | use whisk::Channel; 2 | 3 | enum Cmd { 4 | /// Tell messenger to add 5 | Add(u32, u32, Channel), 6 | } 7 | 8 | async fn worker(channel: Channel>) { 9 | while let Some(command) = channel.recv().await { 10 | println!("Worker receiving command"); 11 | match command { 12 | Cmd::Add(a, b, s) => s.send(a + b).await, 13 | } 14 | } 15 | 16 | println!("Worker stopping…"); 17 | } 18 | 19 | // Call into executor of your choice 20 | #[async_main::async_main] 21 | async fn main(_spawner: impl async_main::Spawn) { 22 | // Create worker on new thread 23 | println!("Spawning worker…"); 24 | let channel = Channel::new(); 25 | let worker_task = worker(channel.clone()); 26 | let worker_thread = 27 | std::thread::spawn(|| pasts::Executor::default().block_on(worker_task)); 28 | 29 | // Do an addition 30 | let oneshot = Channel::new(); 31 | for _ in 0..32 { 32 | println!("Sending command…"); 33 | channel.send(Some(Cmd::Add(43, 400, oneshot.clone()))).await; 34 | println!("Receiving response…"); 35 | let response = oneshot.recv().await; 36 | assert_eq!(response, 443); 37 | } 38 | 39 | // Tell worker to stop 40 | println!("Stopping worker…"); 41 | channel.send(None).await; 42 | println!("Waiting for worker to stop…"); 43 | 44 | worker_thread.join().unwrap(); 45 | println!("Worker thread joined"); 46 | } 47 | -------------------------------------------------------------------------------- /examples/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | num::NonZeroUsize, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures_core_3::Stream; 8 | use tokio::task; 9 | use tokio_stream::StreamExt; 10 | use whisk::Channel; 11 | 12 | struct FilterZeroStream(T); 13 | 14 | impl Stream for FilterZeroStream 15 | where 16 | T: Stream + Unpin, 17 | { 18 | type Item = usize; 19 | 20 | fn poll_next( 21 | mut self: Pin<&mut Self>, 22 | cx: &mut Context<'_>, 23 | ) -> Poll> { 24 | while let Poll::Ready(output) = Pin::new(&mut self.0).poll_next(cx) { 25 | let Some(output) = output else { 26 | return Poll::Ready(None); 27 | }; 28 | 29 | let Some(output) = NonZeroUsize::new(output) else { 30 | continue; 31 | }; 32 | 33 | return Poll::Ready(Some(output.into())); 34 | } 35 | 36 | Poll::Pending 37 | } 38 | } 39 | 40 | /// Expects a channel that sends one non-zero value before closing. 41 | async fn worker(channel: Channel>) -> usize { 42 | let mut filter = FilterZeroStream(channel); 43 | let result = filter.next().await.unwrap(); 44 | 45 | assert!(filter.next().await.is_none()); 46 | 47 | result 48 | } 49 | 50 | #[tokio::main] 51 | async fn main() { 52 | let channel = Channel::new(); 53 | let channel_clone = channel.clone(); 54 | let join_handle = task::spawn(async move { worker(channel_clone).await }); 55 | 56 | channel.send(Some(0)).await; 57 | channel.send(Some(12)).await; 58 | channel.send(None).await; 59 | 60 | let output = join_handle.await.unwrap(); 61 | 62 | assert_eq!(output, 12); 63 | } 64 | -------------------------------------------------------------------------------- /examples/weak.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Weak}; 2 | 3 | use whisk::{Channel, Queue}; 4 | 5 | // Call into executor of your choice 6 | fn main() { 7 | pasts::Executor::default().block_on(async { 8 | let chan: Channel = Channel::new(); 9 | let _weak_chan: Weak = Arc::downgrade(&Arc::from(chan)); 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/channel.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::{ 3 | future::Future, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::{wake_list::WakeHandle, Queue}; 9 | 10 | /// An MPMC channel with both send and receive capabilities 11 | /// 12 | /// Enable the **`futures_core_3`** feature for `Channel` to implement 13 | /// [`Stream`](futures_core_3::Stream) (generic `T` must be `Option`). 14 | /// 15 | /// Enable the **`event_iterator`** feature for `Channel` to implement 16 | /// [`EventIterator`](event_iterator::EventIterator). 17 | pub struct Channel(Arc>, WakeHandle); 18 | 19 | impl Drop for Channel { 20 | fn drop(&mut self) { 21 | // Drop to avoid use after free 22 | self.1 = WakeHandle::new(); 23 | } 24 | } 25 | 26 | impl Channel { 27 | /// Create a new channel. 28 | #[inline(always)] 29 | pub fn new() -> Self { 30 | Self(Arc::new(Queue::new()), WakeHandle::new()) 31 | } 32 | } 33 | 34 | impl Channel { 35 | /// Create a new channel with associated data. 36 | #[inline(always)] 37 | pub fn with(user_data: U) -> Self { 38 | Self::from(Arc::new(Queue::with(user_data))) 39 | } 40 | } 41 | 42 | impl Channel { 43 | /// Send a message on this channel. 44 | #[inline(always)] 45 | pub async fn send(&self, message: T) { 46 | self.0.send(message).await 47 | } 48 | 49 | /// Receive a message from this channel. 50 | #[inline(always)] 51 | pub async fn recv(&self) -> T { 52 | self.0.recv().await 53 | } 54 | } 55 | 56 | impl Clone for Channel { 57 | fn clone(&self) -> Self { 58 | Self(Arc::clone(&self.0), WakeHandle::new()) 59 | } 60 | } 61 | 62 | impl core::fmt::Debug for Channel { 63 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 64 | f.debug_struct("Channel").finish_non_exhaustive() 65 | } 66 | } 67 | 68 | impl Default for Channel { 69 | fn default() -> Self { 70 | Self::with(U::default()) 71 | } 72 | } 73 | 74 | impl core::ops::Deref for Channel { 75 | type Target = U; 76 | 77 | fn deref(&self) -> &Self::Target { 78 | &self.0.user 79 | } 80 | } 81 | 82 | impl Future for Channel { 83 | type Output = T; 84 | 85 | #[inline(always)] 86 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 87 | let this = self.get_mut(); 88 | this.0.data.take(cx, &mut this.1) 89 | } 90 | } 91 | 92 | #[cfg(feature = "event_iterator")] 93 | impl event_iterator::EventIterator for Channel { 94 | type Event<'me> = T where Self: 'me; 95 | 96 | #[inline(always)] 97 | fn poll_next( 98 | self: Pin<&mut Self>, 99 | cx: &mut Context<'_>, 100 | ) -> Poll> { 101 | let this = self.get_mut(); 102 | 103 | this.0.data.take(cx, &mut this.1).map(Some) 104 | } 105 | } 106 | 107 | #[cfg(feature = "futures_core_3")] 108 | impl futures_core_3::Stream for Channel, U> { 109 | type Item = T; 110 | 111 | #[inline(always)] 112 | fn poll_next( 113 | self: Pin<&mut Self>, 114 | cx: &mut Context<'_>, 115 | ) -> Poll> { 116 | let this = self.get_mut(); 117 | this.0.data.take(cx, &mut this.1) 118 | } 119 | } 120 | 121 | impl From>> for Channel { 122 | fn from(inner: Arc>) -> Self { 123 | Self(inner, WakeHandle::new()) 124 | } 125 | } 126 | 127 | impl From> for Arc> { 128 | fn from(channel: Channel) -> Self { 129 | channel.0.clone() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! #### Simple and fast lockless async channels 2 | //! Lightweight async channel that can be used to implement futures, streams, 3 | //! notifiers, and actors. 4 | //! 5 | //! Whisk defines a simple [`Channel`] type rather than splitting into sender / 6 | //! receiver pairs. A [`Channel`] can both send and receive. 7 | //! 8 | //! # Optional Features 9 | //! - **futures_core_3**: Implement [`Stream`](futures_core_3::Stream) for 10 | //! [`Channel`] (generic `T` must be `Option`) 11 | //! - **event_iterator**: Implement 12 | //! [`EventIterator`](event_iterator::EventIterator) for [`Channel`] 13 | //! 14 | //! # Getting Started 15 | //! 16 | //! ```rust 17 | //! use whisk::Channel; 18 | //! 19 | //! enum Cmd { 20 | //! /// Tell messenger to add 21 | //! Add(u32, u32, Channel), 22 | //! } 23 | //! 24 | //! async fn worker_main(commands: Channel>) { 25 | //! while let Some(command) = commands.recv().await { 26 | //! println!("Worker receiving command"); 27 | //! match command { 28 | //! Cmd::Add(a, b, s) => s.send(a + b).await, 29 | //! } 30 | //! } 31 | //! 32 | //! println!("Worker stopping…"); 33 | //! } 34 | //! 35 | //! async fn tasker_main() { 36 | //! // Create worker on new thread 37 | //! println!("Spawning worker…"); 38 | //! let channel = Channel::new(); 39 | //! let worker_task = worker_main(channel.clone()); 40 | //! let worker_thread = 41 | //! std::thread::spawn(|| pasts::Executor::default().block_on(worker_task)); 42 | //! 43 | //! // Do an addition 44 | //! println!("Sending command…"); 45 | //! let oneshot = Channel::new(); 46 | //! channel.send(Some(Cmd::Add(43, 400, oneshot.clone()))).await; 47 | //! println!("Receiving response…"); 48 | //! let response = oneshot.recv().await; 49 | //! assert_eq!(response, 443); 50 | //! 51 | //! // Tell worker to stop 52 | //! println!("Stopping worker…"); 53 | //! channel.send(None).await; 54 | //! println!("Waiting for worker to stop…"); 55 | //! 56 | //! worker_thread.join().unwrap(); 57 | //! println!("Worker thread joined"); 58 | //! } 59 | //! 60 | //! # #[ntest::timeout(1000)] 61 | //! fn main() { 62 | //! // Call into executor of your choice 63 | //! pasts::Executor::default().block_on(tasker_main()); 64 | //! } 65 | //! ``` 66 | 67 | #![no_std] 68 | #![doc( 69 | html_logo_url = "https://ardaku.github.io/mm/logo.svg", 70 | html_favicon_url = "https://ardaku.github.io/mm/icon.svg", 71 | html_root_url = "https://docs.rs/whisk" 72 | )] 73 | #![warn( 74 | anonymous_parameters, 75 | missing_copy_implementations, 76 | missing_debug_implementations, 77 | missing_docs, 78 | nonstandard_style, 79 | rust_2018_idioms, 80 | single_use_lifetimes, 81 | trivial_casts, 82 | trivial_numeric_casts, 83 | unreachable_pub, 84 | unused_extern_crates, 85 | unused_qualifications, 86 | variant_size_differences 87 | )] 88 | #![deny(unsafe_code)] 89 | 90 | extern crate alloc; 91 | 92 | mod channel; 93 | #[allow(unsafe_code)] 94 | mod mutex; 95 | mod queue; 96 | #[allow(unsafe_code)] 97 | mod wake_list; 98 | 99 | pub use self::{channel::Channel, queue::Queue}; 100 | -------------------------------------------------------------------------------- /src/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::{Cell, UnsafeCell}, 3 | sync::atomic::{ 4 | AtomicBool, 5 | Ordering::{Acquire, Release}, 6 | }, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use crate::wake_list::{WakeHandle, WakeList}; 11 | 12 | /// Mutex 13 | pub(crate) struct Mutex { 14 | /// True if mutex is currently being accessed 15 | lock: AtomicBool, 16 | /// Data in transit 17 | data: UnsafeCell>, 18 | /// List of waiting senders 19 | send: WakeList, 20 | /// List of waiting receivers 21 | recv: WakeList, 22 | } 23 | 24 | unsafe impl Send for Mutex {} 25 | unsafe impl Sync for Mutex {} 26 | 27 | impl Mutex { 28 | /// Create a new mutex 29 | pub(crate) const fn new() -> Self { 30 | let lock = AtomicBool::new(false); 31 | let data = UnsafeCell::new(None); 32 | let send = WakeList::new(); 33 | let recv = WakeList::new(); 34 | 35 | Self { 36 | lock, 37 | data, 38 | send, 39 | recv, 40 | } 41 | } 42 | 43 | /// Try to store data in the mutex 44 | pub(crate) fn store( 45 | &self, 46 | data: &Cell>, 47 | cx: &mut Context<'_>, 48 | wh: &mut WakeHandle, 49 | ) -> Poll<()> { 50 | // Try to acquire lock 51 | if self.lock.swap(true, Acquire) { 52 | // Data is contended, register sender to wake list 53 | wh.register(&self.send, cx.waker().clone()); 54 | 55 | // Try again just in case registration is unnecessary 56 | if self.lock.swap(true, Acquire) { 57 | // Will be awoken 58 | return Poll::Pending; 59 | } else { 60 | // Locked, and registered 61 | // If can't send until receive 62 | if unsafe { (*self.data.get()).is_some() } { 63 | // Release lock 64 | self.lock.store(false, Release); 65 | // Wake a receiver 66 | self.recv.wake_one(); 67 | return Poll::Pending; 68 | } 69 | 70 | // Registration was unnecessary, unregister 71 | *wh = WakeHandle::new(); 72 | } 73 | } else { 74 | // Locked, but not registered 75 | // If can't send until receive, register and return 76 | if unsafe { (*self.data.get()).is_some() } { 77 | // Register 78 | wh.register(&self.send, cx.waker().clone()); 79 | // Release lock 80 | self.lock.store(false, Release); 81 | // Wake a receiver 82 | self.recv.wake_one(); 83 | return Poll::Pending; 84 | } 85 | } 86 | 87 | // Write to inner data 88 | unsafe { (*self.data.get()) = data.take() }; 89 | 90 | // Release lock 91 | self.lock.store(false, Release); 92 | 93 | // Wake a receiver 94 | self.recv.wake_one(); 95 | 96 | Poll::Ready(()) 97 | } 98 | 99 | /// Try to take data from the mutex 100 | pub(crate) fn take( 101 | &self, 102 | cx: &mut Context<'_>, 103 | wh: &mut WakeHandle, 104 | ) -> Poll { 105 | // Try to acquire lock 106 | if self.lock.swap(true, Acquire) { 107 | // Data is contended, register sender to wake list 108 | wh.register(&self.recv, cx.waker().clone()); 109 | 110 | // Try again just in case registration is unnecessary 111 | if self.lock.swap(true, Acquire) { 112 | // Will be awoken 113 | return Poll::Pending; 114 | } else { 115 | // Locked, and registered 116 | // If can't receive until send 117 | if unsafe { (*self.data.get()).is_none() } { 118 | // Release lock 119 | self.lock.store(false, Release); 120 | // Wake a sender 121 | self.send.wake_one(); 122 | return Poll::Pending; 123 | } 124 | 125 | // Registration was unnecessary, unregister 126 | *wh = WakeHandle::new(); 127 | } 128 | } else { 129 | // If can't receive until send, register and return 130 | if unsafe { (*self.data.get()).is_none() } { 131 | // Register 132 | wh.register(&self.recv, cx.waker().clone()); 133 | // Release lock 134 | self.lock.store(false, Release); 135 | // Wake a sender 136 | self.send.wake_one(); 137 | return Poll::Pending; 138 | } 139 | } 140 | 141 | // Take from inner data 142 | let ret = if let Some(data) = unsafe { (*self.data.get()).take() } { 143 | Poll::Ready(data) 144 | } else { 145 | Poll::Pending 146 | }; 147 | 148 | // Release lock 149 | self.lock.store(false, Release); 150 | 151 | // Wake a sender 152 | self.send.wake_one(); 153 | 154 | // Return `Ready(_)` 155 | ret 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/queue.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::Cell, 3 | future::{self, Future}, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::{mutex::Mutex, wake_list::WakeHandle}; 9 | 10 | /// A `Queue` can send messages to itself, and can be shared between threads 11 | /// and tasks. 12 | /// 13 | /// Implemented as a multi-producer/multi-consumer queue of size 1. 14 | pub struct Queue { 15 | /// Data in transit 16 | pub(crate) data: Mutex, 17 | /// User data 18 | pub(crate) user: U, 19 | } 20 | 21 | impl core::fmt::Debug for Queue { 22 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 23 | f.debug_struct("Queue").finish_non_exhaustive() 24 | } 25 | } 26 | 27 | impl core::ops::Deref for Queue { 28 | type Target = U; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | &self.user 32 | } 33 | } 34 | 35 | impl Default for Queue { 36 | fn default() -> Self { 37 | Self::with(U::default()) 38 | } 39 | } 40 | 41 | impl Queue { 42 | /// Create a new queue. 43 | #[inline] 44 | pub const fn new() -> Self { 45 | Self::with(()) 46 | } 47 | } 48 | 49 | impl Queue { 50 | /// Create a new queue with associated data. 51 | #[inline] 52 | pub const fn with(user_data: U) -> Self { 53 | Self { 54 | data: Mutex::new(), 55 | user: user_data, 56 | } 57 | } 58 | } 59 | 60 | impl Queue { 61 | /// Send a message on this queue. 62 | #[inline(always)] 63 | pub async fn send(&self, message: T) { 64 | Message(self, Cell::new(Some(message)), WakeHandle::new()).await 65 | } 66 | 67 | /// Receive a message from this queue. 68 | #[inline(always)] 69 | pub async fn recv(&self) -> T { 70 | let mut wh = WakeHandle::new(); 71 | future::poll_fn(|cx| self.data.take(cx, &mut wh)).await 72 | } 73 | } 74 | 75 | /// A message in the process of being sent over a [`Queue`]. 76 | struct Message<'a, T, U: ?Sized>(&'a Queue, Cell>, WakeHandle); 77 | 78 | #[allow(unsafe_code)] 79 | impl Message<'_, T, U> { 80 | #[inline(always)] 81 | fn pin_get_wh(self: Pin<&mut Self>) -> Pin<&mut WakeHandle> { 82 | // This is okay because `1` is pinned when `self` is. 83 | unsafe { self.map_unchecked_mut(|s| &mut s.2) } 84 | } 85 | } 86 | 87 | impl Future for Message<'_, T, U> { 88 | type Output = (); 89 | 90 | #[inline] 91 | fn poll( 92 | mut self: Pin<&mut Self>, 93 | cx: &mut Context<'_>, 94 | ) -> Poll { 95 | let mut wh = WakeHandle::new(); 96 | core::mem::swap(&mut wh, self.as_mut().pin_get_wh().get_mut()); 97 | 98 | let ret = self.0.data.store(&self.1, cx, &mut wh); 99 | 100 | core::mem::swap(&mut wh, self.as_mut().pin_get_wh().get_mut()); 101 | 102 | ret 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/wake_list.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use core::{ 3 | cell::UnsafeCell, 4 | mem::MaybeUninit, 5 | ptr, 6 | sync::atomic::{ 7 | AtomicPtr, AtomicUsize, 8 | Ordering::{Relaxed, SeqCst}, 9 | }, 10 | task::Waker, 11 | }; 12 | 13 | /// Status of wake node 14 | #[repr(usize)] 15 | enum WakeState { 16 | /// Waiting to be re-allocated 17 | Garbage = 0, 18 | /// Waiting for registration 19 | Empty = 1, 20 | /// Ready to be awoken (contains waker) 21 | Ready = 2, 22 | /// 1 task can register at a time (in the process of getting a waker) 23 | Registering = 3, 24 | /// Multiple tasks could be waking (in the process of waking, losing waker) 25 | Waking = 4, 26 | /// Registration is being canceled by waking 27 | Canceling = 5, 28 | /// Waiting to become garbage 29 | Freeing = 6, 30 | } 31 | 32 | struct WakeNode { 33 | /// Atomic `WakeState` for waker 34 | state: AtomicUsize, 35 | /// Waker and a fallback waker 36 | waker: UnsafeCell>, 37 | } 38 | 39 | impl WakeNode { 40 | /// Try to allocate wake node 41 | fn allocate(&self) -> Result<*const WakeNode, ()> { 42 | self.state 43 | .compare_exchange( 44 | WakeState::Garbage as usize, 45 | WakeState::Empty as usize, 46 | SeqCst, 47 | SeqCst, 48 | ) 49 | .map_err(|_| ()) 50 | .map(|_| -> *const WakeNode { self }) 51 | } 52 | 53 | /// Register a new waker 54 | /// 55 | /// Slots can be Empty, Ready or Waking (If Waking, wakes immediately) 56 | fn register(&self, waker: Waker) { 57 | // Attempt to clear first slot and begin registering 58 | let r = self 59 | .state 60 | .fetch_update(SeqCst, SeqCst, |state| match state { 61 | // Switch to registering state 62 | x if x == WakeState::Empty as usize => { 63 | Some(WakeState::Registering as usize) 64 | } 65 | // Switch to registering state, dropping previous waker 66 | x if x == WakeState::Ready as usize => { 67 | Some(WakeState::Registering as usize) 68 | } 69 | // Contention with waking, re-wake immediately 70 | x if x == WakeState::Waking as usize => None, 71 | _ => unreachable!(), 72 | }); 73 | 74 | // Set waker and mark ready 75 | match r { 76 | Ok(prev) => { 77 | // Drop before overwriting 78 | if prev == WakeState::Ready as usize { 79 | unsafe { (*self.waker.get()).assume_init_drop() } 80 | } 81 | 82 | // Use first waker slot and set to ready for waking 83 | unsafe { *self.waker.get() = MaybeUninit::new(waker) }; 84 | 85 | // Finish, checking if canceled 86 | let r = 87 | self.state.fetch_update( 88 | SeqCst, 89 | SeqCst, 90 | |state| match state { 91 | // Switch to registering state 92 | x if x == WakeState::Registering as usize => { 93 | Some(WakeState::Ready as usize) 94 | } 95 | // Switch to registering state 96 | x if x == WakeState::Canceling as usize => None, 97 | _ => unreachable!(), 98 | }, 99 | ); 100 | if r.is_err() { 101 | unsafe { (*self.waker.get()).assume_init_read().wake() } 102 | self.state.store(WakeState::Empty as usize, SeqCst); 103 | } 104 | } 105 | Err(_) => waker.wake(), 106 | } 107 | } 108 | 109 | /// Try to wake this node 110 | /// 111 | /// If already waking, won't wake again 112 | fn wake(&self) -> Result<(), ()> { 113 | let r = self 114 | .state 115 | .fetch_update(SeqCst, SeqCst, |state| match state { 116 | // Ready to be awoken 117 | x if x == WakeState::Ready as usize => { 118 | Some(WakeState::Waking as usize) 119 | } 120 | // Currently registering, wake now 121 | x if x == WakeState::Registering as usize => { 122 | Some(WakeState::Canceling as usize) 123 | } 124 | // Not wakeable 125 | _ => None, 126 | }) 127 | .map_err(|_| ())?; 128 | if r == WakeState::Registering as usize { 129 | return Ok(()); 130 | } 131 | 132 | // Take and wake the waker 133 | unsafe { (*self.waker.get()).assume_init_read().wake() }; 134 | 135 | // Update state 136 | self.state 137 | .fetch_update(SeqCst, SeqCst, |state| match state { 138 | x if x == WakeState::Waking as usize => { 139 | Some(WakeState::Empty as usize) 140 | } 141 | x if x == WakeState::Freeing as usize => { 142 | Some(WakeState::Garbage as usize) 143 | } 144 | _ => unreachable!(), 145 | }) 146 | .map(|_| ()) 147 | .map_err(|_| ()) 148 | } 149 | 150 | /// Free this wake node (must be done on registration task) 151 | fn free(&self) { 152 | match self.state.swap(WakeState::Freeing as usize, SeqCst) { 153 | x if x == WakeState::Empty as usize => {} 154 | x if x == WakeState::Ready as usize => { 155 | unsafe { (*self.waker.get()).assume_init_drop() }; 156 | } 157 | x if x == WakeState::Waking as usize => return, 158 | _ => unreachable!(), 159 | } 160 | 161 | self.state.store(WakeState::Garbage as usize, SeqCst); 162 | } 163 | } 164 | 165 | /// Handle to an optional waker node 166 | pub(crate) struct WakeHandle(*const WakeNode); 167 | 168 | unsafe impl Send for WakeHandle {} 169 | unsafe impl Sync for WakeHandle {} 170 | 171 | impl WakeHandle { 172 | /// Create a new mock handle 173 | pub(crate) fn new() -> Self { 174 | Self(ptr::null()) 175 | } 176 | 177 | /// Register a waker 178 | pub(crate) fn register(&mut self, wl: &WakeList, waker: Waker) { 179 | // Allocate a waker if needed 180 | if self.0.is_null() { 181 | self.0 = wl.allocate(); 182 | } 183 | 184 | // Register the waker 185 | unsafe { (*self.0).register(waker) } 186 | } 187 | } 188 | 189 | impl Drop for WakeHandle { 190 | fn drop(&mut self) { 191 | // Unregister the waker if set 192 | if !self.0.is_null() { 193 | unsafe { (*self.0).free() } 194 | } 195 | } 196 | } 197 | 198 | struct Node { 199 | next: AtomicPtr>, 200 | data: T, 201 | } 202 | 203 | /// A `WakeList` stores an append-only atomic linked list of wakers 204 | pub(crate) struct WakeList { 205 | // Root node of list of wakers 206 | root: AtomicPtr>, 207 | // Next one to try waking ("fairness" mechanism) 208 | next: AtomicPtr>, 209 | } 210 | 211 | impl Drop for WakeList { 212 | fn drop(&mut self) { 213 | let mut tmp = self.root.load(Relaxed); 214 | 215 | while !tmp.is_null() { 216 | let node = unsafe { Box::from_raw(tmp) }; 217 | tmp = node.next.load(Relaxed); 218 | } 219 | } 220 | } 221 | 222 | impl WakeList { 223 | /// Create a new empty wake list 224 | pub(crate) const fn new() -> Self { 225 | Self { 226 | root: AtomicPtr::new(ptr::null_mut()), 227 | next: AtomicPtr::new(ptr::null_mut()), 228 | } 229 | } 230 | 231 | /// Attempt to wake one waker. 232 | /// 233 | /// If no wakers are registered, doesn't do anything. 234 | pub(crate) fn wake_one(&self) { 235 | // Start from next pointer into list 236 | let next = self.next.load(SeqCst); 237 | let mut tmp = next; 238 | while !tmp.is_null() { 239 | let next = unsafe { (*tmp).next.load(Relaxed) }; 240 | if unsafe { (*tmp).data.wake().is_ok() } { 241 | self.next.store(next, SeqCst); 242 | return; 243 | } 244 | tmp = next; 245 | } 246 | 247 | // Start back at beginning of list 248 | tmp = self.root.load(SeqCst); 249 | while tmp != next { 250 | let next = unsafe { (*tmp).next.load(Relaxed) }; 251 | if unsafe { (*tmp).data.wake().is_ok() } { 252 | self.next.store(next, SeqCst); 253 | return; 254 | } 255 | tmp = next; 256 | } 257 | } 258 | 259 | /// Allocate a new `WakeNode` 260 | fn allocate(&self) -> *const WakeNode { 261 | // Go through list to see if unused existing allocation to use 262 | let mut tmp = self.root.load(SeqCst); 263 | while !tmp.is_null() { 264 | if let Ok(wn) = unsafe { (*tmp).data.allocate() } { 265 | return wn; 266 | } 267 | tmp = unsafe { (*tmp).next.load(Relaxed) }; 268 | } 269 | 270 | // Push to front 271 | let data = WakeNode { 272 | state: AtomicUsize::new(WakeState::Empty as usize), 273 | waker: UnsafeCell::new(MaybeUninit::uninit()), 274 | }; 275 | let mut root = self.root.load(SeqCst); 276 | let next = AtomicPtr::new(self.root.load(SeqCst)); 277 | let node = Box::into_raw(Box::new(Node { next, data })); 278 | while let Err(r) = 279 | self.root.compare_exchange(root, node, SeqCst, Relaxed) 280 | { 281 | root = r; 282 | unsafe { (*node).next = AtomicPtr::new(root) }; 283 | } 284 | unsafe { &(*node).data } 285 | } 286 | } 287 | --------------------------------------------------------------------------------