├── .github
└── workflows
│ ├── checks.yml
│ ├── linux.yml
│ └── osx.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── benches
├── async.rs
├── common.rs
└── sync.rs
├── src
├── backoff.rs
├── error.rs
├── future.rs
├── internal.rs
├── lib.rs
├── mutex.rs
├── pointer.rs
└── signal.rs
└── tests
├── async_test.rs
├── sync_test.rs
└── utils
└── mod.rs
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: Checks
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule: [cron: "40 1 * * *"]
7 |
8 | jobs:
9 | clippy:
10 | name: Clippy
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions-rs/toolchain@v1
15 | with:
16 | toolchain: nightly
17 | components: clippy
18 | override: true
19 | - uses: actions-rs/clippy-check@v1
20 | with:
21 | token: ${{ secrets.GITHUB_TOKEN }}
22 | args: --all-features
23 |
24 | fmt:
25 | name: Rustfmt
26 | runs-on: ubuntu-latest
27 | strategy:
28 | matrix:
29 | rust:
30 | - stable
31 | steps:
32 | - uses: actions/checkout@v3
33 | - uses: actions-rs/toolchain@v1
34 | with:
35 | profile: minimal
36 | toolchain: ${{ matrix.rust }}
37 | override: true
38 | - uses: actions-rs/cargo@v1
39 | with:
40 | command: fmt
41 | args: --all -- --check
42 |
--------------------------------------------------------------------------------
/.github/workflows/linux.yml:
--------------------------------------------------------------------------------
1 | name: CI (Linux)
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule: [cron: "40 1 * * *"]
7 |
8 | jobs:
9 | build_and_test:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | version:
14 | # - 1.57.0 # MSRV
15 | - stable
16 | - nightly
17 |
18 | name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@master
23 |
24 | - name: Install ${{ matrix.version }}
25 | uses: actions-rs/toolchain@v1
26 | with:
27 | toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
28 | profile: minimal
29 | components: rustfmt
30 | override: true
31 |
32 | - name: Generate Cargo.lock
33 | uses: actions-rs/cargo@v1
34 | with:
35 | command: generate-lockfile
36 |
37 | - name: Cache cargo registry
38 | uses: actions/cache@v3
39 | with:
40 | path: ~/.cargo/registry
41 | key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
42 |
43 | - name: Cache cargo index
44 | uses: actions/cache@v3
45 | with:
46 | path: ~/.cargo/git
47 | key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
48 |
49 | - name: Run tests
50 | uses: actions-rs/cargo@v1
51 | timeout-minutes: 40
52 | with:
53 | command: test
54 | args: --all-features -- --nocapture
55 |
56 | - name: Install cargo-cache
57 | continue-on-error: true
58 | run: |
59 | cargo install cargo-cache --no-default-features --features ci-autoclean
60 |
61 | - name: Clear the cargo caches
62 | run: |
63 | cargo-cache
64 |
--------------------------------------------------------------------------------
/.github/workflows/osx.yml:
--------------------------------------------------------------------------------
1 | name: CI (OSX)
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule: [cron: "40 1 * * *"]
7 |
8 | jobs:
9 | build_and_test:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | version:
14 | - stable
15 | - nightly
16 |
17 | name: ${{ matrix.version }} - aarch64-apple-darwin
18 | runs-on: macOS-latest
19 |
20 | steps:
21 | - uses: actions/checkout@master
22 | - name: Install ${{ matrix.version }}
23 | uses: actions-rs/toolchain@v1
24 | with:
25 | toolchain: ${{ matrix.version }}-aarch64-apple-darwin
26 | profile: minimal
27 | components: rustfmt
28 | override: true
29 |
30 | - name: Generate Cargo.lock
31 | uses: actions-rs/cargo@v1
32 | with:
33 | command: generate-lockfile
34 |
35 | - name: Cache cargo registry
36 | uses: actions/cache@v3
37 | with:
38 | path: ~/.cargo/registry
39 | key: ${{ matrix.version }}-aarch64-apple-darwin-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
40 |
41 | - name: Cache cargo index
42 | uses: actions/cache@v3
43 | with:
44 | path: ~/.cargo/git
45 | key: ${{ matrix.version }}-aarch64-apple-darwin-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
46 |
47 | - name: Run tests
48 | uses: actions-rs/cargo@v1
49 | with:
50 | command: test
51 | args: --all-features
52 |
53 | - name: Clear the cargo caches
54 | run: |
55 | cargo install cargo-cache --no-default-features --features ci-autoclean
56 | cargo-cache
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /dev
3 | Cargo.lock
4 | rustfmt.toml
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kanal"
3 | version = "0.1.1"
4 | edition = "2021"
5 | authors = ["Khashayar Fereidani"]
6 | description = "The fast sync and async channel that Rust deserves"
7 | repository = "https://github.com/fereidani/kanal"
8 | documentation = "https://docs.rs/kanal"
9 | keywords = ["channel", "mpsc", "mpmc", "async"]
10 | categories = ["concurrency", "data-structures", "asynchronous"]
11 | license = "MIT"
12 | readme = "README.md"
13 |
14 | [dependencies]
15 | cacheguard = "0.1"
16 | futures-core = { version = "0.3", optional = true }
17 | lock_api = "0.4"
18 |
19 | [dev-dependencies]
20 | anyhow = "1.0"
21 | criterion = "0.4"
22 | crossbeam = "0.8"
23 | tokio = { version = "1", features = ["rt-multi-thread", "test-util", "macros"] }
24 | futures = "0.3"
25 |
26 | [features]
27 | async = ["futures-core"]
28 | std-mutex = []
29 | default = ["async"]
30 |
31 | [[bench]]
32 | name = "sync"
33 | harness = false
34 |
35 | [[bench]]
36 | name = "async"
37 | harness = false
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022-2023 Khashayar Fereidani and other Kanal contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kanal
2 |
3 | **The fast sync and async channel that Rust deserves!**
4 |
5 | [![Crates.io][crates-badge]][crates-url]
6 | [![Documentation][doc-badge]][doc-url]
7 | [![MIT licensed][mit-badge]][mit-url]
8 |
9 | [crates-badge]: https://img.shields.io/crates/v/kanal.svg?style=for-the-badge
10 | [crates-url]: https://crates.io/crates/kanal
11 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge
12 | [mit-url]: https://github.com/fereidani/kanal/blob/master/LICENSE
13 | [doc-badge]: https://img.shields.io/docsrs/kanal?style=for-the-badge
14 | [doc-url]: https://docs.rs/kanal
15 |
16 | ## What is Kanal
17 |
18 | The Kanal library is a Rust implementation of channels inspired by the CSP (Communicating Sequential Processes) model. It aims to help programmers create efficient concurrent programs by providing multi-producer and multi-consumer channels with advanced features for fast communication. The library focuses on unifying message passing between synchronous and asynchronous parts of Rust code, offering a combination of synchronous and asynchronous APIs while maintaining high performance.
19 |
20 | ## Why Kanal is faster?
21 |
22 | 1. Kanal employs a highly optimized composite technique for the transfer of objects. When the data size is less than or equal to the pointer size, it utilizes serialization, encoding the data as the pointer address. Conversely, when the data size exceeds the pointer size, the protocol employs a strategy similar to that utilized by the Golang programming language, utilizing direct memory access to copy objects from the sender's stack or write directly to the receiver's stack. This composite method not only eliminates unnecessary pointer access but also eliminates heap allocations for bounded(0) channels.
23 | 2. Kanal utilizes a specially tuned mutex for its channel locking mechanism, made possible by the predictable internal lock time of the channel. That said it's possible to use Rust standard mutex with the `std-mutex` feature and Kanal will perform better than competitors with that feature too.
24 | 3. Utilizing Rust high-performance compiler and powerful LLVM backend with highly optimized memory access and deeply thought algorithms.
25 |
26 | ## Usage
27 |
28 | To use Kanal in your Rust project, add the following line to your `Cargo.toml` file:
29 |
30 | ```toml
31 | [dependencies]
32 | kanal = "0.1"
33 | ```
34 |
35 | Sync channel example:
36 |
37 | ```rust,ignore
38 | // Initialize a bounded sync channel with a capacity for 8 messages
39 | let (sender, receiver) = kanal::bounded(8);
40 |
41 | let s = sender.clone();
42 | std::thread::spawn(move || {
43 | s.send("hello")?;
44 | anyhow::Ok(())
45 | });
46 |
47 | // Receive an example message in another thread
48 | let msg = receiver.recv()?;
49 | println!("I got msg: {}", msg);
50 |
51 |
52 | // Convert and use channel in async context to communicate between sync and async
53 | tokio::spawn(async move {
54 | // Borrow the channel as an async channel and use it in an async context ( or convert it to async using to_async() )
55 | sender.as_async().send("hello").await?;
56 | anyhow::Ok(())
57 | });
58 | ```
59 |
60 | Async channel example:
61 |
62 | ```rust,ignore
63 | // Initialize a bounded channel with a capacity for 8 messages
64 | let (sender, receiver) = kanal::bounded_async(8);
65 |
66 | sender.send("hello").await?;
67 | sender.send("hello").await?;
68 |
69 | // Clone receiver and convert it to a sync receiver
70 | let receiver_sync = receiver.clone().to_sync();
71 |
72 | tokio::spawn(async move {
73 | let msg = receiver.recv().await?;
74 | println!("I got msg: {}", msg);
75 | anyhow::Ok(())
76 | });
77 |
78 | // Spawn a thread and use receiver in sync context
79 | std::thread::spawn(move || {
80 | let msg = receiver_sync.recv()?;
81 | println!("I got msg in sync context: {}", msg);
82 | anyhow::Ok(())
83 | });
84 | ```
85 |
86 | ## Why use Kanal?
87 |
88 | - Kanal offers fast and efficient communication capabilities.
89 | - Kanal simplifies communication in and between synchronous and asynchronous contexts, thanks to its flexible API like `as_sync` and `as_async`.
90 | - Kanal provides a clean and intuitive API, making it easier to work with compared to other Rust libraries.
91 | - Similar to Golang, Kanal allows you to close channels using the `Close` function, enabling you to broadcast a close signal from any channel instance and close the channel for both senders and receivers.
92 | - Kanal includes high-performance MPMC (Multiple Producers Multiple Consumers) and SPSC (Single Producer Single Consumer) channels in a single package.
93 |
94 | ### Benchmark Results
95 |
96 | Results are based on how many messages can be passed in each scenario per second.
97 |
98 | #### Test types:
99 |
100 | 1. Seq is sequentially writing and reading to a channel in the same thread.
101 | 2. SPSC is one receiver, and one sender and passing messages between them.
102 | 3. MPSC is multiple sender threads with only one receiver.
103 | 4. MPMC is multiple senders and multiple receivers communicating through the same channel.
104 |
105 | #### Message types:
106 |
107 | 1. `usize` tests are transferring messages of size hardware pointer.
108 | 2. `big` tests are transferring messages of 8x the size of the hardware pointer.
109 |
110 | N/A means that the test subject is unable to perform the test due to its limitations, Some of the test subjects don't have implementation for size 0 channels, MPMC or unbounded channels.
111 |
112 | Machine: `AMD Ryzen 9 9950X 16-Core Processor`
113 | Rust: `rustc 1.85.1 (4eb161250 2025-03-15)`
114 | Go: `go version go1.24.1 linux/amd64`
115 | OS (`uname -a`): `Linux 6.11.0-19-generic #19~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Feb 17 11:51:52 UTC 2 x86_64`
116 | Date: Mar 19, 2025
117 |
118 | [Benchmark codes](https://github.com/fereidani/rust-channel-benchmarks)
119 |
120 | 
121 |
122 | #### Why does async outperform sync in some tests?
123 |
124 | In certain tests, asynchronous communication may exhibit superior performance compared to synchronous communication. This can be attributed to the context-switching performance of libraries such as tokio, which, similar to Golang, utilize context-switching within the same thread to switch to the next coroutine when a message is ready on a channel. This approach is more efficient than communicating between separate threads. This same principle applies to asynchronous network applications, which generally exhibit better performance compared to synchronous implementations. As the channel size increases, one may observe improved performance in synchronous benchmarks, as the sending threads are able to push data directly to the channel queue without requiring awaiting blocking/suspending signals from receiving threads.
125 |
--------------------------------------------------------------------------------
/benches/async.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | pub use common::*;
4 | use criterion::*;
5 | use std::{thread::available_parallelism, time::Duration};
6 |
7 | macro_rules! run_bench {
8 | ($b:expr, $tx:expr, $rx:expr, $writers:expr, $readers:expr) => {{
9 | let rt = tokio::runtime::Builder::new_multi_thread()
10 | .worker_threads(usize::from(available_parallelism().unwrap()))
11 | .enable_all()
12 | .build()
13 | .unwrap();
14 | let readers_dist = evenly_distribute(BENCH_MSG_COUNT, $readers);
15 | let writers_dist = evenly_distribute(BENCH_MSG_COUNT, $writers);
16 | $b.iter(|| {
17 | let mut handles = Vec::with_capacity($readers + $writers);
18 | for d in 0..$readers {
19 | let rx = $rx.clone();
20 | let iterations = readers_dist[d];
21 | handles.push(rt.spawn(async move {
22 | for _ in 0..iterations {
23 | check_value(black_box(rx.recv().await.unwrap()));
24 | }
25 | }));
26 | }
27 | for d in 0..$writers {
28 | let tx = $tx.clone();
29 | let iterations = writers_dist[d];
30 | handles.push(rt.spawn(async move {
31 | for i in 0..iterations {
32 | tx.send(i + 1).await.unwrap();
33 | }
34 | }));
35 | }
36 | for handle in handles {
37 | rt.block_on(handle).unwrap();
38 | }
39 | });
40 | }};
41 | }
42 |
43 | fn mpmc(c: &mut Criterion) {
44 | let mut g = c.benchmark_group("async::mpmc");
45 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
46 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
47 | g.bench_function("b0", |b| {
48 | let (tx, rx) = kanal::bounded_async::(0);
49 | let core_count = usize::from(available_parallelism().unwrap());
50 | run_bench!(b, tx, rx, core_count, core_count);
51 | });
52 | g.bench_function("b0_contended", |b| {
53 | let (tx, rx) = kanal::bounded_async::(0);
54 | let core_count = usize::from(available_parallelism().unwrap());
55 | run_bench!(b, tx, rx, core_count * 64, core_count * 64);
56 | });
57 | g.bench_function("b1", |b| {
58 | let (tx, rx) = kanal::bounded_async::(1);
59 | let core_count = usize::from(available_parallelism().unwrap());
60 | run_bench!(b, tx, rx, core_count, core_count);
61 | });
62 | g.bench_function("bn", |b| {
63 | let (tx, rx) = kanal::unbounded_async();
64 | let core_count = usize::from(available_parallelism().unwrap());
65 | run_bench!(b, tx, rx, core_count, core_count);
66 | });
67 | g.finish();
68 | }
69 |
70 | fn mpsc(c: &mut Criterion) {
71 | let mut g = c.benchmark_group("async::mpsc");
72 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
73 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
74 | g.bench_function("b0", |b| {
75 | let (tx, rx) = kanal::bounded_async::(0);
76 | let core_count = usize::from(available_parallelism().unwrap());
77 | run_bench!(b, tx, rx, core_count, 1);
78 | });
79 | g.bench_function("b0_contended", |b| {
80 | let (tx, rx) = kanal::bounded_async::(0);
81 | let core_count = usize::from(available_parallelism().unwrap());
82 | run_bench!(b, tx, rx, core_count * 64, 1);
83 | });
84 | g.bench_function("b1", |b| {
85 | let (tx, rx) = kanal::bounded_async::(1);
86 | let core_count = usize::from(available_parallelism().unwrap());
87 | run_bench!(b, tx, rx, core_count, 1);
88 | });
89 | g.bench_function("bn", |b| {
90 | let (tx, rx) = kanal::unbounded_async();
91 | let core_count = usize::from(available_parallelism().unwrap());
92 | run_bench!(b, tx, rx, core_count, 1);
93 | });
94 | g.finish();
95 | }
96 |
97 | fn spsc(c: &mut Criterion) {
98 | let mut g = c.benchmark_group("async::spsc");
99 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
100 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
101 | g.bench_function("b0", |b| {
102 | let (tx, rx) = kanal::bounded_async::(0);
103 | run_bench!(b, tx, rx, 1, 1);
104 | });
105 | g.bench_function("b1", |b| {
106 | let (tx, rx) = kanal::bounded_async::(1);
107 | run_bench!(b, tx, rx, 1, 1);
108 | });
109 | g.finish();
110 | }
111 | criterion_group!(async_bench, mpmc, mpsc, spsc);
112 | criterion_main!(async_bench);
113 |
--------------------------------------------------------------------------------
/benches/common.rs:
--------------------------------------------------------------------------------
1 | pub const BENCH_MSG_COUNT: usize = 1 << 20;
2 |
3 | pub fn check_value(value: usize) {
4 | if value == 0 {
5 | println!("Value should not be zero");
6 | }
7 | }
8 |
9 | pub fn evenly_distribute(total: usize, parts: usize) -> Vec {
10 | if parts == 0 {
11 | return Vec::new();
12 | }
13 |
14 | let base_value = total / parts;
15 | let remainder = total % parts;
16 |
17 | (0..parts)
18 | .map(|i| base_value + if i < remainder { 1 } else { 0 })
19 | .collect()
20 | }
21 |
--------------------------------------------------------------------------------
/benches/sync.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | pub use common::*;
4 | use criterion::*;
5 | use std::{thread::available_parallelism, time::Duration};
6 |
7 | macro_rules! run_bench {
8 | ($b:expr, $tx:expr, $rx:expr, $writers:expr, $readers:expr) => {
9 | use std::thread::spawn;
10 | let readers_dist = evenly_distribute(BENCH_MSG_COUNT, $readers);
11 | let writers_dist = evenly_distribute(BENCH_MSG_COUNT, $writers);
12 | $b.iter(|| {
13 | let mut handles = Vec::with_capacity($readers + $writers);
14 | for d in 0..$readers {
15 | let rx = $rx.clone();
16 | let iterations = readers_dist[d];
17 | handles.push(spawn(move || {
18 | for _ in 0..iterations {
19 | check_value(black_box(rx.recv().unwrap()));
20 | }
21 | }));
22 | }
23 | for d in 0..$writers {
24 | let tx = $tx.clone();
25 | let iterations = writers_dist[d];
26 | handles.push(spawn(move || {
27 | for i in 0..iterations {
28 | tx.send(i + 1).unwrap();
29 | }
30 | }));
31 | }
32 | for handle in handles {
33 | handle.join().unwrap();
34 | }
35 | })
36 | };
37 | }
38 |
39 | fn mpmc(c: &mut Criterion) {
40 | let mut g = c.benchmark_group("sync::mpmc");
41 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
42 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
43 | g.bench_function("b0", |b| {
44 | let (tx, rx) = kanal::bounded::(0);
45 | let core_count = usize::from(available_parallelism().unwrap());
46 | run_bench!(b, tx, rx, core_count, core_count);
47 | });
48 | g.bench_function("b0_contended", |b| {
49 | let (tx, rx) = kanal::bounded::(0);
50 | let core_count = usize::from(available_parallelism().unwrap());
51 | run_bench!(b, tx, rx, core_count * 64, core_count * 64);
52 | });
53 | g.bench_function("b1", |b| {
54 | let (tx, rx) = kanal::bounded::(1);
55 | let core_count = usize::from(available_parallelism().unwrap());
56 | run_bench!(b, tx, rx, core_count, core_count);
57 | });
58 | g.bench_function("bn", |b| {
59 | let (tx, rx) = kanal::unbounded();
60 | let core_count = usize::from(available_parallelism().unwrap());
61 | run_bench!(b, tx, rx, core_count, core_count);
62 | });
63 | g.finish();
64 | }
65 |
66 | fn mpsc(c: &mut Criterion) {
67 | let mut g = c.benchmark_group("sync::mpsc");
68 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
69 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
70 | g.bench_function("b0", |b| {
71 | let (tx, rx) = kanal::bounded::(0);
72 | let core_count = usize::from(available_parallelism().unwrap());
73 | run_bench!(b, tx, rx, core_count, 1);
74 | });
75 | g.bench_function("b0_contended", |b| {
76 | let (tx, rx) = kanal::bounded::(0);
77 | let core_count = usize::from(available_parallelism().unwrap());
78 | run_bench!(b, tx, rx, core_count * 64, 1);
79 | });
80 | g.bench_function("b1", |b| {
81 | let (tx, rx) = kanal::bounded::(1);
82 | let core_count = usize::from(available_parallelism().unwrap());
83 | run_bench!(b, tx, rx, core_count, 1);
84 | });
85 | g.bench_function("bn", |b| {
86 | let (tx, rx) = kanal::unbounded();
87 | let core_count = usize::from(available_parallelism().unwrap());
88 | run_bench!(b, tx, rx, core_count, 1);
89 | });
90 | g.finish();
91 | }
92 |
93 | fn spsc(c: &mut Criterion) {
94 | let mut g = c.benchmark_group("sync::spsc");
95 | g.throughput(Throughput::Elements(BENCH_MSG_COUNT as u64));
96 | g.sample_size(10).warm_up_time(Duration::from_secs(1));
97 | g.bench_function("b0", |b| {
98 | let (tx, rx) = kanal::bounded::(0);
99 | run_bench!(b, tx, rx, 1, 1);
100 | });
101 | g.bench_function("b1", |b| {
102 | let (tx, rx) = kanal::bounded::(1);
103 | run_bench!(b, tx, rx, 1, 1);
104 | });
105 | g.finish();
106 | }
107 | criterion_group!(sync_bench, mpmc, mpsc, spsc);
108 | criterion_main!(sync_bench);
109 |
--------------------------------------------------------------------------------
/src/backoff.rs:
--------------------------------------------------------------------------------
1 | /// This module provides various backoff strategies that can be used to reduce
2 | /// the amount of busy waiting and improve the efficiency of concurrent systems.
3 | ///
4 | /// The main idea behind separating backoff into an independent module is that
5 | /// it makes it easier to test and compare different backoff solutions.
6 | use core::{
7 | num::NonZeroUsize,
8 | sync::atomic::{AtomicU32, AtomicU8, AtomicUsize, Ordering},
9 | time::Duration,
10 | };
11 |
12 | use std::thread::available_parallelism;
13 |
14 | /// Puts the current thread to sleep for a specified duration.
15 | #[inline(always)]
16 | pub fn sleep(dur: Duration) {
17 | std::thread::sleep(dur)
18 | }
19 |
20 | /// Emits a CPU instruction that signals the processor that it is in a spin
21 | /// loop.
22 | #[allow(dead_code)]
23 | #[inline(always)]
24 | pub fn spin_hint() {
25 | std::hint::spin_loop()
26 | }
27 |
28 | /// Yields the thread to the scheduler.
29 | #[allow(dead_code)]
30 | #[inline(always)]
31 | pub fn yield_os() {
32 | // On Unix systems, this function uses libc's sched_yield(), which cooperatively
33 | // gives up a random timeslice to another thread. On Windows systems, it
34 | // uses SwitchToThread(), which does the same thing.
35 | std::thread::yield_now();
36 | }
37 |
38 | /// Spins in a loop for a finite amount of time.
39 | #[allow(dead_code)]
40 | #[inline(always)]
41 | pub fn spin_wait(count: usize) {
42 | for _ in 0..count {
43 | spin_hint();
44 | }
45 | }
46 |
47 | /// Yields the thread to the scheduler for a short random duration.
48 | /// This function is implemented using a simple 7-bit pseudo random number
49 | /// generator based on an atomic fetch-and-add operation.
50 | #[allow(dead_code)]
51 | #[inline(always)]
52 | pub fn spin_rand() {
53 | // This number will be added to the calculated pseudo-random number to avoid
54 | // short spins.
55 | const OFFSET: usize = 1 << 6;
56 | spin_wait((random_u7() as usize).wrapping_add(OFFSET));
57 | }
58 |
59 | /// Generates a 7-bit pseudo-random number using an atomic fetch-and-add
60 | /// operation and a linear congruential generator (LCG)-like algorithm.
61 | /// This generator is only suited for the special use-case of yield_now(), and
62 | /// not recommended for use anywhere else.
63 | #[allow(dead_code)]
64 | #[inline(always)]
65 | fn random_u7() -> u8 {
66 | static SEED: AtomicU8 = AtomicU8::new(13);
67 | const MULTIPLIER: u8 = 113;
68 | // Increment the seed atomically. Relaxed ordering is enough as we only need an
69 | // atomic operation on the SEED itself.
70 | let seed = SEED.fetch_add(1, Ordering::Relaxed);
71 | // Use a LCG-like algorithm to generate a random number from the seed.
72 | seed.wrapping_mul(MULTIPLIER) & 0x7F
73 | }
74 |
75 | /// Generates a pseudo-random u32 number using an atomic fetch-and-add operation
76 | /// and a LCG-like algorithm. This function is implemented using the same
77 | /// algorithm as random_u8().
78 | #[allow(dead_code)]
79 | #[inline(always)]
80 | fn random_u32() -> u32 {
81 | static SEED: AtomicU32 = AtomicU32::new(13);
82 | const MULTIPLIER: u32 = 1812433253;
83 | let seed = SEED.fetch_add(1, Ordering::Relaxed);
84 | seed.wrapping_mul(MULTIPLIER)
85 | }
86 |
87 | /// Randomizes the input by up to 25%.
88 | /// This function is used to introduce some randomness into backoff strategies.
89 | #[allow(dead_code)]
90 | #[inline(always)]
91 | pub fn randomize(d: usize) -> usize {
92 | d - (d >> 3) + random_u32() as usize % (d >> 2)
93 | }
94 |
95 | // Static atomic variable used to store the degree of parallelism.
96 | // Initialized to 0, meaning that the parallelism degree has not been computed
97 | // yet.
98 | static PARALLELISM: AtomicUsize = AtomicUsize::new(0);
99 |
100 | /// Retrieves the available degree of parallelism.
101 | /// If the degree of parallelism has not been computed yet, it computes and
102 | /// stores it in the PARALLELISM atomic variable. The degree of parallelism
103 | /// typically corresponds to the number of processor cores that can execute
104 | /// threads concurrently.
105 | #[inline(always)]
106 | pub fn get_parallelism() -> usize {
107 | let mut p = PARALLELISM.load(Ordering::Relaxed);
108 | // If the parallelism degree has not been computed yet.
109 | if p == 0 {
110 | // Try to get the degree of parallelism from available_parallelism.
111 | // If it is not available, default to 1.
112 | p = usize::from(available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()));
113 | PARALLELISM.store(p, Ordering::SeqCst);
114 | }
115 | // Return the computed degree of parallelism.
116 | p
117 | }
118 |
119 | /// Spins until the specified condition becomes true.
120 | /// This function uses a combination of spinning, yielding, and sleeping to
121 | /// reduce busy waiting and improve the efficiency of concurrent systems.
122 | ///
123 | /// The function starts with a short spinning phase, followed by a longer
124 | /// spinning and yielding phase, then a longer spinning and yielding phase with
125 | /// the operating system's yield function, and finally a phase with zero-length
126 | /// sleeping and yielding.
127 | ///
128 | /// The function uses a geometric backoff strategy to increase the spin time
129 | /// between each phase. The spin time starts at 8 iterations and doubles after
130 | /// each unsuccessful iteration, up to a maximum of 2^30 iterations.
131 | ///
132 | /// The function also uses a simple randomization strategy to introduce some
133 | /// variation into the spin time.
134 | ///
135 | /// The function takes a closure that returns a boolean value indicating whether
136 | /// the condition has been met. The function returns when the condition is true.
137 | #[allow(dead_code)]
138 | #[allow(clippy::reversed_empty_ranges)]
139 | #[inline(always)]
140 | pub fn spin_cond bool>(cond: F) {
141 | if get_parallelism() == 1 {
142 | // For environments with limited resources, such as small Virtual Private
143 | // Servers (VPS) or single-core systems, active spinning may lead to inefficient
144 | // CPU usage without performance benefits. This is due to the fact that there's
145 | // only one thread of execution, making it impossible for another thread to make
146 | // progress during the spin wait period.
147 | while !cond() {
148 | yield_os();
149 | }
150 | return;
151 | }
152 |
153 | const NO_YIELD: usize = 1;
154 | const SPIN_YIELD: usize = 1;
155 | const OS_YIELD: usize = 0;
156 | const ZERO_SLEEP: usize = 2;
157 | const SPINS: u32 = 8;
158 | let mut spins: u32 = SPINS;
159 |
160 | // Short spinning phase
161 | for _ in 0..NO_YIELD {
162 | for _ in 0..SPINS / 2 {
163 | if cond() {
164 | return;
165 | }
166 | spin_hint();
167 | }
168 | }
169 |
170 | // Longer spinning and yielding phase
171 | loop {
172 | for _ in 0..SPIN_YIELD {
173 | spin_rand();
174 |
175 | for _ in 0..spins {
176 | if cond() {
177 | return;
178 | }
179 | }
180 | }
181 |
182 | // Longer spinning and yielding phase with OS yield
183 | for _ in 0..OS_YIELD {
184 | yield_os();
185 |
186 | for _ in 0..spins {
187 | if cond() {
188 | return;
189 | }
190 | }
191 | }
192 |
193 | // Phase with zero-length sleeping and yielding
194 | for _ in 0..ZERO_SLEEP {
195 | sleep(Duration::from_nanos(0));
196 |
197 | for _ in 0..spins {
198 | if cond() {
199 | return;
200 | }
201 | }
202 | }
203 |
204 | // Geometric backoff
205 | if spins < (1 << 30) {
206 | spins <<= 1;
207 | }
208 | // Backoff about 1ms
209 | sleep(Duration::from_nanos(1 << 20));
210 | }
211 | }
212 |
213 | macro_rules! return_if_some {
214 | ($result:expr) => {{
215 | let result = $result;
216 | if result.is_some() {
217 | return result;
218 | }
219 | }};
220 | }
221 |
222 | /// Computes a future timeout instant by adding a specified number of microseconds to the current time.
223 | ///
224 | /// # Parameters
225 | /// - `spin_micros`: The number of microseconds to add to the current time.
226 | ///
227 | /// # Returns
228 | /// A [`std::time::Instant`] indicating when the timeout will occur.
229 | ///
230 | /// # Panics
231 | /// This function will panic if the addition of the duration results in an overflow.
232 | #[inline(always)]
233 | #[allow(dead_code)]
234 | pub(crate) fn spin_option_yield_only(
235 | predicate: impl Fn() -> Option,
236 | spin_micros: u64,
237 | ) -> Option {
238 | // exit early if predicate is already satisfied
239 | return_if_some!(predicate());
240 | let timeout = if let Some(timeout) =
241 | std::time::Instant::now().checked_add(Duration::from_micros(spin_micros))
242 | {
243 | timeout
244 | } else {
245 | return None;
246 | };
247 |
248 | loop {
249 | for _ in 0..32 {
250 | yield_os();
251 | return_if_some!(predicate());
252 | }
253 | if std::time::Instant::now() >= timeout {
254 | return None;
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | #![forbid(unsafe_code)]
2 | use core::fmt;
3 | use core::fmt::Debug;
4 | /// Error type for channel send operations without timeout
5 | #[derive(Debug, PartialEq, Eq)]
6 | pub enum SendError {
7 | /// Indicates that the channel is closed on both sides with
8 | /// call to `close()`
9 | Closed,
10 | /// Indicates that all receiver instances are dropped and the channel is
11 | /// closed from the receive side
12 | ReceiveClosed,
13 | }
14 | impl core::error::Error for SendError {}
15 | impl fmt::Display for SendError {
16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17 | fmt::Display::fmt(
18 | match *self {
19 | SendError::Closed => "send to a closed channel",
20 | SendError::ReceiveClosed => "send to a half closed channel",
21 | },
22 | f,
23 | )
24 | }
25 | }
26 |
27 | /// Error type for channel send operations with timeout
28 | #[derive(Debug, PartialEq, Eq)]
29 | pub enum SendErrorTimeout {
30 | /// Indicates that the channel is closed on both sides with a call to
31 | /// `close()`
32 | Closed,
33 | /// Indicates that all receiver instances are dropped and the channel is
34 | /// closed from the receive side
35 | ReceiveClosed,
36 | /// Indicates that channel operation reached timeout and is canceled
37 | Timeout,
38 | }
39 | impl core::error::Error for SendErrorTimeout {}
40 | impl fmt::Display for SendErrorTimeout {
41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 | fmt::Display::fmt(
43 | match *self {
44 | SendErrorTimeout::Closed => "send to a closed channel",
45 | SendErrorTimeout::ReceiveClosed => "send to a half closed channel",
46 | SendErrorTimeout::Timeout => "send timeout",
47 | },
48 | f,
49 | )
50 | }
51 | }
52 |
53 | /// Error type for channel receive operations without timeout
54 | #[derive(Debug, PartialEq, Eq)]
55 | pub enum ReceiveError {
56 | /// Indicates that the channel is closed on both sides with a call to
57 | /// `close()`
58 | Closed,
59 | /// Indicates that all sender instances are dropped and the channel is
60 | /// closed from the send side
61 | SendClosed,
62 | }
63 | impl core::error::Error for ReceiveError {}
64 | impl fmt::Display for ReceiveError {
65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 | fmt::Display::fmt(
67 | match *self {
68 | ReceiveError::Closed => "receive from a closed channel",
69 | ReceiveError::SendClosed => "receive from a half closed channel",
70 | },
71 | f,
72 | )
73 | }
74 | }
75 |
76 | /// Error type for channel receive operations with timeout
77 | #[derive(Debug, PartialEq, Eq)]
78 | pub enum ReceiveErrorTimeout {
79 | /// Indicates that the channel is closed on both sides with a call to
80 | /// `close()`
81 | Closed,
82 | /// Indicates that all sender instances are dropped and the channel is
83 | /// closed from the send side
84 | SendClosed,
85 | /// Indicates that channel operation reached timeout and is canceled
86 | Timeout,
87 | }
88 | impl core::error::Error for ReceiveErrorTimeout {}
89 | impl fmt::Display for ReceiveErrorTimeout {
90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 | fmt::Display::fmt(
92 | match *self {
93 | ReceiveErrorTimeout::Closed => "receive from a closed channel",
94 | ReceiveErrorTimeout::SendClosed => "receive from a half closed channel",
95 | ReceiveErrorTimeout::Timeout => "receive timeout",
96 | },
97 | f,
98 | )
99 | }
100 | }
101 |
102 | /// Error type for closing a channel when channel is already closed
103 | #[derive(Debug, PartialEq, Eq)]
104 | pub struct CloseError();
105 | impl core::error::Error for CloseError {}
106 | impl fmt::Display for CloseError {
107 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 | fmt::Display::fmt("channel is already closed", f)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/future.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | internal::{acquire_internal, Internal},
3 | pointer::KanalPtr,
4 | signal::Signal,
5 | AsyncReceiver, ReceiveError, SendError,
6 | };
7 | use core::{
8 | fmt::Debug,
9 | marker::PhantomPinned,
10 | mem::{needs_drop, size_of, MaybeUninit},
11 | pin::Pin,
12 | task::Poll,
13 | };
14 | use futures_core::{FusedStream, Future, Stream};
15 |
16 | #[repr(u8)]
17 | #[derive(PartialEq, Clone, Copy)]
18 | pub(crate) enum FutureState {
19 | Zero,
20 | Waiting,
21 | Done,
22 | }
23 |
24 | impl FutureState {
25 | #[inline(always)]
26 | fn is_waiting(&self) -> bool {
27 | *self == FutureState::Waiting
28 | }
29 |
30 | #[inline(always)]
31 | fn is_done(&self) -> bool {
32 | *self == FutureState::Done
33 | }
34 | }
35 |
36 | /// SendFuture is a future for sending an object to a channel asynchronously.
37 | /// It must be polled to complete the send operation.
38 | #[must_use = "futures do nothing unless you .await or poll them"]
39 | pub struct SendFuture<'a, T> {
40 | state: FutureState,
41 | internal: &'a Internal,
42 | sig: Signal,
43 | data: MaybeUninit,
44 | _pinned: PhantomPinned,
45 | }
46 |
47 | impl Debug for SendFuture<'_, T> {
48 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49 | write!(f, "SendFuture {{ .. }}")
50 | }
51 | }
52 |
53 | impl Drop for SendFuture<'_, T> {
54 | fn drop(&mut self) {
55 | if !self.state.is_done() {
56 | if self.state.is_waiting()
57 | && !acquire_internal(self.internal).cancel_send_signal(&self.sig)
58 | {
59 | // a receiver got signal ownership, should wait until the response
60 | if self.sig.async_blocking_wait() {
61 | // no need to drop data is moved to receiver
62 | return;
63 | }
64 | }
65 | // signal is canceled, or in zero stated, drop data locally
66 | if needs_drop::() {
67 | // Safety: data is not moved it's safe to drop it
68 | unsafe {
69 | self.drop_local_data();
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | impl<'a, T> SendFuture<'a, T> {
77 | /// Creates a new SendFuture with the given internal channel and data.
78 | #[inline(always)]
79 | pub(crate) fn new(internal: &'a Internal, data: T) -> Self {
80 | if size_of::() > size_of::<*mut T>() {
81 | SendFuture {
82 | state: FutureState::Zero,
83 | internal,
84 | sig: Signal::new_async(),
85 | data: MaybeUninit::new(data),
86 | _pinned: PhantomPinned,
87 | }
88 | } else {
89 | SendFuture {
90 | state: FutureState::Zero,
91 | internal,
92 | sig: Signal::new_async_ptr(KanalPtr::new_owned(data)),
93 | data: MaybeUninit::uninit(),
94 | _pinned: PhantomPinned,
95 | }
96 | }
97 | }
98 | /// Safety: it's only safe to call this function once and only if send
99 | /// operation will finish after this call.
100 | #[inline(always)]
101 | unsafe fn read_local_data(&self) -> T {
102 | if size_of::() > size_of::<*mut T>() {
103 | // if its smaller than register size, it does not need pointer setup as data
104 | // will be stored in register address object
105 | core::ptr::read(self.data.as_ptr())
106 | } else {
107 | self.sig.assume_init()
108 | }
109 | }
110 | /// Safety: it's only safe to call this function once and only if send
111 | /// operation fails
112 | #[inline(always)]
113 | unsafe fn drop_local_data(&mut self) {
114 | if size_of::() > size_of::<*mut T>() {
115 | self.data.assume_init_drop();
116 | } else {
117 | self.sig.load_and_drop();
118 | }
119 | }
120 | }
121 |
122 | impl Future for SendFuture<'_, T> {
123 | type Output = Result<(), SendError>;
124 |
125 | #[inline(always)]
126 | fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll {
127 | let this = unsafe { self.get_unchecked_mut() };
128 |
129 | match this.state {
130 | FutureState::Zero => {
131 | let mut internal = acquire_internal(this.internal);
132 | if internal.recv_count == 0 {
133 | let send_count = internal.send_count;
134 | drop(internal);
135 | this.state = FutureState::Done;
136 | if needs_drop::() {
137 | // the data failed to move, drop it locally
138 | // Safety: the data is not moved, we are sure that it is inited in this
139 | // point, it's safe to init drop it.
140 | unsafe {
141 | this.drop_local_data();
142 | }
143 | }
144 | return Poll::Ready(Err(if send_count == 0 {
145 | SendError::Closed
146 | } else {
147 | SendError::ReceiveClosed
148 | }));
149 | }
150 | if let Some(first) = internal.next_recv() {
151 | drop(internal);
152 | this.state = FutureState::Done;
153 | // Safety: data is inited and available from constructor
154 | unsafe { first.send(this.read_local_data()) }
155 | Poll::Ready(Ok(()))
156 | } else if internal.queue.len() < internal.capacity {
157 | this.state = FutureState::Done;
158 | // Safety: data is inited and available from constructor
159 | internal.queue.push_back(unsafe { this.read_local_data() });
160 | drop(internal);
161 | Poll::Ready(Ok(()))
162 | } else {
163 | this.state = FutureState::Waiting;
164 | // if T is smaller than register size, we already have data in pointer address
165 | // from initialization step
166 | if size_of::() > size_of::<*mut T>() {
167 | this.sig
168 | .set_ptr(KanalPtr::new_unchecked(this.data.as_mut_ptr()));
169 | }
170 | this.sig.register_waker(cx.waker());
171 | // send directly to the waitlist
172 | internal.push_send(this.sig.get_terminator());
173 | drop(internal);
174 | Poll::Pending
175 | }
176 | }
177 | FutureState::Waiting => match this.sig.poll() {
178 | Poll::Ready(success) => {
179 | this.state = FutureState::Done;
180 | if success {
181 | Poll::Ready(Ok(()))
182 | } else {
183 | if needs_drop::() {
184 | // the data failed to move, drop it locally
185 | // Safety: the data is not moved, we are sure that it is inited in
186 | // this point, it's safe to init drop it.
187 | unsafe {
188 | this.drop_local_data();
189 | }
190 | }
191 | Poll::Ready(Err(SendError::Closed))
192 | }
193 | }
194 | Poll::Pending => {
195 | if !this.sig.will_wake(cx.waker()) {
196 | // Waker is changed and we need to update waker in the waiting list
197 | if acquire_internal(this.internal).send_signal_exists(&this.sig) {
198 | // signal is not shared with other thread yet so it's safe to
199 | // update waker locally
200 | // this.sig.register_waker(cx.waker());
201 | Poll::Pending
202 | } else {
203 | // signal is already shared, and data will be available shortly, so wait
204 | // synchronously and return the result note:
205 | // it's not possible safely to update waker after the signal is shared,
206 | // but we know data will be ready shortly,
207 | // we can wait synchronously and receive it.
208 | this.state = FutureState::Done;
209 | if this.sig.async_blocking_wait() {
210 | Poll::Ready(Ok(()))
211 | } else {
212 | // the data failed to move, drop it locally
213 | // Safety: the data is not moved, we are sure that it is inited in
214 | // this point, it's safe to init
215 | // drop it.
216 | if needs_drop::() {
217 | unsafe {
218 | this.drop_local_data();
219 | }
220 | }
221 | Poll::Ready(Err(SendError::Closed))
222 | }
223 | }
224 | } else {
225 | Poll::Pending
226 | }
227 | }
228 | },
229 | _ => panic!("polled after result is already returned"),
230 | }
231 | }
232 | }
233 |
234 | /// ReceiveFuture is a future for receiving an object from a channel
235 | /// asynchronously. It must be polled to complete the receive operation.
236 | #[must_use = "futures do nothing unless you .await or poll them"]
237 | pub struct ReceiveFuture<'a, T> {
238 | state: FutureState,
239 | is_stream: bool,
240 | internal: &'a Internal,
241 | sig: Signal,
242 | data: MaybeUninit,
243 | _pinned: PhantomPinned,
244 | }
245 |
246 | impl Drop for ReceiveFuture<'_, T> {
247 | fn drop(&mut self) {
248 | if self.state.is_waiting() {
249 | // try to cancel recv signal
250 | if !acquire_internal(self.internal).cancel_recv_signal(&self.sig) {
251 | // a sender got signal ownership, receiver should wait until the response
252 | if self.sig.async_blocking_wait() {
253 | // got ownership of data that is not going to be used ever again, so drop it
254 | if needs_drop::() {
255 | // Safety: data is not moved it's safe to drop it
256 | unsafe {
257 | self.drop_local_data();
258 | }
259 | }
260 | }
261 | }
262 | }
263 | }
264 | }
265 |
266 | impl Debug for ReceiveFuture<'_, T> {
267 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
268 | write!(f, "ReceiveFuture {{ .. }}")
269 | }
270 | }
271 |
272 | impl<'a, T> ReceiveFuture<'a, T> {
273 | #[inline(always)]
274 | unsafe fn read_local_data(&self) -> T {
275 | if size_of::() > size_of::<*mut T>() {
276 | // if T is smaller than register size, it does not need pointer setup as data
277 | // will be stored in register address object
278 | core::ptr::read(self.data.as_ptr())
279 | } else {
280 | self.sig.assume_init()
281 | }
282 | }
283 |
284 | #[inline(always)]
285 | unsafe fn drop_local_data(&mut self) {
286 | if size_of::() > size_of::<*mut T>() {
287 | self.data.assume_init_drop();
288 | } else {
289 | self.sig.load_and_drop();
290 | }
291 | }
292 |
293 | #[inline(always)]
294 | pub(crate) fn new_ref(internal: &'a Internal) -> Self {
295 | Self {
296 | state: FutureState::Zero,
297 | sig: Signal::new_async(),
298 | internal,
299 | data: MaybeUninit::uninit(),
300 | is_stream: false,
301 | _pinned: PhantomPinned,
302 | }
303 | }
304 | }
305 |
306 | impl Future for ReceiveFuture<'_, T> {
307 | type Output = Result;
308 |
309 | #[inline(always)]
310 | fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll {
311 | let this = unsafe { self.get_unchecked_mut() };
312 |
313 | loop {
314 | return match this.state {
315 | FutureState::Zero => {
316 | let mut internal = acquire_internal(this.internal);
317 | if internal.recv_count == 0 {
318 | this.state = FutureState::Done;
319 | return Poll::Ready(Err(ReceiveError::Closed));
320 | }
321 | if let Some(v) = internal.queue.pop_front() {
322 | if let Some(t) = internal.next_send() {
323 | // if there is a sender take its data and push it into the queue
324 | unsafe { internal.queue.push_back(t.recv()) }
325 | }
326 | drop(internal);
327 | this.state = FutureState::Done;
328 | Poll::Ready(Ok(v))
329 | } else if let Some(t) = internal.next_send() {
330 | drop(internal);
331 | this.state = FutureState::Done;
332 | Poll::Ready(Ok(unsafe { t.recv() }))
333 | } else {
334 | if internal.send_count == 0 {
335 | this.state = FutureState::Done;
336 | return Poll::Ready(Err(ReceiveError::SendClosed));
337 | }
338 | this.state = FutureState::Waiting;
339 | if size_of::() > size_of::<*mut T>() {
340 | // if type T smaller than register size, it does not need pointer setup
341 | // as data will be stored in register address object
342 | this.sig
343 | .set_ptr(KanalPtr::new_unchecked(this.data.as_mut_ptr()));
344 | }
345 | this.sig.register_waker(cx.waker());
346 | // no active waiter so push to the queue
347 | internal.push_recv(this.sig.get_terminator());
348 | drop(internal);
349 | Poll::Pending
350 | }
351 | }
352 | FutureState::Waiting => match this.sig.poll() {
353 | Poll::Ready(success) => {
354 | this.state = FutureState::Done;
355 | if success {
356 | Poll::Ready(Ok(unsafe { this.read_local_data() }))
357 | } else {
358 | Poll::Ready(Err(ReceiveError::Closed))
359 | }
360 | }
361 | Poll::Pending => {
362 | if !this.sig.will_wake(cx.waker()) {
363 | // the Waker is changed and we need to update waker in the waiting
364 | // list
365 | if acquire_internal(this.internal).recv_signal_exists(&this.sig) {
366 | // signal is not shared with other thread yet so it's safe
367 | // to update waker locally
368 | this.sig.register_waker(cx.waker());
369 | Poll::Pending
370 | } else {
371 | // the signal is already shared, and data will be available shortly,
372 | // so wait synchronously and return the result
373 | // note: it's not possible safely to update waker after the signal
374 | // is shared, but we know data will be ready shortly,
375 | // we can wait synchronously and receive it.
376 | this.state = FutureState::Done;
377 | if this.sig.async_blocking_wait() {
378 | Poll::Ready(Ok(unsafe { this.read_local_data() }))
379 | } else {
380 | Poll::Ready(Err(ReceiveError::Closed))
381 | }
382 | }
383 | } else {
384 | Poll::Pending
385 | }
386 | }
387 | },
388 | _ => {
389 | if this.is_stream {
390 | this.state = FutureState::Zero;
391 | continue;
392 | }
393 | panic!("polled after result is already returned")
394 | }
395 | };
396 | }
397 | }
398 | }
399 |
400 | /// ReceiveStream is a stream for receiving objects from a channel
401 | /// asynchronously.
402 | pub struct ReceiveStream<'a, T: 'a> {
403 | future: Pin>>,
404 | terminated: bool,
405 | receiver: &'a AsyncReceiver,
406 | }
407 |
408 | impl Debug for ReceiveStream<'_, T> {
409 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
410 | write!(f, "ReceiveStream {{ .. }}")
411 | }
412 | }
413 |
414 | impl Stream for ReceiveStream<'_, T> {
415 | type Item = T;
416 |
417 | fn poll_next(
418 | mut self: Pin<&mut Self>,
419 | cx: &mut core::task::Context<'_>,
420 | ) -> Poll