#channel #async-channel #reusable #async #oneshot #future

multishot

An async, lock-free, reusable channel for sending single values to asynchronous tasks

3 releases (breaking)

0.3.2 Mar 20, 2024
0.3.1 Mar 19, 2024
0.3.0 Dec 29, 2022
0.2.0 Feb 14, 2022
0.1.0 Feb 10, 2022

#386 in Asynchronous

Download history 5/week @ 2024-02-12 75/week @ 2024-02-19 44/week @ 2024-02-26 73/week @ 2024-03-04 167/week @ 2024-03-11 442/week @ 2024-03-18 47/week @ 2024-03-25 171/week @ 2024-04-01

832 downloads per month
Used in asynchronix

MIT/Apache

53KB
678 lines

multishot

An async, lock-free, reusable channel for sending single values to asynchronous tasks.

Cargo Documentation License

Overview

In a multi-shot channel, the receiver half is reusable and able to recycle the sender half without ever re-allocating. Sending or awaiting a value and recycling the sender are all lock-free operations, the last two being additionally wait-free. Producing a new sender does not require additional synchronization or spinning: it is guaranteed to succeed immediately if the value sent by a previous sender was received.

Usage

Add this to your Cargo.toml:

[dependencies]
multishot = "0.3.2"

Example

use std::thread;

async {
    let (s, mut r) = multishot::channel();

    // Send a value to the channel from another thread.
    thread::spawn(move || {
        s.send("42");
    });

    // Receive the value.
    let res = r.recv().await;
    assert_eq!(res, Ok("42"));

    // Recycle the sender. This is guaranteed to succeed if the previous
    // message has been read.
    let s = r.sender().unwrap();

    // Drop the sender on another thread without sending a message.
    thread::spawn(move || {
        drop(s);
    });

    // Receive an error.
    let res = r.recv().await;
    assert_eq!(res, Err(multishot::RecvError {}));
};

Safety

This is a low-level primitive and as such its implementation relies on unsafe. The test suite makes extensive use of Loom and MIRI to assess its correctness. As amazing as they are, however, Loom and MIRI cannot formally prove the absence of data races so soundness issues are possible.

Implementation

Sending, receiving and recycling a sender are lock-free operations; the last two are additionally wait-free.

Polling requires no read-modify-write (RMW) operation if the value is readily available, 1 RMW if this is the first waker update and 2 RMWs otherwise. Sending needs 1 RMW if no waker was registered, and typically 2 RMW if one was registered. Compared to a non-reusable one-shot channel such as Tokio's, the only extra cost is 1 RMW in case the waker was updated (which is rare in practice). Also, the implementation of multishot partially offsets this extra cost by using arithmetic atomic operations when sending rather than the typically more expensive compare-and-swap operation.

License

This software is licensed under the Apache License, Version 2.0 or the MIT license, at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~0–29MB
~358K SLoC