This commit is contained in:
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Cargo test & clippy
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
task:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose --all-features
|
||||||
|
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy --all-features
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
72
Cargo.lock
generated
Normal file
72
Cargo.lock
generated
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "laika"
|
||||||
|
version = "0.1.4"
|
||||||
|
dependencies = [
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.110"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "laika"
|
||||||
|
version = "0.1.4"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["Laika Schmidt <laika.schmidt@magenta.de>"]
|
||||||
|
description = "A namespace crate containing miscellaneous submodules (like an SPMC channel) of owner its-laika"
|
||||||
|
repository = "https://github.com/its-laika/crates"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
keywords = ["channel", "spmc", "oneshot"]
|
||||||
|
categories = ["concurrency"]
|
||||||
|
include = ["**/*.rs", "Cargo.toml"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
source = "src/lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
shotgun = []
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { version = "1.48", features = ["rt", "macros"] }
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Laika Schmidt
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Laika's namespace crate
|
||||||
|
|
||||||
|
**This module contains multiple submodules (included via feature flags) with
|
||||||
|
different functionalities. They're all grouped under the `laika` namespace,
|
||||||
|
providing some kind of scoped crates (avoiding naming conflicts).**
|
||||||
|
|
||||||
|
## Submodules / Features
|
||||||
|
|
||||||
|
## shotgun
|
||||||
|
_A dead simple one-shot single producer, multiple consumer (SPMC) channel_
|
||||||
|
|
||||||
|
### About
|
||||||
|
Shotgun is a simple one-shot single producer, multiple consumer (SPMC) channel.
|
||||||
|
It internally uses `std::sync::Mutex` and `std::sync::Arc` and does not contain
|
||||||
|
any unsafe code.
|
||||||
|
|
||||||
|
### When to use
|
||||||
|
|
||||||
|
Likely when you need to pass a signal to multiple threads or functions to stop
|
||||||
|
in order to shut down the application.
|
||||||
|
|
||||||
|
### How to use
|
||||||
|
|
||||||
|
#### Synchronous
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
use laika::shotgun::channel;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let rx1 = rx.clone();
|
||||||
|
let thread1 = thread::spawn(move || loop {
|
||||||
|
if rx1.try_recv().is_some() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
let rx2 = rx.clone();
|
||||||
|
let thread2 = thread::spawn(move || loop {
|
||||||
|
if rx2.try_recv().is_some() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
tx.send(()); // `tx` is dropped here.
|
||||||
|
|
||||||
|
assert!(thread1.join().is_ok_and(|v| v == 1));
|
||||||
|
assert!(thread2.join().is_ok_and(|v| v == 2));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Asynchronous
|
||||||
|
```rust
|
||||||
|
#[tokio]
|
||||||
|
async fn main() {
|
||||||
|
use laika::shotgun::channel;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let rx1 = rx.clone();
|
||||||
|
let fun1 = async move {
|
||||||
|
rx1.await;
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
let rx2 = rx.clone();
|
||||||
|
let fun2 = async move {
|
||||||
|
// Explicit call to recv(), does the same as calling`.await` directly.
|
||||||
|
rx2.recv().await;
|
||||||
|
2
|
||||||
|
};
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
tx.send(());
|
||||||
|
|
||||||
|
let rx3 = rx.clone();
|
||||||
|
let fun3 = async move {
|
||||||
|
rx3.await;
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = join!(fun1, fun2);
|
||||||
|
|
||||||
|
assert_eq!(result.0, 1);
|
||||||
|
assert_eq!(result.1, 2);
|
||||||
|
assert_eq!(fun3.await, 3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
[MIT](LICENSE)
|
||||||
22
SECURITY.md
Normal file
22
SECURITY.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Support is limited to the latest version and also the current state in the main
|
||||||
|
branch.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------------- | ------------------ |
|
||||||
|
| _main_ | :white_check_mark: |
|
||||||
|
| 0.1.4 | :white_check_mark: |
|
||||||
|
| <= 0.1.3 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you want to report a vulnerability, check if it's possible to create an issue
|
||||||
|
without endangering other users. If that's not the case, send a mail to the
|
||||||
|
address on my ([its-laika](https://github.com/its-laika)) profile. I check my
|
||||||
|
mails pretty frequently so expect a reply within a few days. From there on, I'll
|
||||||
|
do my best to fix the vulnerability asap. If you want to, you can also provide
|
||||||
|
patches directly or via pull requests. A new version is pushed as soon as the
|
||||||
|
vulnerability is fixed.
|
||||||
16
src/lib.rs
Normal file
16
src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//! # Laika's namespace crate
|
||||||
|
//!
|
||||||
|
//! This module contains multiple submodules (included via feature flags) with
|
||||||
|
//! different functionalities. They're all grouped under the `laika` namespace,
|
||||||
|
//! providing some kind of scoped crates (avoiding naming conflicts).
|
||||||
|
//!
|
||||||
|
//! ## Submodules / Features
|
||||||
|
//!
|
||||||
|
//! ### [`shotgun`]
|
||||||
|
//!
|
||||||
|
//! Shotgun is a simple one-shot single producer, multiple consumer (SPMC)
|
||||||
|
//! channel. It internally uses `std::sync::Mutex` and `std::sync::Arc` and does
|
||||||
|
//! not contain any unsafe code.
|
||||||
|
//! See module documentation for more information.
|
||||||
|
#[cfg(feature = "shotgun")]
|
||||||
|
pub mod shotgun;
|
||||||
426
src/shotgun.rs
Normal file
426
src/shotgun.rs
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
//! # A dead simple one-shot single producer, multiple consumer (SPMC) channel
|
||||||
|
//!
|
||||||
|
//! Shotgun is a simple oneshot single producer, multiple consumer (SPMC)
|
||||||
|
//! channel. Internally using [`std::sync::Mutex`] and [`std::sync::Arc`], not
|
||||||
|
//! containing any unsafe code.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
clone::Clone,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
task::{Context, Poll, Waker},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Oneshot receiver of a [`channel`]
|
||||||
|
///
|
||||||
|
/// Use [`Receiver::try_recv`] or [`Receiver::recv`] to (try to) receive a value
|
||||||
|
/// from the channel, if it has been sent. As this is a oneshot receiver, only
|
||||||
|
/// one value can be received.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ## Synchronous
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // Initialy, oneshot receiver has no value
|
||||||
|
/// assert_eq!(rx.try_recv(), None);
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
///
|
||||||
|
/// // Now, oneshot receiver has the value
|
||||||
|
/// assert_eq!(rx.try_recv(), Some(12));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Asynchronous
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // ... in any async runtime
|
||||||
|
///
|
||||||
|
/// let fun1 = async move {
|
||||||
|
/// rx.recv().await;
|
||||||
|
/// return 1;
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Receiver<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Inner receiver that holds the sent value and possible wakers
|
||||||
|
inner: Arc<Mutex<_Receiver<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Oneshot sender of a [`channel`]
|
||||||
|
///
|
||||||
|
/// Use [`Sender::send`] to send a value to all receivers of the channel.
|
||||||
|
/// As this is a oneshot sender, only one value can be sent.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ## Send a value
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Sender is dropped after sending
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
/// tx.send(13); // This won't compile
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sender<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
inner: _Sender<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Receiver<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Try to receive a value from the channel, if it has been sent.
|
||||||
|
/// As this is a oneshot receiver, only one value can be received.
|
||||||
|
/// This function is **non-blocking** and just returns [`None`] if no value
|
||||||
|
/// has been sent.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if mutex is poisened due to another thread panicking while using
|
||||||
|
/// inner receiver too.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // Initialy, oneshot receiver has no value
|
||||||
|
/// assert_eq!(rx.try_recv(), None);
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
///
|
||||||
|
/// // Now, oneshot receiver has the value
|
||||||
|
/// assert_eq!(rx.try_recv(), Some(12));
|
||||||
|
/// // Value is kept after being received
|
||||||
|
/// assert_eq!(rx.try_recv(), Some(12));
|
||||||
|
/// ```
|
||||||
|
pub fn try_recv(&self) -> Option<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
self.inner
|
||||||
|
.as_ref()
|
||||||
|
.lock()
|
||||||
|
.expect("Mutex is poisoned")
|
||||||
|
.try_recv()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a value from the channel.
|
||||||
|
/// Waits until value has been sent and then returns it.
|
||||||
|
/// This function is blocking asynchronously.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// You can directly [`Future`]'s `.await` on the receiver too.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// (*Note that this won't compile because no async runtime exists here.*)
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// let fun1 = async move {
|
||||||
|
/// rx.recv().await;
|
||||||
|
/// return 1;
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(());
|
||||||
|
///
|
||||||
|
/// // Now, oneshot receiver has the value
|
||||||
|
/// assert_eq!(fun1.await, 1);
|
||||||
|
/// ```
|
||||||
|
pub async fn recv(self) -> T {
|
||||||
|
self.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Sender<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Send a value to all receivers of the channel.
|
||||||
|
/// As this is a oneshot sender, only one value can be sent.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ## Send a value
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel();
|
||||||
|
///
|
||||||
|
/// // Send a value
|
||||||
|
/// tx.send(12);
|
||||||
|
/// ```
|
||||||
|
pub fn send(self, value: T) {
|
||||||
|
self.inner.send(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner receiver of a [`channel`]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct _Receiver<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Value that was sent by [`_Sender`]
|
||||||
|
value: Option<T>,
|
||||||
|
/// Wakers that will be woken up when value is sent by [`_Sender`]
|
||||||
|
wakers: Vec<Waker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner sender of a [`channel`]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct _Sender<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// [`_Receiver`] instance that will receive the value and is referecend by
|
||||||
|
/// all [`Receiver`]s.
|
||||||
|
receiver: Option<Arc<Mutex<_Receiver<T>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> _Receiver<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Clones the value (if it has been given by [`_Sender`]) and returns clone
|
||||||
|
/// of it.
|
||||||
|
fn try_recv(&self) -> Option<T> {
|
||||||
|
self.value.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value to be received by all [`Receiver`]s from [`_Sender`].
|
||||||
|
fn set(&mut self, value: T) {
|
||||||
|
self.value = Some(value);
|
||||||
|
|
||||||
|
for waker in self.wakers.clone() {
|
||||||
|
waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [`Future`] for [`Receiver`] to be able to use it in async
|
||||||
|
/// functions.
|
||||||
|
impl<T> Future for Receiver<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut inner = self.inner.lock().expect("Mutex is poisoned");
|
||||||
|
|
||||||
|
if let Some(value) = &inner.value {
|
||||||
|
Poll::Ready(value.clone())
|
||||||
|
} else {
|
||||||
|
if inner.wakers.iter().all(|w| !w.will_wake(cx.waker())) {
|
||||||
|
inner.wakers.push(cx.waker().clone());
|
||||||
|
}
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> _Sender<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
/// Send a value to all [`Receiver`]s.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if mutex is poisened due to another thread panicking while using
|
||||||
|
/// referenced receiver too.
|
||||||
|
fn send(self, value: T) {
|
||||||
|
if let Some(recv) = self.receiver.as_ref() {
|
||||||
|
recv.lock().expect("Mutex is poisoned").set(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a one-shot, single producer multiple consumer channel that can be
|
||||||
|
/// used to send one value to multiple receivers.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let (mut tx, rx) = laika::shotgun::channel::<u8>();
|
||||||
|
/// // do something with tx and rx
|
||||||
|
/// ```
|
||||||
|
pub fn channel<T>() -> (Sender<T>, Receiver<T>)
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
let mut sender = Sender {
|
||||||
|
inner: _Sender { receiver: None },
|
||||||
|
};
|
||||||
|
|
||||||
|
let receiver_ref = Arc::new(Mutex::new(_Receiver {
|
||||||
|
value: None,
|
||||||
|
wakers: Vec::new(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let receiver = Receiver {
|
||||||
|
inner: receiver_ref.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sender.inner.receiver = Some(receiver_ref);
|
||||||
|
|
||||||
|
(sender, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
|
||||||
|
tx.send(());
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), Some(()));
|
||||||
|
assert_eq!(rx.try_recv(), Some(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_work_without_receiver() {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
|
||||||
|
drop(rx);
|
||||||
|
|
||||||
|
tx.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_work_without_sender() {
|
||||||
|
let (tx, rx) = channel::<()>();
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_work_with_multiple_receivers() {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let rx1 = rx.clone();
|
||||||
|
let rx2 = rx.clone();
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), None);
|
||||||
|
assert_eq!(rx1.try_recv(), None);
|
||||||
|
assert_eq!(rx2.try_recv(), None);
|
||||||
|
|
||||||
|
tx.send(1337);
|
||||||
|
|
||||||
|
assert_eq!(rx.try_recv(), Some(1337));
|
||||||
|
assert_eq!(rx1.try_recv(), Some(1337));
|
||||||
|
assert_eq!(rx2.try_recv(), Some(1337));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_works_in_threads() {
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let rx1 = rx.clone();
|
||||||
|
|
||||||
|
let thread1 = thread::spawn(move || loop {
|
||||||
|
if rx1.try_recv().is_some() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
let rx2 = rx.clone();
|
||||||
|
let thread2 = thread::spawn(move || loop {
|
||||||
|
if rx2.try_recv().is_some() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
tx.send(());
|
||||||
|
|
||||||
|
assert!(thread1.join().is_ok_and(|v| v == 1));
|
||||||
|
assert!(thread2.join().is_ok_and(|v| v == 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_recv() {
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let mut join_set = JoinSet::new();
|
||||||
|
let rx1 = rx.clone();
|
||||||
|
join_set.spawn(async move {
|
||||||
|
rx1.await;
|
||||||
|
1
|
||||||
|
});
|
||||||
|
|
||||||
|
let rx2 = rx.clone();
|
||||||
|
join_set.spawn(async move {
|
||||||
|
rx2.recv().await; // Explicit call to recv
|
||||||
|
2
|
||||||
|
});
|
||||||
|
|
||||||
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
tx.send(());
|
||||||
|
|
||||||
|
let rx3 = rx.clone();
|
||||||
|
let fun3 = async move {
|
||||||
|
rx3.await;
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = join_set.join_all().await;
|
||||||
|
|
||||||
|
assert_eq!(result[0], 1);
|
||||||
|
assert_eq!(result[1], 2);
|
||||||
|
assert_eq!(fun3.await, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user