mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4918adbd8 | ||
|
|
b1310ad14d | ||
|
|
dcac336dc7 | ||
|
|
c39d9867bb | ||
|
|
e4f76688a0 | ||
|
|
e6103d6661 | ||
|
|
cc90a5c679 | ||
|
|
f107c4f49b | ||
|
|
e6a9167bb7 | ||
|
|
345b29ca11 | ||
|
|
b75d02a2b6 | ||
|
|
a1b4bdee61 | ||
|
|
edfff7551a | ||
|
|
1cda0f16a2 | ||
|
|
e40ec3e424 | ||
|
|
6919f7cede | ||
|
|
bcb95db4e2 | ||
|
|
8c5cde9bc3 | ||
|
|
b08b5edb2c | ||
|
|
88863a0c5d | ||
|
|
d0e4dd1d7b | ||
|
|
872bc09e83 | ||
|
|
db1d90453c | ||
|
|
05eeea570e | ||
|
|
f70b9b84f7 | ||
|
|
704de8c01b | ||
|
|
47be928444 | ||
|
|
e06b257e09 | ||
|
|
0867a6fc03 | ||
|
|
0d838bf5ad | ||
|
|
fc23f8a1a5 | ||
|
|
fd93ecf5e0 | ||
|
|
d2ad7afd21 | ||
|
|
5756a005a6 | ||
|
|
017a483b5e | ||
|
|
112e160b62 | ||
|
|
c9d2a36c7b | ||
|
|
8efed43fa7 | ||
|
|
7de18af82c | ||
|
|
52457dcf5b | ||
|
|
53558cb489 | ||
|
|
36d7dab504 | ||
|
|
6fd06aaeec | ||
|
|
36bcfa6b9d | ||
|
|
e3f2dcf5bc | ||
|
|
7c6a1c4637 | ||
|
|
4099bfdef0 | ||
|
|
469b43de6a | ||
|
|
e827829402 | ||
|
|
58bd242831 | ||
|
|
6fd9084d47 |
@@ -82,14 +82,23 @@ jobs:
|
||||
sudo apt-get install -y valgrind
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build
|
||||
- name: cargo build test-mem
|
||||
run: cargo build --features rt-net --bin test-mem
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind
|
||||
- name: Run valgrind test-mem
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build test-process-signal
|
||||
run: cargo build --features rt-process-signal --bin test-process-signal
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-process-signal
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
+12
-3
@@ -124,17 +124,27 @@ arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
When building documentation normally, the markers that list the features
|
||||
required for various parts of Tokio are missing. To build the documentation
|
||||
correctly, use this command:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
There is currently a [bug in cargo] that means documentation cannot be built
|
||||
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
|
||||
command shown above will work.
|
||||
|
||||
[bug in cargo]: https://github.com/rust-lang/cargo/issues/9274
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
@@ -571,9 +581,8 @@ When releasing a new version of a crate, follow these steps:
|
||||
2. **Update Cargo metadata.** After releasing any path dependencies, update the
|
||||
`version` field in `Cargo.toml` to the new version, and the `documentation`
|
||||
field to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the `#![doc(html_root_url)]`
|
||||
attribute in the crate's `lib.rs` and the "Documentation" link in the crate's
|
||||
`README.md` to point to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the "Documentation" link in the
|
||||
crate's `README.md` to point to the docs.rs URL of the new version.
|
||||
4. **Update the changelog for the crate.** Each crate in the Tokio repository
|
||||
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
|
||||
crate since the last release should be added to the changelog. Change
|
||||
|
||||
@@ -11,7 +11,6 @@ tokio = { version = "1.0.0", features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||
tokio-stream = { version = "0.1" }
|
||||
|
||||
async-stream = "0.3"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
bytes = "1.0.0"
|
||||
|
||||
+31
-69
@@ -28,8 +28,8 @@
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
@@ -37,9 +37,7 @@ use std::env;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
@@ -101,6 +99,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
/// Shorthand for the receive half of the message channel.
|
||||
type Rx = mpsc::UnboundedReceiver<String>;
|
||||
|
||||
/// Data that is shared between all peers in the chat server.
|
||||
///
|
||||
/// This is the set of `Tx` handles for all connected clients. Whenever a
|
||||
@@ -124,7 +125,7 @@ struct Peer {
|
||||
///
|
||||
/// This is used to receive messages from peers. When a message is received
|
||||
/// off of this `Rx`, it will be written to the socket.
|
||||
rx: Pin<Box<dyn Stream<Item = String> + Send>>,
|
||||
rx: Rx,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
@@ -156,58 +157,15 @@ impl Peer {
|
||||
let addr = lines.get_ref().peer_addr()?;
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().await.peers.insert(addr, tx);
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Peer { lines, rx })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
/// A message that should be broadcasted to others.
|
||||
Broadcast(String),
|
||||
|
||||
/// A message that should be received by a client
|
||||
Received(String),
|
||||
}
|
||||
|
||||
// Peer implements `Stream` in a way that polls both the `Rx`, and `Framed` types.
|
||||
// A message is produced whenever an event is ready until the `Framed` stream returns `None`.
|
||||
impl Stream for Peer {
|
||||
type Item = Result<Message, LinesCodecError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// First poll the `UnboundedReceiver`.
|
||||
|
||||
if let Poll::Ready(Some(v)) = Pin::new(&mut self.rx).poll_next(cx) {
|
||||
return Poll::Ready(Some(Ok(Message::Received(v))));
|
||||
}
|
||||
|
||||
// Secondly poll the `Framed` stream.
|
||||
let result: Option<_> = futures::ready!(Pin::new(&mut self.lines).poll_next(cx));
|
||||
|
||||
Poll::Ready(match result {
|
||||
// We've received a message we should broadcast to others.
|
||||
Some(Ok(message)) => Some(Ok(Message::Broadcast(message))),
|
||||
|
||||
// An error occurred.
|
||||
Some(Err(e)) => Some(Err(e)),
|
||||
|
||||
// The stream has been exhausted.
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an individual chat client
|
||||
async fn process(
|
||||
state: Arc<Mutex<Shared>>,
|
||||
@@ -241,28 +199,32 @@ async fn process(
|
||||
}
|
||||
|
||||
// Process incoming messages until our stream is exhausted by a disconnect.
|
||||
while let Some(result) = peer.next().await {
|
||||
match result {
|
||||
// A message was received from the current user, we should
|
||||
// broadcast this message to the other users.
|
||||
Ok(Message::Broadcast(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// A message was received from a peer. Send it to the
|
||||
// current user.
|
||||
Ok(Message::Received(msg)) => {
|
||||
loop {
|
||||
tokio::select! {
|
||||
// A message was received from a peer. Send it to the current user.
|
||||
Some(msg) = peer.rx.recv() => {
|
||||
peer.lines.send(&msg).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username,
|
||||
e
|
||||
);
|
||||
}
|
||||
result = peer.lines.next() => match result {
|
||||
// A message was received from the current user, we should
|
||||
// broadcast this message to the other users.
|
||||
Some(Ok(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// An error occurred.
|
||||
Some(Err(e)) => {
|
||||
tracing::error!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username,
|
||||
e
|
||||
);
|
||||
}
|
||||
// The stream has been exhausted.
|
||||
None => break,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,15 @@ name = "test-cat"
|
||||
name = "test-mem"
|
||||
required-features = ["rt-net"]
|
||||
|
||||
[[bin]]
|
||||
name = "test-process-signal"
|
||||
required-features = ["rt-process-signal"]
|
||||
|
||||
[features]
|
||||
# For mem check
|
||||
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
|
||||
# For test-process-signal
|
||||
rt-process-signal = ["rt", "tokio/process", "tokio/signal"]
|
||||
|
||||
full = [
|
||||
"macros",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// https://github.com/tokio-rs/tokio/issues/3550
|
||||
fn main() {
|
||||
for _ in 0..1000 {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
drop(rt);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/1.1.0")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
# 0.1.5 (March 20, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- stream: documentation note for throttle `Unpin` ([#3600])
|
||||
|
||||
[#3600]: https://github.com/tokio-rs/tokio/pull/3600
|
||||
|
||||
# 0.1.4 (March 9, 2021)
|
||||
|
||||
Added
|
||||
|
||||
- signal: add `Signal` wrapper ([#3510])
|
||||
|
||||
Fixed
|
||||
|
||||
- stream: remove duplicate `doc_cfg` declaration ([#3561])
|
||||
- sync: yield initial value in `WatchStream` ([#3576])
|
||||
|
||||
[#3510]: https://github.com/tokio-rs/tokio/pull/3510
|
||||
[#3561]: https://github.com/tokio-rs/tokio/pull/3561
|
||||
[#3576]: https://github.com/tokio-rs/tokio/pull/3576
|
||||
|
||||
# 0.1.3 (February 5, 2021)
|
||||
|
||||
Added
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
name = "tokio-stream"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.3/tokio_stream"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.5/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
@@ -26,15 +25,16 @@ net = ["tokio/net"]
|
||||
io-util = ["tokio/io-util"]
|
||||
fs = ["tokio/fs"]
|
||||
sync = ["tokio/sync", "tokio-util"]
|
||||
signal = ["tokio/signal"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.0", features = ["sync"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", features = ["full", "test-util"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-stream/0.1.3")]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
@@ -10,18 +9,12 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
//! Stream utilities for Tokio.
|
||||
//!
|
||||
|
||||
@@ -48,6 +48,16 @@ macro_rules! cfg_sync {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_signal {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "signal")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "signal")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ready {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
|
||||
@@ -23,7 +23,8 @@ where
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Stream for the [`throttle`](throttle) function.
|
||||
/// Stream for the [`throttle`](throttle) function. This object is `!Unpin`. If you need it to
|
||||
/// implement `Unpin` you can pin your throttle like this: `Box::pin(your_throttle)`.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Throttle<T> {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
//! Wrappers for Tokio types that implement `Stream`.
|
||||
//!
|
||||
#![cfg_attr(
|
||||
unix,
|
||||
doc = "You are viewing documentation built under unix. To view windows-specific wrappers, change to the `x86_64-pc-windows-msvc` platform."
|
||||
)]
|
||||
#![cfg_attr(
|
||||
windows,
|
||||
doc = "You are viewing documentation built under windows. To view unix-specific wrappers, change to the `x86_64-unknown-linux-gnu` platform."
|
||||
)]
|
||||
|
||||
/// Error types for the wrappers.
|
||||
pub mod errors {
|
||||
@@ -21,6 +30,18 @@ cfg_sync! {
|
||||
pub use watch::WatchStream;
|
||||
}
|
||||
|
||||
cfg_signal! {
|
||||
#[cfg(unix)]
|
||||
mod signal_unix;
|
||||
#[cfg(unix)]
|
||||
pub use signal_unix::SignalStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod signal_windows;
|
||||
#[cfg(windows)]
|
||||
pub use signal_windows::{CtrlCStream, CtrlBreakStream};
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
mod interval;
|
||||
pub use interval::IntervalStream;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use crate::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::signal::unix::Signal;
|
||||
|
||||
/// A wrapper around [`Signal`] that implements [`Stream`].
|
||||
///
|
||||
/// [`Signal`]: struct@tokio::signal::unix::Signal
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "signal"))))]
|
||||
pub struct SignalStream {
|
||||
inner: Signal,
|
||||
}
|
||||
|
||||
impl SignalStream {
|
||||
/// Create a new `SignalStream`.
|
||||
pub fn new(interval: Signal) -> Self {
|
||||
Self { inner: interval }
|
||||
}
|
||||
|
||||
/// Get back the inner `Signal`.
|
||||
pub fn into_inner(self) -> Signal {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for SignalStream {
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Signal> for SignalStream {
|
||||
fn as_ref(&self) -> &Signal {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Signal> for SignalStream {
|
||||
fn as_mut(&mut self) -> &mut Signal {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use crate::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::signal::windows::{CtrlBreak, CtrlC};
|
||||
|
||||
/// A wrapper around [`CtrlC`] that implements [`Stream`].
|
||||
///
|
||||
/// [`CtrlC`]: struct@tokio::signal::windows::CtrlC
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(windows, feature = "signal"))))]
|
||||
pub struct CtrlCStream {
|
||||
inner: CtrlC,
|
||||
}
|
||||
|
||||
impl CtrlCStream {
|
||||
/// Create a new `CtrlCStream`.
|
||||
pub fn new(interval: CtrlC) -> Self {
|
||||
Self { inner: interval }
|
||||
}
|
||||
|
||||
/// Get back the inner `CtrlC`.
|
||||
pub fn into_inner(self) -> CtrlC {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for CtrlCStream {
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<CtrlC> for CtrlCStream {
|
||||
fn as_ref(&self) -> &CtrlC {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<CtrlC> for CtrlCStream {
|
||||
fn as_mut(&mut self) -> &mut CtrlC {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`CtrlBreak`] that implements [`Stream`].
|
||||
///
|
||||
/// [`CtrlBreak`]: struct@tokio::signal::windows::CtrlBreak
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(windows, feature = "signal"))))]
|
||||
pub struct CtrlBreakStream {
|
||||
inner: CtrlBreak,
|
||||
}
|
||||
|
||||
impl CtrlBreakStream {
|
||||
/// Create a new `CtrlBreakStream`.
|
||||
pub fn new(interval: CtrlBreak) -> Self {
|
||||
Self { inner: interval }
|
||||
}
|
||||
|
||||
/// Get back the inner `CtrlBreak`.
|
||||
pub fn into_inner(self) -> CtrlBreak {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for CtrlBreakStream {
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<CtrlBreak> for CtrlBreakStream {
|
||||
fn as_ref(&self) -> &CtrlBreak {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<CtrlBreak> for CtrlBreakStream {
|
||||
fn as_mut(&mut self) -> &mut CtrlBreak {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,41 @@ use tokio::sync::watch::error::RecvError;
|
||||
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will always start by yielding the current value when the WatchStream is polled,
|
||||
/// regardles of whether it was the initial value or sent afterwards.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
/// use tokio::sync::watch;
|
||||
///
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::new(rx);
|
||||
///
|
||||
/// assert_eq!(rx.next().await, Some("hello"));
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
/// use tokio::sync::watch;
|
||||
///
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::new(rx);
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::watch::Receiver`]: struct@tokio::sync::watch::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
@@ -28,7 +63,7 @@ impl<T: 'static + Clone + Unpin + Send + Sync> WatchStream<T> {
|
||||
/// Create a new `WatchStream`.
|
||||
pub fn new(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
inner: ReusableBoxFuture::new(async move { (Ok(()), rx) }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.4.1 (March 10, 2021)
|
||||
|
||||
- Fix `io::Mock` to be `Send` and `Sync` ([#3594])
|
||||
|
||||
[#3594]: https://github.com/tokio-rs/tokio/pull/3594
|
||||
|
||||
# 0.4.0 (December 23, 2020)
|
||||
|
||||
- Track `tokio` 1.0 release.
|
||||
|
||||
@@ -2,25 +2,24 @@
|
||||
name = "tokio-test"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.0/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.1/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3"
|
||||
|
||||
@@ -28,7 +27,7 @@ bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use futures_core::{ready, Stream};
|
||||
use std::collections::VecDeque;
|
||||
@@ -69,8 +70,7 @@ struct Inner {
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
read_wait: Option<Waker>,
|
||||
// rx: mpsc::UnboundedReceiver<Action>,
|
||||
rx: Pin<Box<dyn Stream<Item = Action> + Send>>,
|
||||
rx: UnboundedReceiverStream<Action>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -185,13 +185,9 @@ impl Handle {
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
let rx = UnboundedReceiverStream::new(rx);
|
||||
|
||||
let inner = Inner {
|
||||
actions,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.4.0")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
@@ -22,9 +21,9 @@ pub mod task;
|
||||
/// future completes.
|
||||
///
|
||||
/// For more information, see the documentation for
|
||||
/// [`tokio::runtime::current_thread::Runtime::block_on`][runtime-block-on].
|
||||
/// [`tokio::runtime::Runtime::block_on`][runtime-block-on].
|
||||
///
|
||||
/// [runtime-block-on]: https://docs.rs/tokio/0.2.0-alpha.2/tokio/runtime/current_thread/struct.Runtime.html#method.block_on
|
||||
/// [runtime-block-on]: https://docs.rs/tokio/1.3.0/tokio/runtime/struct.Runtime.html#method.block_on
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
# 0.6.5 (March 20, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- util: annotate time module as requiring `time` feature ([#3606])
|
||||
|
||||
[#3606]: https://github.com/tokio-rs/tokio/pull/3606
|
||||
|
||||
# 0.6.4 (March 9, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- codec: `AnyDelimiter` codec ([#3406])
|
||||
- sync: add pollable `mpsc::Sender` ([#3490])
|
||||
|
||||
### Fixed
|
||||
|
||||
- codec: `LinesCodec` should only return `MaxLineLengthExceeded` once per line ([#3556])
|
||||
- sync: fuse PollSemaphore ([#3578])
|
||||
|
||||
[#3406]: https://github.com/tokio-rs/tokio/pull/3406
|
||||
[#3490]: https://github.com/tokio-rs/tokio/pull/3490
|
||||
[#3556]: https://github.com/tokio-rs/tokio/pull/3556
|
||||
[#3578]: https://github.com/tokio-rs/tokio/pull/3578
|
||||
|
||||
# 0.6.3 (January 31, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
name = "tokio-util"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.6.x" git tag.
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.3/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.5/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
|
||||
@@ -47,3 +47,13 @@ macro_rules! cfg_rt {
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_time {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use std::{cmp, fmt, io, str, usize};
|
||||
|
||||
const DEFAULT_SEEK_DELIMITERS: &[u8] = b",;\n\r";
|
||||
const DEFAULT_SEQUENCE_WRITER: &[u8] = b",";
|
||||
/// A simple [`Decoder`] and [`Encoder`] implementation that splits up data into chunks based on any character in the given delimiter string.
|
||||
///
|
||||
/// [`Decoder`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
///
|
||||
/// # Example
|
||||
/// Decode string of bytes containing various different delimiters.
|
||||
///
|
||||
/// [`BytesMut`]: bytes::BytesMut
|
||||
/// [`Error`]: std::io::Error
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::codec::{AnyDelimiterCodec, Decoder};
|
||||
/// use bytes::{BufMut, BytesMut};
|
||||
///
|
||||
/// #
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), std::io::Error> {
|
||||
/// let mut codec = AnyDelimiterCodec::new(b",;\r\n".to_vec(),b";".to_vec());
|
||||
/// let buf = &mut BytesMut::new();
|
||||
/// buf.reserve(200);
|
||||
/// buf.put_slice(b"chunk 1,chunk 2;chunk 3\n\r");
|
||||
/// assert_eq!("chunk 1", codec.decode(buf).unwrap().unwrap());
|
||||
/// assert_eq!("chunk 2", codec.decode(buf).unwrap().unwrap());
|
||||
/// assert_eq!("chunk 3", codec.decode(buf).unwrap().unwrap());
|
||||
/// assert_eq!("", codec.decode(buf).unwrap().unwrap());
|
||||
/// assert_eq!(None, codec.decode(buf).unwrap());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct AnyDelimiterCodec {
|
||||
// Stored index of the next index to examine for the delimiter character.
|
||||
// This is used to optimize searching.
|
||||
// For example, if `decode` was called with `abc` and the delimiter is '{}', it would hold `3`,
|
||||
// because that is the next index to examine.
|
||||
// The next time `decode` is called with `abcde}`, the method will
|
||||
// only look at `de}` before returning.
|
||||
next_index: usize,
|
||||
|
||||
/// The maximum length for a given chunk. If `usize::MAX`, chunks will be
|
||||
/// read until a delimiter character is reached.
|
||||
max_length: usize,
|
||||
|
||||
/// Are we currently discarding the remainder of a chunk which was over
|
||||
/// the length limit?
|
||||
is_discarding: bool,
|
||||
|
||||
/// The bytes that are using for search during decode
|
||||
seek_delimiters: Vec<u8>,
|
||||
|
||||
/// The bytes that are using for encoding
|
||||
sequence_writer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AnyDelimiterCodec {
|
||||
/// Returns a `AnyDelimiterCodec` for splitting up data into chunks.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The returned `AnyDelimiterCodec` will not have an upper bound on the length
|
||||
/// of a buffered chunk. See the documentation for [`new_with_max_length`]
|
||||
/// for information on why this could be a potential security risk.
|
||||
///
|
||||
/// [`new_with_max_length`]: crate::codec::AnyDelimiterCodec::new_with_max_length()
|
||||
pub fn new(seek_delimiters: Vec<u8>, sequence_writer: Vec<u8>) -> AnyDelimiterCodec {
|
||||
AnyDelimiterCodec {
|
||||
next_index: 0,
|
||||
max_length: usize::MAX,
|
||||
is_discarding: false,
|
||||
seek_delimiters,
|
||||
sequence_writer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `AnyDelimiterCodec` with a maximum chunk length limit.
|
||||
///
|
||||
/// If this is set, calls to `AnyDelimiterCodec::decode` will return a
|
||||
/// [`AnyDelimiterCodecError`] when a chunk exceeds the length limit. Subsequent calls
|
||||
/// will discard up to `limit` bytes from that chunk until a delimiter
|
||||
/// character is reached, returning `None` until the delimiter over the limit
|
||||
/// has been fully discarded. After that point, calls to `decode` will
|
||||
/// function as normal.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Setting a length limit is highly recommended for any `AnyDelimiterCodec` which
|
||||
/// will be exposed to untrusted input. Otherwise, the size of the buffer
|
||||
/// that holds the chunk currently being read is unbounded. An attacker could
|
||||
/// exploit this unbounded buffer by sending an unbounded amount of input
|
||||
/// without any delimiter characters, causing unbounded memory consumption.
|
||||
///
|
||||
/// [`AnyDelimiterCodecError`]: crate::codec::AnyDelimiterCodecError
|
||||
pub fn new_with_max_length(
|
||||
seek_delimiters: Vec<u8>,
|
||||
sequence_writer: Vec<u8>,
|
||||
max_length: usize,
|
||||
) -> Self {
|
||||
AnyDelimiterCodec {
|
||||
max_length,
|
||||
..AnyDelimiterCodec::new(seek_delimiters, sequence_writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum chunk length when decoding.
|
||||
///
|
||||
/// ```
|
||||
/// use std::usize;
|
||||
/// use tokio_util::codec::AnyDelimiterCodec;
|
||||
///
|
||||
/// let codec = AnyDelimiterCodec::new(b",;\n".to_vec(), b";".to_vec());
|
||||
/// assert_eq!(codec.max_length(), usize::MAX);
|
||||
/// ```
|
||||
/// ```
|
||||
/// use tokio_util::codec::AnyDelimiterCodec;
|
||||
///
|
||||
/// let codec = AnyDelimiterCodec::new_with_max_length(b",;\n".to_vec(), b";".to_vec(), 256);
|
||||
/// assert_eq!(codec.max_length(), 256);
|
||||
/// ```
|
||||
pub fn max_length(&self) -> usize {
|
||||
self.max_length
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for AnyDelimiterCodec {
|
||||
type Item = Bytes;
|
||||
type Error = AnyDelimiterCodecError;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Bytes>, AnyDelimiterCodecError> {
|
||||
loop {
|
||||
// Determine how far into the buffer we'll search for a delimiter. If
|
||||
// there's no max_length set, we'll read to the end of the buffer.
|
||||
let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
|
||||
|
||||
let new_chunk_offset = buf[self.next_index..read_to].iter().position(|b| {
|
||||
self.seek_delimiters
|
||||
.iter()
|
||||
.any(|delimiter| *b == *delimiter)
|
||||
});
|
||||
|
||||
match (self.is_discarding, new_chunk_offset) {
|
||||
(true, Some(offset)) => {
|
||||
// If we found a new chunk, discard up to that offset and
|
||||
// then stop discarding. On the next iteration, we'll try
|
||||
// to read a chunk normally.
|
||||
buf.advance(offset + self.next_index + 1);
|
||||
self.is_discarding = false;
|
||||
self.next_index = 0;
|
||||
}
|
||||
(true, None) => {
|
||||
// Otherwise, we didn't find a new chunk, so we'll discard
|
||||
// everything we read. On the next iteration, we'll continue
|
||||
// discarding up to max_len bytes unless we find a new chunk.
|
||||
buf.advance(read_to);
|
||||
self.next_index = 0;
|
||||
if buf.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
(false, Some(offset)) => {
|
||||
// Found a chunk!
|
||||
let new_chunk_index = offset + self.next_index;
|
||||
self.next_index = 0;
|
||||
let mut chunk = buf.split_to(new_chunk_index + 1);
|
||||
chunk.truncate(chunk.len() - 1);
|
||||
let chunk = chunk.freeze();
|
||||
return Ok(Some(chunk));
|
||||
}
|
||||
(false, None) if buf.len() > self.max_length => {
|
||||
// Reached the maximum length without finding a
|
||||
// new chunk, return an error and start discarding on the
|
||||
// next call.
|
||||
self.is_discarding = true;
|
||||
return Err(AnyDelimiterCodecError::MaxChunkLengthExceeded);
|
||||
}
|
||||
(false, None) => {
|
||||
// We didn't find a chunk or reach the length limit, so the next
|
||||
// call will resume searching at the current offset.
|
||||
self.next_index = read_to;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Bytes>, AnyDelimiterCodecError> {
|
||||
Ok(match self.decode(buf)? {
|
||||
Some(frame) => Some(frame),
|
||||
None => {
|
||||
// return remaining data, if any
|
||||
if buf.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let chunk = buf.split_to(buf.len());
|
||||
self.next_index = 0;
|
||||
Some(chunk.freeze())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Encoder<T> for AnyDelimiterCodec
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
type Error = AnyDelimiterCodecError;
|
||||
|
||||
fn encode(&mut self, chunk: T, buf: &mut BytesMut) -> Result<(), AnyDelimiterCodecError> {
|
||||
let chunk = chunk.as_ref();
|
||||
buf.reserve(chunk.len() + 1);
|
||||
buf.put(chunk.as_bytes());
|
||||
buf.put(self.sequence_writer.as_ref());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AnyDelimiterCodec {
|
||||
fn default() -> Self {
|
||||
Self::new(
|
||||
DEFAULT_SEEK_DELIMITERS.to_vec(),
|
||||
DEFAULT_SEQUENCE_WRITER.to_vec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a chunk.
|
||||
#[derive(Debug)]
|
||||
pub enum AnyDelimiterCodecError {
|
||||
/// The maximum chunk length was exceeded.
|
||||
MaxChunkLengthExceeded,
|
||||
/// An IO error occurred.
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for AnyDelimiterCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AnyDelimiterCodecError::MaxChunkLengthExceeded => {
|
||||
write!(f, "max chunk length exceeded")
|
||||
}
|
||||
AnyDelimiterCodecError::Io(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for AnyDelimiterCodecError {
|
||||
fn from(e: io::Error) -> AnyDelimiterCodecError {
|
||||
AnyDelimiterCodecError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AnyDelimiterCodecError {}
|
||||
@@ -535,14 +535,14 @@ impl LengthDelimitedCodec {
|
||||
Ok(Some(n))
|
||||
}
|
||||
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> Option<BytesMut> {
|
||||
// At this point, the buffer has already had the required capacity
|
||||
// reserved. All there is to do is read.
|
||||
if src.len() < n {
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
|
||||
Ok(Some(src.split_to(n)))
|
||||
Some(src.split_to(n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ impl Decoder for LengthDelimitedCodec {
|
||||
DecodeState::Data(n) => n,
|
||||
};
|
||||
|
||||
match self.decode_data(n, src)? {
|
||||
match self.decode_data(n, src) {
|
||||
Some(data) => {
|
||||
// Update the decode state
|
||||
self.state = DecodeState::Head;
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Decoder for LinesCodec {
|
||||
buf.advance(read_to);
|
||||
self.next_index = 0;
|
||||
if buf.is_empty() {
|
||||
return Err(LinesCodecError::MaxLineLengthExceeded);
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
(false, Some(offset)) => {
|
||||
|
||||
@@ -285,3 +285,6 @@ pub use self::length_delimited::{LengthDelimitedCodec, LengthDelimitedCodecError
|
||||
|
||||
mod lines_codec;
|
||||
pub use self::lines_codec::{LinesCodec, LinesCodecError};
|
||||
|
||||
mod any_delimiter_codec;
|
||||
pub use self::any_delimiter_codec::{AnyDelimiterCodec, AnyDelimiterCodecError};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.6.3")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -46,13 +45,14 @@ cfg_rt! {
|
||||
pub mod context;
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
pub mod time;
|
||||
}
|
||||
|
||||
pub mod sync;
|
||||
|
||||
pub mod either;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub mod time;
|
||||
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod util {
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
@@ -5,6 +5,9 @@ pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
|
||||
|
||||
mod intrusive_double_linked_list;
|
||||
|
||||
mod mpsc;
|
||||
pub use mpsc::PollSender;
|
||||
|
||||
mod poll_semaphore;
|
||||
pub use poll_semaphore::PollSemaphore;
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::mpsc::{error::SendError, Sender};
|
||||
|
||||
use super::ReusableBoxFuture;
|
||||
|
||||
// This implementation was chosen over something based on permits because to get a
|
||||
// `tokio::sync::mpsc::Permit` out of the `inner` future, you must transmute the
|
||||
// lifetime on the permit to `'static`.
|
||||
|
||||
/// A wrapper around [`mpsc::Sender`] that can be polled.
|
||||
///
|
||||
/// [`mpsc::Sender`]: tokio::sync::mpsc::Sender
|
||||
#[derive(Debug)]
|
||||
pub struct PollSender<T> {
|
||||
/// is none if closed
|
||||
sender: Option<Arc<Sender<T>>>,
|
||||
is_sending: bool,
|
||||
inner: ReusableBoxFuture<Result<(), SendError<T>>>,
|
||||
}
|
||||
|
||||
// By reusing the same async fn for both Some and None, we make sure every
|
||||
// future passed to ReusableBoxFuture has the same underlying type, and hence
|
||||
// the same size and alignment.
|
||||
async fn make_future<T>(data: Option<(Arc<Sender<T>>, T)>) -> Result<(), SendError<T>> {
|
||||
match data {
|
||||
Some((sender, value)) => sender.send(value).await,
|
||||
None => unreachable!(
|
||||
"This future should not be pollable, as is_sending should be set to false."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> PollSender<T> {
|
||||
/// Create a new `PollSender`.
|
||||
pub fn new(sender: Sender<T>) -> Self {
|
||||
Self {
|
||||
sender: Some(Arc::new(sender)),
|
||||
is_sending: false,
|
||||
inner: ReusableBoxFuture::new(make_future(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start sending a new item.
|
||||
///
|
||||
/// This method panics if a send is currently in progress. To ensure that no
|
||||
/// send is in progress, call `poll_send_done` first until it returns
|
||||
/// `Poll::Ready`.
|
||||
///
|
||||
/// If this method returns an error, that indicates that the channel is
|
||||
/// closed. Note that this method is not guaranteed to return an error if
|
||||
/// the channel is closed, but in that case the error would be reported by
|
||||
/// the first call to `poll_send_done`.
|
||||
pub fn start_send(&mut self, value: T) -> Result<(), SendError<T>> {
|
||||
if self.is_sending {
|
||||
panic!("start_send called while not ready.");
|
||||
}
|
||||
match self.sender.clone() {
|
||||
Some(sender) => {
|
||||
self.inner.set(make_future(Some((sender, value))));
|
||||
self.is_sending = true;
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SendError(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If a send is in progress, poll for its completion. If no send is in progress,
|
||||
/// this method returns `Poll::Ready(Ok(()))`.
|
||||
///
|
||||
/// This method can return the following values:
|
||||
///
|
||||
/// - `Poll::Ready(Ok(()))` if the in-progress send has been completed, or there is
|
||||
/// no send in progress (even if the channel is closed).
|
||||
/// - `Poll::Ready(Err(err))` if the in-progress send failed because the channel has
|
||||
/// been closed.
|
||||
/// - `Poll::Pending` if a send is in progress, but it could not complete now.
|
||||
///
|
||||
/// When this method returns `Poll::Pending`, the current task is scheduled
|
||||
/// to receive a wakeup when the message is sent, or when the entire channel
|
||||
/// is closed (but not if just this sender is closed by
|
||||
/// `close_this_sender`). Note that on multiple calls to `poll_send_done`,
|
||||
/// only the `Waker` from the `Context` passed to the most recent call is
|
||||
/// scheduled to receive a wakeup.
|
||||
///
|
||||
/// If this method returns `Poll::Ready`, then `start_send` is guaranteed to
|
||||
/// not panic.
|
||||
pub fn poll_send_done(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), SendError<T>>> {
|
||||
if !self.is_sending {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
let result = self.inner.poll(cx);
|
||||
if result.is_ready() {
|
||||
self.is_sending = false;
|
||||
}
|
||||
if let Poll::Ready(Err(_)) = &result {
|
||||
self.sender = None;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Check whether the channel is ready to send more messages now.
|
||||
///
|
||||
/// If this method returns `true`, then `start_send` is guaranteed to not
|
||||
/// panic.
|
||||
///
|
||||
/// If the channel is closed, this method returns `true`.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
!self.is_sending
|
||||
}
|
||||
|
||||
/// Check whether the channel has been closed.
|
||||
pub fn is_closed(&self) -> bool {
|
||||
match &self.sender {
|
||||
Some(sender) => sender.is_closed(),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the underlying `Sender`.
|
||||
///
|
||||
/// If this method returns `None`, then the channel is closed. (But it is
|
||||
/// not guaranteed to return `None` if the channel is closed.)
|
||||
pub fn clone_inner(&self) -> Option<Sender<T>> {
|
||||
self.sender.as_ref().map(|sender| (&**sender).clone())
|
||||
}
|
||||
|
||||
/// Access the underlying `Sender`.
|
||||
///
|
||||
/// If this method returns `None`, then the channel is closed. (But it is
|
||||
/// not guaranteed to return `None` if the channel is closed.)
|
||||
pub fn inner_ref(&self) -> Option<&Sender<T>> {
|
||||
self.sender.as_deref()
|
||||
}
|
||||
|
||||
// This operation is supported because it is required by the Sink trait.
|
||||
/// Close this sender. No more messages can be sent from this sender.
|
||||
///
|
||||
/// Note that this only closes the channel from the view-point of this
|
||||
/// sender. The channel remains open until all senders have gone away, or
|
||||
/// until the [`Receiver`] closes the channel.
|
||||
///
|
||||
/// If there is a send in progress when this method is called, that send is
|
||||
/// unaffected by this operation, and `poll_send_done` can still be called
|
||||
/// to complete that send.
|
||||
///
|
||||
/// [`Receiver`]: tokio::sync::mpsc::Receiver
|
||||
pub fn close_this_sender(&mut self) {
|
||||
self.sender = None;
|
||||
}
|
||||
|
||||
/// Abort the current in-progress send, if any.
|
||||
///
|
||||
/// Returns `true` if a send was aborted.
|
||||
pub fn abort_send(&mut self) -> bool {
|
||||
if self.is_sending {
|
||||
self.inner.set(make_future(None));
|
||||
self.is_sending = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for PollSender<T> {
|
||||
/// Clones this `PollSender`. The resulting clone will not have any
|
||||
/// in-progress send operations, even if the current `PollSender` does.
|
||||
fn clone(&self) -> PollSender<T> {
|
||||
Self {
|
||||
sender: self.sender.clone(),
|
||||
is_sending: false,
|
||||
inner: ReusableBoxFuture::new(async { unreachable!() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Sink<T> for PollSender<T> {
|
||||
type Error = SendError<T>;
|
||||
|
||||
/// This is equivalent to calling [`poll_send_done`].
|
||||
///
|
||||
/// [`poll_send_done`]: PollSender::poll_send_done
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::into_inner(self).poll_send_done(cx)
|
||||
}
|
||||
|
||||
/// This is equivalent to calling [`poll_send_done`].
|
||||
///
|
||||
/// [`poll_send_done`]: PollSender::poll_send_done
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::into_inner(self).poll_send_done(cx)
|
||||
}
|
||||
|
||||
/// This is equivalent to calling [`start_send`].
|
||||
///
|
||||
/// [`start_send`]: PollSender::start_send
|
||||
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
|
||||
Pin::into_inner(self).start_send(item)
|
||||
}
|
||||
|
||||
/// This method will first flush the `PollSender`, and then close it by
|
||||
/// calling [`close_this_sender`].
|
||||
///
|
||||
/// If a send fails while flushing because the [`Receiver`] has gone away,
|
||||
/// then this function returns an error. The channel is still successfully
|
||||
/// closed in this situation.
|
||||
///
|
||||
/// [`close_this_sender`]: PollSender::close_this_sender
|
||||
/// [`Receiver`]: tokio::sync::mpsc::Receiver
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.as_mut().poll_flush(cx))?;
|
||||
|
||||
Pin::into_inner(self).close_this_sender();
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -55,12 +55,13 @@ impl PollSemaphore {
|
||||
/// the `Waker` from the `Context` passed to the most recent call is
|
||||
/// scheduled to receive a wakeup.
|
||||
pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
|
||||
match ready!(self.permit_fut.poll(cx)) {
|
||||
Ok(permit) => {
|
||||
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
|
||||
self.permit_fut.set(next_fut);
|
||||
Poll::Ready(Some(permit))
|
||||
}
|
||||
let result = ready!(self.permit_fut.poll(cx));
|
||||
|
||||
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
|
||||
self.permit_fut.set(next_fut);
|
||||
|
||||
match result {
|
||||
Ok(permit) => Poll::Ready(Some(permit)),
|
||||
Err(_closed) => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
+245
-1
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio_util::codec::{BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
use tokio_util::codec::{AnyDelimiterCodec, BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
@@ -201,7 +201,26 @@ fn lines_decoder_discard_repeat() {
|
||||
buf.put_slice(b"aa");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"a");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
}
|
||||
|
||||
// Regression test for [subsequent calls to LinesCodec decode does not return the desired results bug](https://github.com/tokio-rs/tokio/issues/3555)
|
||||
#[test]
|
||||
fn lines_decoder_max_length_underrun_twice() {
|
||||
const MAX_LENGTH: usize = 11;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too very l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"aaaaaaaaaaaaaaaaaaaaaaa");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"ong\nshort\n");
|
||||
assert_eq!("short", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -215,3 +234,228 @@ fn lines_encoder() {
|
||||
codec.encode("line 2", &mut buf).unwrap();
|
||||
assert_eq!("line 1\nline 2\n", buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiters_decoder_any_character() {
|
||||
let mut codec = AnyDelimiterCodec::new(b",;\n\r".to_vec(), b",".to_vec());
|
||||
let buf = &mut BytesMut::new();
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk 1,chunk 2;chunk 3\n\r");
|
||||
assert_eq!("chunk 1", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("chunk 2", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("chunk 3", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put_slice(b"k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!("k", codec.decode_eof(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiters_decoder_max_length() {
|
||||
const MAX_LENGTH: usize = 7;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk 1 is too long\nchunk 2\nchunk 3\r\nchunk 4\n\r\n");
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("chunk 2", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("chunk 3", chunk);
|
||||
|
||||
// \r\n cause empty chunk
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("chunk 4", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put_slice(b"k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
let chunk = codec.decode_eof(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("k", chunk);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
|
||||
// Delimiter that's one character too long. This could cause an out of bounds
|
||||
// error if we peek at the next characters using slice indexing.
|
||||
buf.put_slice(b"aaabbbcc");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_decoder_max_length_underrun() {
|
||||
const MAX_LENGTH: usize = 7;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"ong\n");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put_slice(b"chunk 2");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b",");
|
||||
assert_eq!("chunk 2", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_decoder_max_length_underrun_twice() {
|
||||
const MAX_LENGTH: usize = 11;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too very l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"aaaaaaaaaaaaaaaaaaaaaaa");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"ong\nshort\n");
|
||||
assert_eq!("short", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn any_delimiter_decoder_max_length_bursts() {
|
||||
const MAX_LENGTH: usize = 11;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too l");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"ong\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_decoder_max_length_big_burst() {
|
||||
const MAX_LENGTH: usize = 11;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"chunk ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too long!\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_decoder_max_length_delimiter_between_decodes() {
|
||||
const MAX_LENGTH: usize = 5;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"hello");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put_slice(b",world");
|
||||
assert_eq!("hello", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_decoder_discard_repeat() {
|
||||
const MAX_LENGTH: usize = 1;
|
||||
|
||||
let mut codec =
|
||||
AnyDelimiterCodec::new_with_max_length(b",;\n\r".to_vec(), b",".to_vec(), MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"aa");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"a");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_delimiter_encoder() {
|
||||
let mut codec = AnyDelimiterCodec::new(b",".to_vec(), b";--;".to_vec());
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
codec.encode("chunk 1", &mut buf).unwrap();
|
||||
assert_eq!("chunk 1;--;", buf);
|
||||
|
||||
codec.encode("chunk 2", &mut buf).unwrap();
|
||||
assert_eq!("chunk 1;--;chunk 2;--;", buf);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
use futures::future::poll_fn;
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio_test::task::spawn;
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok};
|
||||
use tokio_util::sync::PollSender;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_simple() {
|
||||
let (send, mut recv) = channel(3);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
for i in 1..=3i32 {
|
||||
send.start_send(i).unwrap();
|
||||
assert_ready_ok!(spawn(poll_fn(|cx| send.poll_send_done(cx))).poll());
|
||||
}
|
||||
|
||||
send.start_send(4).unwrap();
|
||||
let mut fourth_send = spawn(poll_fn(|cx| send.poll_send_done(cx)));
|
||||
assert_pending!(fourth_send.poll());
|
||||
assert_eq!(recv.recv().await.unwrap(), 1);
|
||||
assert!(fourth_send.is_woken());
|
||||
assert_ready_ok!(fourth_send.poll());
|
||||
|
||||
drop(recv);
|
||||
|
||||
// Here, start_send is not guaranteed to fail, but if it doesn't the first
|
||||
// call to poll_send_done should.
|
||||
if send.start_send(5).is_ok() {
|
||||
assert_ready_err!(spawn(poll_fn(|cx| send.poll_send_done(cx))).poll());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_abort() {
|
||||
let (send, mut recv) = channel(3);
|
||||
let mut send = PollSender::new(send);
|
||||
let send2 = send.clone_inner().unwrap();
|
||||
|
||||
for i in 1..=3i32 {
|
||||
send.start_send(i).unwrap();
|
||||
assert_ready_ok!(spawn(poll_fn(|cx| send.poll_send_done(cx))).poll());
|
||||
}
|
||||
|
||||
send.start_send(4).unwrap();
|
||||
{
|
||||
let mut fourth_send = spawn(poll_fn(|cx| send.poll_send_done(cx)));
|
||||
assert_pending!(fourth_send.poll());
|
||||
assert_eq!(recv.recv().await.unwrap(), 1);
|
||||
assert!(fourth_send.is_woken());
|
||||
}
|
||||
|
||||
let mut send2_send = spawn(send2.send(5));
|
||||
assert_pending!(send2_send.poll());
|
||||
send.abort_send();
|
||||
assert!(send2_send.is_woken());
|
||||
assert_ready_ok!(send2_send.poll());
|
||||
|
||||
assert_eq!(recv.recv().await.unwrap(), 2);
|
||||
assert_eq!(recv.recv().await.unwrap(), 3);
|
||||
assert_eq!(recv.recv().await.unwrap(), 5);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn close_sender_last() {
|
||||
let (send, mut recv) = channel::<i32>(3);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
send.close_this_sender();
|
||||
|
||||
assert!(recv_task.is_woken());
|
||||
assert!(assert_ready!(recv_task.poll()).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn close_sender_not_last() {
|
||||
let (send, mut recv) = channel::<i32>(3);
|
||||
let send2 = send.clone();
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
send.close_this_sender();
|
||||
|
||||
assert!(!recv_task.is_woken());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
drop(send2);
|
||||
|
||||
assert!(recv_task.is_woken());
|
||||
assert!(assert_ready!(recv_task.poll()).is_none());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
||||
use tokio_util::sync::PollSemaphore;
|
||||
|
||||
type SemRet = Option<OwnedSemaphorePermit>;
|
||||
|
||||
fn semaphore_poll<'a>(
|
||||
sem: &'a mut PollSemaphore,
|
||||
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + 'a> {
|
||||
let fut = futures::future::poll_fn(move |cx| sem.poll_acquire(cx));
|
||||
tokio_test::task::spawn(fut)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_works() {
|
||||
let sem = Arc::new(Semaphore::new(1));
|
||||
let mut poll_sem = PollSemaphore::new(sem.clone());
|
||||
|
||||
let permit = sem.acquire().await.unwrap();
|
||||
let mut poll = semaphore_poll(&mut poll_sem);
|
||||
assert!(poll.poll().is_pending());
|
||||
drop(permit);
|
||||
|
||||
assert!(matches!(poll.poll(), Poll::Ready(Some(_))));
|
||||
drop(poll);
|
||||
|
||||
sem.close();
|
||||
|
||||
assert!(semaphore_poll(&mut poll_sem).await.is_none());
|
||||
|
||||
// Check that it is fused.
|
||||
assert!(semaphore_poll(&mut poll_sem).await.is_none());
|
||||
assert!(semaphore_poll(&mut poll_sem).await.is_none());
|
||||
}
|
||||
@@ -1,3 +1,85 @@
|
||||
# 1.4.0 (March 20, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- macros: introduce biased argument for `select!` ([#3603])
|
||||
- runtime: add `Handle::block_on` ([#3569])
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: avoid unnecessary polling of `block_on` future ([#3582])
|
||||
- runtime: fix memory leak/growth when creating many runtimes ([#3564])
|
||||
- runtime: mark `EnterGuard` with `must_use` ([#3609])
|
||||
|
||||
### Documented
|
||||
|
||||
- chore: mention fix for building docs in contributing guide ([#3618])
|
||||
- doc: add link to `PollSender` ([#3613])
|
||||
- doc: alias sleep to delay ([#3604])
|
||||
- sync: improve `Mutex` FIFO explanation ([#3615])
|
||||
- timer: fix double newline in module docs ([#3617])
|
||||
|
||||
[#3564]: https://github.com/tokio-rs/tokio/pull/3564
|
||||
[#3613]: https://github.com/tokio-rs/tokio/pull/3613
|
||||
[#3618]: https://github.com/tokio-rs/tokio/pull/3618
|
||||
[#3617]: https://github.com/tokio-rs/tokio/pull/3617
|
||||
[#3582]: https://github.com/tokio-rs/tokio/pull/3582
|
||||
[#3615]: https://github.com/tokio-rs/tokio/pull/3615
|
||||
[#3603]: https://github.com/tokio-rs/tokio/pull/3603
|
||||
[#3609]: https://github.com/tokio-rs/tokio/pull/3609
|
||||
[#3604]: https://github.com/tokio-rs/tokio/pull/3604
|
||||
[#3569]: https://github.com/tokio-rs/tokio/pull/3569
|
||||
|
||||
# 1.3.0 (March 9, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- coop: expose an `unconstrained()` opt-out ([#3547])
|
||||
- net: add `into_std` for net types without it ([#3509])
|
||||
- sync: add `same_channel` method to `mpsc::Sender` ([#3532])
|
||||
- sync: add `{try_,}acquire_many_owned` to `Semaphore` ([#3535])
|
||||
- sync: add back `RwLockWriteGuard::map` and `RwLockWriteGuard::try_map` ([#3348])
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: allow `oneshot::Receiver::close` after successful `try_recv` ([#3552])
|
||||
- time: do not panic on `timeout(Duration::MAX)` ([#3551])
|
||||
|
||||
### Documented
|
||||
|
||||
- doc: doc aliases for pre-1.0 function names ([#3523])
|
||||
- io: fix typos ([#3541])
|
||||
- io: note the EOF behaviour of `read_until` ([#3536])
|
||||
- io: update `AsyncRead::poll_read` doc ([#3557])
|
||||
- net: update `UdpSocket` splitting doc ([#3517])
|
||||
- runtime: add link to `LocalSet` on `new_current_thread` ([#3508])
|
||||
- runtime: update documentation of thread limits ([#3527])
|
||||
- sync: do not recommend `join_all` for `Barrier` ([#3514])
|
||||
- sync: documentation for `oneshot` ([#3592])
|
||||
- sync: rename `notify` to `notify_one` ([#3526])
|
||||
- time: fix typo in `Sleep` doc ([#3515])
|
||||
- time: sync `interval.rs` and `time/mod.rs` docs ([#3533])
|
||||
|
||||
[#3348]: https://github.com/tokio-rs/tokio/pull/3348
|
||||
[#3508]: https://github.com/tokio-rs/tokio/pull/3508
|
||||
[#3509]: https://github.com/tokio-rs/tokio/pull/3509
|
||||
[#3514]: https://github.com/tokio-rs/tokio/pull/3514
|
||||
[#3515]: https://github.com/tokio-rs/tokio/pull/3515
|
||||
[#3517]: https://github.com/tokio-rs/tokio/pull/3517
|
||||
[#3523]: https://github.com/tokio-rs/tokio/pull/3523
|
||||
[#3526]: https://github.com/tokio-rs/tokio/pull/3526
|
||||
[#3527]: https://github.com/tokio-rs/tokio/pull/3527
|
||||
[#3532]: https://github.com/tokio-rs/tokio/pull/3532
|
||||
[#3533]: https://github.com/tokio-rs/tokio/pull/3533
|
||||
[#3535]: https://github.com/tokio-rs/tokio/pull/3535
|
||||
[#3536]: https://github.com/tokio-rs/tokio/pull/3536
|
||||
[#3541]: https://github.com/tokio-rs/tokio/pull/3541
|
||||
[#3547]: https://github.com/tokio-rs/tokio/pull/3547
|
||||
[#3551]: https://github.com/tokio-rs/tokio/pull/3551
|
||||
[#3552]: https://github.com/tokio-rs/tokio/pull/3552
|
||||
[#3557]: https://github.com/tokio-rs/tokio/pull/3557
|
||||
[#3592]: https://github.com/tokio-rs/tokio/pull/3592
|
||||
|
||||
# 1.2.0 (February 5, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
+2
-3
@@ -2,18 +2,17 @@
|
||||
name = "tokio"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.0.x" git tag.
|
||||
version = "1.2.0"
|
||||
version = "1.4.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/1.2.0/tokio/"
|
||||
documentation = "https://docs.rs/tokio/1.4.0/tokio/"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
description = """
|
||||
|
||||
+32
-47
@@ -1,55 +1,33 @@
|
||||
#![cfg_attr(not(feature = "full"), allow(dead_code))]
|
||||
|
||||
//! Opt-in yield points for improved cooperative scheduling.
|
||||
//! Yield points for improved cooperative scheduling.
|
||||
//!
|
||||
//! A single call to [`poll`] on a top-level task may potentially do a lot of
|
||||
//! work before it returns `Poll::Pending`. If a task runs for a long period of
|
||||
//! time without yielding back to the executor, it can starve other tasks
|
||||
//! waiting on that executor to execute them, or drive underlying resources.
|
||||
//! Since Rust does not have a runtime, it is difficult to forcibly preempt a
|
||||
//! long-running task. Instead, this module provides an opt-in mechanism for
|
||||
//! futures to collaborate with the executor to avoid starvation.
|
||||
//! Documentation for this can be found in the [`tokio::task`] module.
|
||||
//!
|
||||
//! Consider a future like this one:
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_stream::{Stream, StreamExt};
|
||||
//! async fn drop_all<I: Stream + Unpin>(mut input: I) {
|
||||
//! while let Some(_) = input.next().await {}
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! It may look harmless, but consider what happens under heavy load if the
|
||||
//! input stream is _always_ ready. If we spawn `drop_all`, the task will never
|
||||
//! yield, and will starve other tasks and resources on the same executor. With
|
||||
//! opt-in yield points, this problem is alleviated:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! # use tokio_stream::{Stream, StreamExt};
|
||||
//! async fn drop_all<I: Stream + Unpin>(mut input: I) {
|
||||
//! while let Some(_) = input.next().await {
|
||||
//! tokio::coop::proceed().await;
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The `proceed` future will coordinate with the executor to make sure that
|
||||
//! every so often control is yielded back to the executor so it can run other
|
||||
//! tasks.
|
||||
//!
|
||||
//! # Placing yield points
|
||||
//!
|
||||
//! Voluntary yield points should be placed _after_ at least some work has been
|
||||
//! done. If they are not, a future sufficiently deep in the task hierarchy may
|
||||
//! end up _never_ getting to run because of the number of yield points that
|
||||
//! inevitably appear before it is reached. In general, you will want yield
|
||||
//! points to only appear in "leaf" futures -- those that do not themselves poll
|
||||
//! other futures. By doing this, you avoid double-counting each iteration of
|
||||
//! the outer future against the cooperating budget.
|
||||
//!
|
||||
//! [`poll`]: method@std::future::Future::poll
|
||||
//! [`tokio::task`]: crate::task.
|
||||
|
||||
// NOTE: The doctests in this module are ignored since the whole module is (currently) private.
|
||||
// ```ignore
|
||||
// # use tokio_stream::{Stream, StreamExt};
|
||||
// async fn drop_all<I: Stream + Unpin>(mut input: I) {
|
||||
// while let Some(_) = input.next().await {
|
||||
// tokio::coop::proceed().await;
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The `proceed` future will coordinate with the executor to make sure that
|
||||
// every so often control is yielded back to the executor so it can run other
|
||||
// tasks.
|
||||
//
|
||||
// # Placing yield points
|
||||
//
|
||||
// Voluntary yield points should be placed _after_ at least some work has been
|
||||
// done. If they are not, a future sufficiently deep in the task hierarchy may
|
||||
// end up _never_ getting to run because of the number of yield points that
|
||||
// inevitably appear before it is reached. In general, you will want yield
|
||||
// points to only appear in "leaf" futures -- those that do not themselves poll
|
||||
// other futures. By doing this, you avoid double-counting each iteration of
|
||||
// the outer future against the cooperating budget.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
@@ -98,6 +76,13 @@ pub(crate) fn budget<R>(f: impl FnOnce() -> R) -> R {
|
||||
with_budget(Budget::initial(), f)
|
||||
}
|
||||
|
||||
/// Run the given closure with an unconstrained task budget. When the function returns, the budget
|
||||
/// is reset to the value prior to calling the function.
|
||||
#[inline(always)]
|
||||
pub(crate) fn with_unconstrained<R>(f: impl FnOnce() -> R) -> R {
|
||||
with_budget(Budget::unconstrained(), f)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
|
||||
struct ResetGuard<'a> {
|
||||
|
||||
@@ -519,6 +519,8 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
/// create this `AsyncFdReadyGuard`.
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "with_io"))]
|
||||
pub fn try_io<R>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&AsyncFd<Inner>) -> io::Result<R>,
|
||||
|
||||
@@ -43,9 +43,9 @@ use std::task::{Context, Poll};
|
||||
pub trait AsyncRead {
|
||||
/// Attempts to read from the `AsyncRead` into `buf`.
|
||||
///
|
||||
/// On success, returns `Poll::Ready(Ok(()))` and fills `buf` with data
|
||||
/// read. If no data was read (`buf.filled().is_empty()`) it implies that
|
||||
/// EOF has been reached.
|
||||
/// On success, returns `Poll::Ready(Ok(()))` and places data in the
|
||||
/// unfilled portion of `buf`. If no data was read (`buf.filled().len()` is
|
||||
/// unchanged), it implies that EOF has been reached.
|
||||
///
|
||||
/// If no data is available for reading, the method returns `Poll::Pending`
|
||||
/// and arranges for the current task (via `cx.waker()`) to receive a
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(not(feature = "net"), allow(unreachable_pub))]
|
||||
#![cfg_attr(not(feature = "net"), allow(dead_code, unreachable_pub))]
|
||||
|
||||
use crate::io::driver::Ready;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(feature = "net"), allow(dead_code))]
|
||||
|
||||
use crate::io::driver::{Direction, Handle, Interest, ReadyEvent, ScheduledIo};
|
||||
use crate::util::slab;
|
||||
|
||||
@@ -233,7 +235,10 @@ cfg_io_readiness! {
|
||||
|
||||
crate::future::poll_fn(|cx| {
|
||||
if self.handle.inner().is_none() {
|
||||
return Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "reactor gone")));
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR
|
||||
)));
|
||||
}
|
||||
|
||||
Pin::new(&mut fut).poll(cx).map(Ok)
|
||||
|
||||
@@ -443,7 +443,7 @@ cfg_io_readiness! {
|
||||
// Currently ready!
|
||||
let tick = TICK.unpack(curr) as u8;
|
||||
*state = State::Done;
|
||||
return Poll::Ready(ReadyEvent { ready, tick });
|
||||
return Poll::Ready(ReadyEvent { tick, ready });
|
||||
}
|
||||
|
||||
// Wasn't ready, take the lock (and check again while locked).
|
||||
@@ -462,7 +462,7 @@ cfg_io_readiness! {
|
||||
// Currently ready!
|
||||
let tick = TICK.unpack(curr) as u8;
|
||||
*state = State::Done;
|
||||
return Poll::Ready(ReadyEvent { ready, tick });
|
||||
return Poll::Ready(ReadyEvent { tick, ready });
|
||||
}
|
||||
|
||||
// Not ready even after locked, insert into list...
|
||||
|
||||
@@ -121,6 +121,11 @@ impl<E: Source> PollEvented<E> {
|
||||
}
|
||||
|
||||
/// Returns a reference to the registration
|
||||
#[cfg(any(
|
||||
feature = "net",
|
||||
all(unix, feature = "process"),
|
||||
all(unix, feature = "signal"),
|
||||
))]
|
||||
pub(crate) fn registration(&self) -> &Registration {
|
||||
&self.registration
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ cfg_io_util! {
|
||||
///
|
||||
/// If successful, this function will return the total number of bytes read.
|
||||
///
|
||||
/// If this function returns `Ok(0)`, the stream has reached EOF.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will ignore all instances of [`ErrorKind::Interrupted`] and
|
||||
|
||||
@@ -35,7 +35,7 @@ cfg_io_util! {
|
||||
|
||||
/// Reads bytes from a source.
|
||||
///
|
||||
/// Implemented as an extention trait, adding utility methods to all
|
||||
/// Implemented as an extension trait, adding utility methods to all
|
||||
/// [`AsyncRead`] types. Callers will tend to import this trait instead of
|
||||
/// [`AsyncRead`].
|
||||
///
|
||||
|
||||
@@ -35,7 +35,7 @@ cfg_io_util! {
|
||||
|
||||
/// Writes bytes to a sink.
|
||||
///
|
||||
/// Implemented as an extention trait, adding utility methods to all
|
||||
/// Implemented as an extension trait, adding utility methods to all
|
||||
/// [`AsyncWrite`] types. Callers will tend to import this trait instead of
|
||||
/// [`AsyncWrite`].
|
||||
///
|
||||
|
||||
@@ -77,7 +77,7 @@ cfg_io_util! {
|
||||
|
||||
|
||||
// used by `BufReader` and `BufWriter`
|
||||
// https://github.com/rust-lang/rust/blob/master/src/libstd/sys_common/io.rs#L1
|
||||
// https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1
|
||||
const DEFAULT_BUF_SIZE: usize = 8 * 1024;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio/1.2.0")]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
|
||||
@@ -357,3 +357,21 @@ macro_rules! cfg_coop {
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_not_coop {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(not(any(
|
||||
feature = "fs",
|
||||
feature = "io-std",
|
||||
feature = "net",
|
||||
feature = "process",
|
||||
feature = "rt",
|
||||
feature = "signal",
|
||||
feature = "sync",
|
||||
feature = "time",
|
||||
)))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
+93
-26
@@ -129,8 +129,24 @@
|
||||
///
|
||||
/// ### Fairness
|
||||
///
|
||||
/// `select!` randomly picks a branch to check first. This provides some level
|
||||
/// of fairness when calling `select!` in a loop with branches that are always
|
||||
/// By default, `select!` randomly picks a branch to check first. This provides
|
||||
/// some level of fairness when calling `select!` in a loop with branches that
|
||||
/// are always ready.
|
||||
///
|
||||
/// This behavior can be overridden by adding `biased;` to the beginning of the
|
||||
/// macro usage. See the exmples for details. This will cause `select` to poll
|
||||
/// the futures in the order they appear from top to bottom. There are a few
|
||||
/// reasons you may want this:
|
||||
///
|
||||
/// - The random number generation of `tokio::select!` has a non-zero CPU cost
|
||||
/// - Your futures may interact in a way where known polling order is significant
|
||||
///
|
||||
/// But there is an important caveat to this mode. It becomes your responsibility
|
||||
/// to ensure that the polling order of your futures is fair. If for example you
|
||||
/// are selecting between a stream and a shutdown future, and the stream has a
|
||||
/// huge volume of messages and zero or nearly zero time between them, you should
|
||||
/// place the shutdown future earlier in the `select!` list to ensure that it is
|
||||
/// always polled, and will not be ignored due to the stream being constantly
|
||||
/// ready.
|
||||
///
|
||||
/// # Panics
|
||||
@@ -283,6 +299,45 @@
|
||||
/// assert_eq!(res.1, "second");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Using the `biased;` mode to control polling order.
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut count = 0u8;
|
||||
///
|
||||
/// loop {
|
||||
/// tokio::select! {
|
||||
/// // If you run this example without `biased;`, the polling order is
|
||||
/// // psuedo-random, and the assertions on the value of count will
|
||||
/// // (probably) fail.
|
||||
/// biased;
|
||||
///
|
||||
/// _ = async {}, if count < 1 => {
|
||||
/// count += 1;
|
||||
/// assert_eq!(count, 1);
|
||||
/// }
|
||||
/// _ = async {}, if count < 2 => {
|
||||
/// count += 1;
|
||||
/// assert_eq!(count, 2);
|
||||
/// }
|
||||
/// _ = async {}, if count < 3 => {
|
||||
/// count += 1;
|
||||
/// assert_eq!(count, 3);
|
||||
/// }
|
||||
/// _ = async {}, if count < 4 => {
|
||||
/// count += 1;
|
||||
/// assert_eq!(count, 4);
|
||||
/// }
|
||||
///
|
||||
/// else => {
|
||||
/// break;
|
||||
/// }
|
||||
/// };
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! select {
|
||||
@@ -300,6 +355,10 @@ macro_rules! select {
|
||||
|
||||
// All input is normalized, now transform.
|
||||
(@ {
|
||||
// The index of the future to poll first (in bias mode), or the RNG
|
||||
// expression to use to pick a future to poll first.
|
||||
start=$start:expr;
|
||||
|
||||
// One `_` for each branch in the `select!` macro. Passing this to
|
||||
// `count!` converts $skip to an integer.
|
||||
( $($count:tt)* )
|
||||
@@ -357,9 +416,11 @@ macro_rules! select {
|
||||
// disabled.
|
||||
let mut is_pending = false;
|
||||
|
||||
// Randomly generate a starting point. This makes `select!` a
|
||||
// bit more fair and avoids always polling the first future.
|
||||
let start = $crate::macros::support::thread_rng_n(BRANCHES);
|
||||
// Choose a starting index to begin polling the futures at. In
|
||||
// practice, this will either be a psuedo-randomly generrated
|
||||
// number by default, or the constant 0 if `biased;` is
|
||||
// supplied.
|
||||
let start = $start;
|
||||
|
||||
for i in 0..BRANCHES {
|
||||
let branch;
|
||||
@@ -444,42 +505,48 @@ macro_rules! select {
|
||||
// These rules match a single `select!` branch and normalize it for
|
||||
// processing by the first rule.
|
||||
|
||||
(@ { $($t:tt)* } ) => {
|
||||
(@ { start=$start:expr; $($t:tt)* } ) => {
|
||||
// No `else` branch
|
||||
$crate::select!(@{ $($t)*; panic!("all branches are disabled and there is no else branch") })
|
||||
$crate::select!(@{ start=$start; $($t)*; panic!("all branches are disabled and there is no else branch") })
|
||||
};
|
||||
(@ { $($t:tt)* } else => $else:expr $(,)?) => {
|
||||
$crate::select!(@{ $($t)*; $else })
|
||||
(@ { start=$start:expr; $($t:tt)* } else => $else:expr $(,)?) => {
|
||||
$crate::select!(@{ start=$start; $($t)*; $else })
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:block, $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:block, $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:block, $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:block, $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:block $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:block $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:block $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:block $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:expr ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, })
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:expr ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, })
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:expr ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, })
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:expr ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, })
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:expr, $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr, if $c:expr => $h:expr, $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if $c => $h, } $($r)*)
|
||||
};
|
||||
(@ { ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:expr, $($r:tt)* ) => {
|
||||
$crate::select!(@{ ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
(@ { start=$start:expr; ( $($s:tt)* ) $($t:tt)* } $p:pat = $f:expr => $h:expr, $($r:tt)* ) => {
|
||||
$crate::select!(@{ start=$start; ($($s)* _) $($t)* ($($s)*) $p = $f, if true => $h, } $($r)*)
|
||||
};
|
||||
|
||||
// ===== Entry point =====
|
||||
|
||||
(biased; $p:pat = $($t:tt)* ) => {
|
||||
$crate::select!(@{ start=0; () } $p = $($t)*)
|
||||
};
|
||||
|
||||
( $p:pat = $($t:tt)* ) => {
|
||||
$crate::select!(@{ () } $p = $($t)*)
|
||||
// Randomly generate a starting point. This makes `select!` a bit more
|
||||
// fair and avoids always polling the first future.
|
||||
$crate::select!(@{ start={ $crate::macros::support::thread_rng_n(BRANCHES) }; () } $p = $($t)*)
|
||||
};
|
||||
() => {
|
||||
compile_error!("select! requires at least one branch.")
|
||||
|
||||
@@ -192,7 +192,6 @@ impl TcpListener {
|
||||
/// backing event loop. This allows configuration of options like
|
||||
/// `SO_REUSEPORT`, binding to multiple addresses, etc.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
@@ -221,6 +220,48 @@ impl TcpListener {
|
||||
Ok(TcpListener { io })
|
||||
}
|
||||
|
||||
/// Turn a [`tokio::net::TcpListener`] into a [`std::net::TcpListener`].
|
||||
///
|
||||
/// The returned [`std::net::TcpListener`] will have nonblocking mode set as
|
||||
/// `true`. Use [`set_nonblocking`] to change the blocking mode if needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::error::Error;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let tokio_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
|
||||
/// let std_listener = tokio_listener.into_std()?;
|
||||
/// std_listener.set_nonblocking(false)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::net::TcpListener`]: TcpListener
|
||||
/// [`std::net::TcpListener`]: std::net::TcpListener
|
||||
/// [`set_nonblocking`]: fn@std::net::TcpListener::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::net::TcpListener> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(|raw_fd| unsafe { std::net::TcpListener::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_socket())
|
||||
.map(|raw_socket| unsafe { std::net::TcpListener::from_raw_socket(raw_socket) })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(listener: mio::net::TcpListener) -> io::Result<TcpListener> {
|
||||
let io = PollEvented::new(listener)?;
|
||||
Ok(TcpListener { io })
|
||||
|
||||
@@ -8,11 +8,6 @@ use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
@@ -199,7 +194,7 @@ impl TcpStream {
|
||||
|
||||
/// Turn a [`tokio::net::TcpStream`] into a [`std::net::TcpStream`].
|
||||
///
|
||||
/// The returned [`std::net::TcpStream`] will have `nonblocking mode` set as `true`.
|
||||
/// The returned [`std::net::TcpStream`] will have nonblocking mode set as `true`.
|
||||
/// Use [`set_nonblocking`] to change the blocking mode if needed.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -234,6 +229,7 @@ impl TcpStream {
|
||||
pub fn into_std(self) -> io::Result<std::net::TcpStream> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
@@ -242,6 +238,7 @@ impl TcpStream {
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_socket())
|
||||
@@ -932,11 +929,13 @@ impl TcpStream {
|
||||
fn to_mio(&self) -> mio::net::TcpSocket {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{AsRawSocket, FromRawSocket};
|
||||
unsafe { mio::net::TcpSocket::from_raw_socket(self.as_raw_socket()) }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
unsafe { mio::net::TcpSocket::from_raw_fd(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
+48
-4
@@ -23,10 +23,12 @@ cfg_net! {
|
||||
/// and [`recv`](`UdpSocket::recv`) to communicate only with that remote address
|
||||
///
|
||||
/// This type does not provide a `split` method, because this functionality
|
||||
/// can be achieved by wrapping the socket in an [`Arc`]. Note that you do
|
||||
/// not need a `Mutex` to share the `UdpSocket` — an `Arc<UdpSocket>` is
|
||||
/// enough. This is because all of the methods take `&self` instead of `&mut
|
||||
/// self`.
|
||||
/// can be achieved by instead wrapping the socket in an [`Arc`]. Note that
|
||||
/// you do not need a `Mutex` to share the `UdpSocket` — an `Arc<UdpSocket>`
|
||||
/// is enough. This is because all of the methods take `&self` instead of
|
||||
/// `&mut self`. Once you have wrapped it in an `Arc`, you can call
|
||||
/// `.clone()` on the `Arc<UdpSocket>` to get multiple shared handles to the
|
||||
/// same socket. An example of such usage can be found further down.
|
||||
///
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
///
|
||||
@@ -209,6 +211,48 @@ impl UdpSocket {
|
||||
UdpSocket::new(io)
|
||||
}
|
||||
|
||||
/// Turn a [`tokio::net::UdpSocket`] into a [`std::net::UdpSocket`].
|
||||
///
|
||||
/// The returned [`std::net::UdpSocket`] will have nonblocking mode set as
|
||||
/// `true`. Use [`set_nonblocking`] to change the blocking mode if needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::error::Error;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let tokio_socket = tokio::net::UdpSocket::bind("127.0.0.1:0").await?;
|
||||
/// let std_socket = tokio_socket.into_std()?;
|
||||
/// std_socket.set_nonblocking(false)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::net::UdpSocket`]: UdpSocket
|
||||
/// [`std::net::UdpSocket`]: std::net::UdpSocket
|
||||
/// [`set_nonblocking`]: fn@std::net::UdpSocket::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::net::UdpSocket> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(|raw_fd| unsafe { std::net::UdpSocket::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_socket())
|
||||
.map(|raw_socket| unsafe { std::net::UdpSocket::from_raw_socket(raw_socket) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the local address that this socket is bound to.
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -376,6 +376,36 @@ impl UnixDatagram {
|
||||
Ok(UnixDatagram { io })
|
||||
}
|
||||
|
||||
/// Turn a [`tokio::net::UnixDatagram`] into a [`std::os::unix::net::UnixDatagram`].
|
||||
///
|
||||
/// The returned [`std::os::unix::net::UnixDatagram`] will have nonblocking
|
||||
/// mode set as `true`. Use [`set_nonblocking`] to change the blocking mode
|
||||
/// if needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::error::Error;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let tokio_socket = tokio::net::UnixDatagram::bind("127.0.0.1:0")?;
|
||||
/// let std_socket = tokio_socket.into_std()?;
|
||||
/// std_socket.set_nonblocking(false)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::net::UnixDatagram`]: UnixDatagram
|
||||
/// [`std::os::unix::net::UnixDatagram`]: std::os::unix::net::UnixDatagram
|
||||
/// [`set_nonblocking`]: fn@std::os::unix::net::UnixDatagram::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixDatagram> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(|raw_fd| unsafe { std::os::unix::net::UnixDatagram::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
fn new(socket: mio::net::UnixDatagram) -> io::Result<UnixDatagram> {
|
||||
let io = PollEvented::new(socket)?;
|
||||
Ok(UnixDatagram { io })
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::net::unix::{SocketAddr, UnixStream};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -88,6 +88,35 @@ impl UnixListener {
|
||||
Ok(UnixListener { io })
|
||||
}
|
||||
|
||||
/// Turn a [`tokio::net::UnixListener`] into a [`std::os::unix::net::UnixListener`].
|
||||
///
|
||||
/// The returned [`std::os::unix::net::UnixListener`] will have nonblocking mode
|
||||
/// set as `true`. Use [`set_nonblocking`] to change the blocking mode if needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::error::Error;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let tokio_listener = tokio::net::UnixListener::bind("127.0.0.1:0")?;
|
||||
/// let std_listener = tokio_listener.into_std()?;
|
||||
/// std_listener.set_nonblocking(false)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::net::UnixListener`]: UnixListener
|
||||
/// [`std::os::unix::net::UnixListener`]: std::os::unix::net::UnixListener
|
||||
/// [`set_nonblocking`]: fn@std::os::unix::net::UnixListener::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixListener> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(|raw_fd| unsafe { net::UnixListener::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
/// Returns the local socket address of this listener.
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.io.local_addr().map(SocketAddr)
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
@@ -508,6 +508,51 @@ impl UnixStream {
|
||||
Ok(UnixStream { io })
|
||||
}
|
||||
|
||||
/// Turn a [`tokio::net::UnixStream`] into a [`std::os::unix::net::UnixStream`].
|
||||
///
|
||||
/// The returned [`std::os::unix::net::UnixStream`] will have nonblocking
|
||||
/// mode set as `true`. Use [`set_nonblocking`] to change the blocking
|
||||
/// mode if needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::error::Error;
|
||||
/// use std::io::Read;
|
||||
/// use tokio::net::UnixListener;
|
||||
/// # use tokio::net::UnixStream;
|
||||
/// # use tokio::io::AsyncWriteExt;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let dir = tempfile::tempdir().unwrap();
|
||||
/// let bind_path = dir.path().join("bind_path");
|
||||
///
|
||||
/// let mut data = [0u8; 12];
|
||||
/// let listener = UnixListener::bind(&bind_path)?;
|
||||
/// # let handle = tokio::spawn(async {
|
||||
/// # let mut stream = UnixStream::connect(bind_path).await.unwrap();
|
||||
/// # stream.write(b"Hello world!").await.unwrap();
|
||||
/// # });
|
||||
/// let (tokio_unix_stream, _) = listener.accept().await?;
|
||||
/// let mut std_unix_stream = tokio_unix_stream.into_std()?;
|
||||
/// # handle.await.expect("The task being joined has panicked");
|
||||
/// std_unix_stream.set_nonblocking(false)?;
|
||||
/// std_unix_stream.read_exact(&mut data)?;
|
||||
/// # assert_eq!(b"Hello world!", &data);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// [`tokio::net::UnixStream`]: UnixStream
|
||||
/// [`std::os::unix::net::UnixStream`]: std::os::unix::net::UnixStream
|
||||
/// [`set_nonblocking`]: fn@std::os::unix::net::UnixStream::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixStream> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(|raw_fd| unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
/// Creates an unnamed pair of connected sockets.
|
||||
///
|
||||
/// This function will create a pair of interconnected Unix sockets for
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::park::Park;
|
||||
use crate::process::unix::orphan::ReapOrphanQueue;
|
||||
use crate::process::unix::GlobalOrphanQueue;
|
||||
use crate::signal::unix::driver::Driver as SignalDriver;
|
||||
use crate::signal::unix::{signal_with_handle, InternalStream, Signal, SignalKind};
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
use crate::signal::unix::{signal_with_handle, SignalKind};
|
||||
use crate::sync::watch;
|
||||
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
@@ -16,7 +16,7 @@ use std::time::Duration;
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Driver {
|
||||
park: SignalDriver,
|
||||
inner: CoreDriver<Signal, GlobalOrphanQueue>,
|
||||
inner: CoreDriver<watch::Receiver<()>, GlobalOrphanQueue>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -25,27 +25,25 @@ struct CoreDriver<S, Q> {
|
||||
orphan_queue: Q,
|
||||
}
|
||||
|
||||
trait HasChanged {
|
||||
fn has_changed(&mut self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> HasChanged for watch::Receiver<T> {
|
||||
fn has_changed(&mut self) -> bool {
|
||||
self.try_has_changed().and_then(Result::ok).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CoreDriver =====
|
||||
|
||||
impl<S, Q> CoreDriver<S, Q>
|
||||
where
|
||||
S: InternalStream,
|
||||
S: HasChanged,
|
||||
Q: ReapOrphanQueue,
|
||||
{
|
||||
fn got_signal(&mut self) -> bool {
|
||||
match self.sigchild.try_recv() {
|
||||
Ok(()) => true,
|
||||
Err(TryRecvError::Empty) => false,
|
||||
Err(TryRecvError::Closed) => panic!("signal was deregistered"),
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self) {
|
||||
if self.got_signal() {
|
||||
// Drain all notifications which may have been buffered
|
||||
// so we can try to reap all orphans in one batch
|
||||
while self.got_signal() {}
|
||||
|
||||
if self.sigchild.has_changed() {
|
||||
self.orphan_queue.reap_orphans();
|
||||
}
|
||||
}
|
||||
@@ -97,8 +95,6 @@ impl Park for Driver {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::process::unix::orphan::test::MockQueue;
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct MockStream {
|
||||
total_try_recv: usize,
|
||||
@@ -114,17 +110,10 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalStream for MockStream {
|
||||
fn poll_recv(&mut self, _cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn try_recv(&mut self) -> Result<(), TryRecvError> {
|
||||
impl HasChanged for MockStream {
|
||||
fn has_changed(&mut self) -> bool {
|
||||
self.total_try_recv += 1;
|
||||
match self.values.remove(0) {
|
||||
Some(()) => Ok(()),
|
||||
None => Err(TryRecvError::Empty),
|
||||
}
|
||||
self.values.remove(0).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,17 +129,4 @@ mod test {
|
||||
assert_eq!(1, driver.sigchild.total_try_recv);
|
||||
assert_eq!(0, driver.orphan_queue.total_reaps.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coalesce_signals_before_reaping() {
|
||||
let mut driver = CoreDriver {
|
||||
sigchild: MockStream::new(vec![Some(()), Some(()), None]),
|
||||
orphan_queue: MockQueue::<()>::new(),
|
||||
};
|
||||
|
||||
driver.process();
|
||||
|
||||
assert_eq!(3, driver.sigchild.total_try_recv);
|
||||
assert_eq!(1, driver.orphan_queue.total_reaps.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::task::Poll;
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Reaper<W, Q, S>
|
||||
where
|
||||
W: Wait + Unpin,
|
||||
W: Wait,
|
||||
Q: OrphanQueue<W>,
|
||||
{
|
||||
inner: Option<W>,
|
||||
@@ -25,7 +25,7 @@ where
|
||||
|
||||
impl<W, Q, S> Deref for Reaper<W, Q, S>
|
||||
where
|
||||
W: Wait + Unpin,
|
||||
W: Wait,
|
||||
Q: OrphanQueue<W>,
|
||||
{
|
||||
type Target = W;
|
||||
@@ -37,7 +37,7 @@ where
|
||||
|
||||
impl<W, Q, S> Reaper<W, Q, S>
|
||||
where
|
||||
W: Wait + Unpin,
|
||||
W: Wait,
|
||||
Q: OrphanQueue<W>,
|
||||
{
|
||||
pub(crate) fn new(inner: W, orphan_queue: Q, signal: S) -> Self {
|
||||
@@ -61,7 +61,7 @@ impl<W, Q, S> Future for Reaper<W, Q, S>
|
||||
where
|
||||
W: Wait + Unpin,
|
||||
Q: OrphanQueue<W> + Unpin,
|
||||
S: InternalStream,
|
||||
S: InternalStream + Unpin,
|
||||
{
|
||||
type Output = io::Result<ExitStatus>;
|
||||
|
||||
@@ -106,7 +106,7 @@ where
|
||||
|
||||
impl<W, Q, S> Kill for Reaper<W, Q, S>
|
||||
where
|
||||
W: Kill + Wait + Unpin,
|
||||
W: Kill + Wait,
|
||||
Q: OrphanQueue<W>,
|
||||
{
|
||||
fn kill(&mut self) -> io::Result<()> {
|
||||
@@ -116,7 +116,7 @@ where
|
||||
|
||||
impl<W, Q, S> Drop for Reaper<W, Q, S>
|
||||
where
|
||||
W: Wait + Unpin,
|
||||
W: Wait,
|
||||
Q: OrphanQueue<W>,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
@@ -134,7 +134,6 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
use crate::process::unix::orphan::test::MockQueue;
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
use futures::future::FutureExt;
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::process::ExitStatus;
|
||||
@@ -206,10 +205,6 @@ mod test {
|
||||
None => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_recv(&mut self) -> Result<(), TryRecvError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::future::poll_fn;
|
||||
use crate::loom::sync::atomic::AtomicBool;
|
||||
use crate::loom::sync::Mutex;
|
||||
use crate::park::{Park, Unpark};
|
||||
use crate::runtime::task::{self, JoinHandle, Schedule, Task};
|
||||
@@ -10,6 +11,7 @@ use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire, Release};
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll::{Pending, Ready};
|
||||
use std::time::Duration;
|
||||
@@ -70,6 +72,9 @@ struct Shared {
|
||||
|
||||
/// Unpark the blocked thread
|
||||
unpark: Box<dyn Unpark>,
|
||||
|
||||
// indicates whether the blocked on thread was woken
|
||||
woken: AtomicBool,
|
||||
}
|
||||
|
||||
/// Thread-local context.
|
||||
@@ -85,6 +90,9 @@ struct Context {
|
||||
const INITIAL_CAPACITY: usize = 64;
|
||||
|
||||
/// Max number of tasks to poll per tick.
|
||||
#[cfg(loom)]
|
||||
const MAX_TASKS_PER_TICK: usize = 4;
|
||||
#[cfg(not(loom))]
|
||||
const MAX_TASKS_PER_TICK: usize = 61;
|
||||
|
||||
/// How often to check the remote queue first.
|
||||
@@ -101,6 +109,7 @@ impl<P: Park> BasicScheduler<P> {
|
||||
shared: Arc::new(Shared {
|
||||
queue: Mutex::new(VecDeque::with_capacity(INITIAL_CAPACITY)),
|
||||
unpark: unpark as Box<dyn Unpark>,
|
||||
woken: AtomicBool::new(false),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -177,12 +186,16 @@ impl<P: Park> Inner<P> {
|
||||
let _enter = crate::runtime::enter(false);
|
||||
let waker = scheduler.spawner.waker_ref();
|
||||
let mut cx = std::task::Context::from_waker(&waker);
|
||||
let mut polled = false;
|
||||
|
||||
pin!(future);
|
||||
|
||||
'outer: loop {
|
||||
if let Ready(v) = crate::coop::budget(|| future.as_mut().poll(&mut cx)) {
|
||||
return v;
|
||||
if scheduler.spawner.was_woken() || !polled {
|
||||
polled = true;
|
||||
if let Ready(v) = crate::coop::budget(|| future.as_mut().poll(&mut cx)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..MAX_TASKS_PER_TICK {
|
||||
@@ -329,8 +342,14 @@ impl Spawner {
|
||||
}
|
||||
|
||||
fn waker_ref(&self) -> WakerRef<'_> {
|
||||
// clear the woken bit
|
||||
self.shared.woken.swap(false, AcqRel);
|
||||
waker_ref(&self.shared)
|
||||
}
|
||||
|
||||
fn was_woken(&self) -> bool {
|
||||
self.shared.woken.load(Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Spawner {
|
||||
@@ -384,6 +403,7 @@ impl Wake for Shared {
|
||||
|
||||
/// Wake by reference
|
||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||
arc_self.woken.store(true, Release);
|
||||
arc_self.unpark.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ impl Builder {
|
||||
/// Returns a new builder with the current thread scheduler selected.
|
||||
///
|
||||
/// Configuration methods can be chained on the return value.
|
||||
///
|
||||
/// To spawn non-`Send` tasks on the resulting runtime, combine it with a
|
||||
/// [`LocalSet`].
|
||||
///
|
||||
/// [`LocalSet`]: crate::task::LocalSet
|
||||
pub fn new_current_thread() -> Builder {
|
||||
Builder::new(Kind::CurrentThread)
|
||||
}
|
||||
@@ -162,8 +167,8 @@ impl Builder {
|
||||
|
||||
/// Sets the number of worker threads the `Runtime` will use.
|
||||
///
|
||||
/// This should be a number between 0 and 32,768 though it is advised to
|
||||
/// keep this value on the smaller side.
|
||||
/// This can be any number above 0 though it is advised to keep this value
|
||||
/// on the smaller side.
|
||||
///
|
||||
/// # Default
|
||||
///
|
||||
@@ -215,19 +220,28 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifies limit for threads spawned by the Runtime used for blocking operations.
|
||||
/// Specifies the limit for additional threads spawned by the Runtime.
|
||||
///
|
||||
///
|
||||
/// Similarly to the `worker_threads`, this number should be between 1 and 32,768.
|
||||
/// These threads are used for blocking operations like tasks spawned
|
||||
/// through [`spawn_blocking`]. Unlike the [`worker_threads`], they are not
|
||||
/// always active and will exit if left idle for too long. You can change
|
||||
/// this timeout duration with [`thread_keep_alive`].
|
||||
///
|
||||
/// The default value is 512.
|
||||
///
|
||||
/// Otherwise as `worker_threads` are always active, it limits additional threads (e.g. for
|
||||
/// blocking annotations).
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic if `val` is not larger than `0`.
|
||||
///
|
||||
/// # Upgrading from 0.x
|
||||
///
|
||||
/// In old versions `max_threads` limited both blocking and worker threads, but the
|
||||
/// current `max_blocking_threads` does not include async worker threads in the count.
|
||||
///
|
||||
/// [`spawn_blocking`]: fn@crate::task::spawn_blocking
|
||||
/// [`worker_threads`]: Self::worker_threads
|
||||
/// [`thread_keep_alive`]: Self::thread_keep_alive
|
||||
#[cfg_attr(docsrs, doc(alias = "max_threads"))]
|
||||
pub fn max_blocking_threads(&mut self, val: usize) -> &mut Self {
|
||||
assert!(val > 0, "Max blocking threads cannot be set to 0");
|
||||
self.max_blocking_threads = val;
|
||||
|
||||
@@ -40,20 +40,14 @@ cfg_time! {
|
||||
|
||||
cfg_test_util! {
|
||||
pub(crate) fn clock() -> Option<crate::runtime::driver::Clock> {
|
||||
CONTEXT.with(|ctx| match *ctx.borrow() {
|
||||
Some(ref ctx) => Some(ctx.clock.clone()),
|
||||
None => None,
|
||||
})
|
||||
CONTEXT.with(|ctx| (*ctx.borrow()).as_ref().map(|ctx| ctx.clock.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_rt! {
|
||||
pub(crate) fn spawn_handle() -> Option<crate::runtime::Spawner> {
|
||||
CONTEXT.with(|ctx| match *ctx.borrow() {
|
||||
Some(ref ctx) => Some(ctx.spawner.clone()),
|
||||
None => None,
|
||||
})
|
||||
CONTEXT.with(|ctx| (*ctx.borrow()).as_ref().map(|ctx| ctx.spawner.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct Handle {
|
||||
///
|
||||
/// [`Runtime::enter`]: fn@crate::runtime::Runtime::enter
|
||||
#[derive(Debug)]
|
||||
#[must_use = "Creating and dropping a guard does nothing"]
|
||||
pub struct EnterGuard<'a> {
|
||||
handle: &'a Handle,
|
||||
guard: context::EnterGuard,
|
||||
@@ -201,6 +202,93 @@ impl Handle {
|
||||
let _ = self.blocking_spawner.spawn(task, &self);
|
||||
handle
|
||||
}
|
||||
|
||||
/// Run a future to completion on this `Handle`'s associated `Runtime`.
|
||||
///
|
||||
/// This runs the given future on the runtime, blocking until it is
|
||||
/// complete, and yielding its resolved result. Any tasks or timers which
|
||||
/// the future spawns internally will be executed on the runtime.
|
||||
///
|
||||
/// When this is used on a `current_thread` runtime, only the
|
||||
/// [`Runtime::block_on`] method can drive the IO and timer drivers, but the
|
||||
/// `Handle::block_on` method cannot drive them. This means that, when using
|
||||
/// this method on a current_thread runtime, anything that relies on IO or
|
||||
/// timers will not work unless there is another thread currently calling
|
||||
/// [`Runtime::block_on`] on the same runtime.
|
||||
///
|
||||
/// # If the runtime has been shut down
|
||||
///
|
||||
/// If the `Handle`'s associated `Runtime` has been shut down (through
|
||||
/// [`Runtime::shutdown_background`], [`Runtime::shutdown_timeout`], or by
|
||||
/// dropping it) and `Handle::block_on` is used it might return an error or
|
||||
/// panic. Specifically IO resources will return an error and timers will
|
||||
/// panic. Runtime independent futures will run as normal.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the provided future panics, if called within an
|
||||
/// asynchronous execution context, or if a timer future is executed on a
|
||||
/// runtime that has been shut down.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime::Runtime;
|
||||
///
|
||||
/// // Create the runtime
|
||||
/// let rt = Runtime::new().unwrap();
|
||||
///
|
||||
/// // Get a handle from this runtime
|
||||
/// let handle = rt.handle();
|
||||
///
|
||||
/// // Execute the future, blocking the current thread until completion
|
||||
/// handle.block_on(async {
|
||||
/// println!("hello");
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Or using `Handle::current`:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime::Handle;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main () {
|
||||
/// let handle = Handle::current();
|
||||
/// std::thread::spawn(move || {
|
||||
/// // Using Handle::block_on to run async code in the new thread.
|
||||
/// handle.block_on(async {
|
||||
/// println!("hello");
|
||||
/// });
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`JoinError`]: struct@crate::task::JoinError
|
||||
/// [`JoinHandle`]: struct@crate::task::JoinHandle
|
||||
/// [`Runtime::block_on`]: fn@crate::runtime::Runtime::block_on
|
||||
/// [`Runtime::shutdown_background`]: fn@crate::runtime::Runtime::shutdown_background
|
||||
/// [`Runtime::shutdown_timeout`]: fn@crate::runtime::Runtime::shutdown_timeout
|
||||
/// [`spawn_blocking`]: crate::task::spawn_blocking
|
||||
/// [`tokio::fs`]: crate::fs
|
||||
/// [`tokio::net`]: crate::net
|
||||
/// [`tokio::time`]: crate::time
|
||||
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
|
||||
// Enter the **runtime** context. This configures spawning, the current I/O driver, ...
|
||||
let _rt_enter = self.enter();
|
||||
|
||||
// Enter a **blocking** context. This prevents blocking from a runtime.
|
||||
let mut blocking_enter = crate::runtime::enter(true);
|
||||
|
||||
// Block on the future
|
||||
blocking_enter
|
||||
.block_on(future)
|
||||
.expect("failed to park thread")
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(mut self) {
|
||||
self.spawner.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `try_current` when no Runtime has been started
|
||||
|
||||
@@ -526,7 +526,7 @@ cfg_rt! {
|
||||
/// ```
|
||||
pub fn shutdown_timeout(mut self, duration: Duration) {
|
||||
// Wakeup and shutdown all the worker threads
|
||||
self.handle.spawner.shutdown();
|
||||
self.handle.shutdown();
|
||||
self.blocking_pool.shutdown(Some(duration));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::Arc;
|
||||
use crate::loom::thread;
|
||||
use crate::runtime::{Builder, Runtime};
|
||||
use crate::sync::oneshot::{self, Receiver};
|
||||
use crate::task;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::Ordering::{Acquire, Release};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
fn assert_at_most_num_polls(rt: Arc<Runtime>, at_most_polls: usize) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let num_polls = Arc::new(AtomicUsize::new(0));
|
||||
rt.spawn(async move {
|
||||
for _ in 0..12 {
|
||||
task::yield_now().await;
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
BlockedFuture {
|
||||
rx,
|
||||
num_polls: num_polls.clone(),
|
||||
}
|
||||
.await;
|
||||
});
|
||||
|
||||
let polls = num_polls.load(Acquire);
|
||||
assert!(polls <= at_most_polls);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_on_num_polls() {
|
||||
loom::model(|| {
|
||||
// we expect at most 3 number of polls because there are
|
||||
// three points at which we poll the future. At any of these
|
||||
// points it can be ready:
|
||||
//
|
||||
// - when we fail to steal the parker and we block on a
|
||||
// notification that it is available.
|
||||
//
|
||||
// - when we steal the parker and we schedule the future
|
||||
//
|
||||
// - when the future is woken up and we have ran the max
|
||||
// number of tasks for the current tick or there are no
|
||||
// more tasks to run.
|
||||
//
|
||||
let at_most = 3;
|
||||
|
||||
let rt1 = Arc::new(Builder::new_current_thread().build().unwrap());
|
||||
let rt2 = rt1.clone();
|
||||
let rt3 = rt1.clone();
|
||||
|
||||
let th1 = thread::spawn(move || assert_at_most_num_polls(rt1, at_most));
|
||||
let th2 = thread::spawn(move || assert_at_most_num_polls(rt2, at_most));
|
||||
let th3 = thread::spawn(move || assert_at_most_num_polls(rt3, at_most));
|
||||
|
||||
th1.join().unwrap();
|
||||
th2.join().unwrap();
|
||||
th3.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
struct BlockedFuture {
|
||||
rx: Receiver<()>,
|
||||
num_polls: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Future for BlockedFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.num_polls.fetch_add(1, Release);
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
_ => Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
cfg_loom! {
|
||||
mod loom_basic_scheduler;
|
||||
mod loom_blocking;
|
||||
mod loom_oneshot;
|
||||
mod loom_pool;
|
||||
|
||||
@@ -827,6 +827,6 @@ impl Shared {
|
||||
}
|
||||
|
||||
fn ptr_eq(&self, other: &Shared) -> bool {
|
||||
self as *const _ == other as *const _
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
use crate::sync::watch::Receiver;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
mod ctrl_c;
|
||||
pub use ctrl_c::ctrl_c;
|
||||
@@ -58,3 +60,41 @@ mod os {
|
||||
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
|
||||
mod reusable_box;
|
||||
use self::reusable_box::ReusableBoxFuture;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RxFuture {
|
||||
inner: ReusableBoxFuture<Receiver<()>>,
|
||||
}
|
||||
|
||||
async fn make_future(mut rx: Receiver<()>) -> Receiver<()> {
|
||||
match rx.changed().await {
|
||||
Ok(()) => rx,
|
||||
Err(_) => panic!("signal sender went away"),
|
||||
}
|
||||
}
|
||||
|
||||
impl RxFuture {
|
||||
fn new(rx: Receiver<()>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv(&mut self) -> Option<()> {
|
||||
use crate::future::poll_fn;
|
||||
poll_fn(|cx| self.poll_recv(cx)).await
|
||||
}
|
||||
|
||||
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
match self.inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(rx) => {
|
||||
self.inner.set(make_future(rx));
|
||||
Poll::Ready(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,32 @@
|
||||
|
||||
use crate::signal::os::{OsExtraData, OsStorage};
|
||||
|
||||
use crate::sync::mpsc::Sender;
|
||||
use crate::sync::watch;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ops;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub(crate) type EventId = usize;
|
||||
|
||||
/// State for a specific event, whether a notification is pending delivery,
|
||||
/// and what listeners are registered.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EventInfo {
|
||||
pending: AtomicBool,
|
||||
recipients: Mutex<Vec<Sender<()>>>,
|
||||
tx: watch::Sender<()>,
|
||||
}
|
||||
|
||||
impl Default for EventInfo {
|
||||
fn default() -> Self {
|
||||
let (tx, _rx) = watch::channel(());
|
||||
|
||||
Self {
|
||||
pending: AtomicBool::new(false),
|
||||
tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for retrieving the `EventInfo` for a particular eventId.
|
||||
@@ -67,14 +77,12 @@ impl<S> Registry<S> {
|
||||
|
||||
impl<S: Storage> Registry<S> {
|
||||
/// Registers a new listener for `event_id`.
|
||||
fn register_listener(&self, event_id: EventId, listener: Sender<()>) {
|
||||
fn register_listener(&self, event_id: EventId) -> watch::Receiver<()> {
|
||||
self.storage
|
||||
.event_info(event_id)
|
||||
.unwrap_or_else(|| panic!("invalid event_id: {}", event_id))
|
||||
.recipients
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(listener);
|
||||
.tx
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/// Marks `event_id` as having been delivered, without broadcasting it to
|
||||
@@ -89,8 +97,6 @@ impl<S: Storage> Registry<S> {
|
||||
///
|
||||
/// Returns `true` if an event was delivered to at least one listener.
|
||||
fn broadcast(&self) -> bool {
|
||||
use crate::sync::mpsc::error::TrySendError;
|
||||
|
||||
let mut did_notify = false;
|
||||
self.storage.for_each(|event_info| {
|
||||
// Any signal of this kind arrived since we checked last?
|
||||
@@ -98,23 +104,9 @@ impl<S: Storage> Registry<S> {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut recipients = event_info.recipients.lock().unwrap();
|
||||
|
||||
// Notify all waiters on this signal that the signal has been
|
||||
// received. If we can't push a message into the queue then we don't
|
||||
// worry about it as everything is coalesced anyway. If the channel
|
||||
// has gone away then we can remove that slot.
|
||||
for i in (0..recipients.len()).rev() {
|
||||
match recipients[i].try_send(()) {
|
||||
Ok(()) => did_notify = true,
|
||||
Err(TrySendError::Closed(..)) => {
|
||||
recipients.swap_remove(i);
|
||||
}
|
||||
|
||||
// Channel is full, ignore the error since the
|
||||
// receiver has already been woken up
|
||||
Err(_) => {}
|
||||
}
|
||||
// Ignore errors if there are no listeners
|
||||
if event_info.tx.send(()).is_ok() {
|
||||
did_notify = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -137,8 +129,8 @@ impl ops::Deref for Globals {
|
||||
|
||||
impl Globals {
|
||||
/// Registers a new listener for `event_id`.
|
||||
pub(crate) fn register_listener(&self, event_id: EventId, listener: Sender<()>) {
|
||||
self.registry.register_listener(event_id, listener);
|
||||
pub(crate) fn register_listener(&self, event_id: EventId) -> watch::Receiver<()> {
|
||||
self.registry.register_listener(event_id)
|
||||
}
|
||||
|
||||
/// Marks `event_id` as having been delivered, without broadcasting it to
|
||||
@@ -179,7 +171,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::{self, Runtime};
|
||||
use crate::sync::{mpsc, oneshot};
|
||||
use crate::sync::{oneshot, watch};
|
||||
|
||||
use futures::future;
|
||||
|
||||
@@ -193,13 +185,9 @@ mod tests {
|
||||
EventInfo::default(),
|
||||
]);
|
||||
|
||||
let (first_tx, first_rx) = mpsc::channel(3);
|
||||
let (second_tx, second_rx) = mpsc::channel(3);
|
||||
let (third_tx, third_rx) = mpsc::channel(3);
|
||||
|
||||
registry.register_listener(0, first_tx);
|
||||
registry.register_listener(1, second_tx);
|
||||
registry.register_listener(2, third_tx);
|
||||
let first = registry.register_listener(0);
|
||||
let second = registry.register_listener(1);
|
||||
let third = registry.register_listener(2);
|
||||
|
||||
let (fire, wait) = oneshot::channel();
|
||||
|
||||
@@ -213,6 +201,9 @@ mod tests {
|
||||
registry.record_event(1);
|
||||
registry.broadcast();
|
||||
|
||||
// Yield so the previous broadcast can get received
|
||||
crate::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
// Send subsequent signal
|
||||
registry.record_event(0);
|
||||
registry.broadcast();
|
||||
@@ -221,7 +212,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let _ = fire.send(());
|
||||
let all = future::join3(collect(first_rx), collect(second_rx), collect(third_rx));
|
||||
let all = future::join3(collect(first), collect(second), collect(third));
|
||||
|
||||
let (first_results, second_results, third_results) = all.await;
|
||||
assert_eq!(2, first_results.len());
|
||||
@@ -235,8 +226,7 @@ mod tests {
|
||||
fn register_panics_on_invalid_input() {
|
||||
let registry = Registry::new(vec![EventInfo::default()]);
|
||||
|
||||
let (tx, _) = mpsc::channel(1);
|
||||
registry.register_listener(1, tx);
|
||||
registry.register_listener(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -245,74 +235,37 @@ mod tests {
|
||||
registry.record_event(42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broadcast_cleans_up_disconnected_listeners() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let registry = Registry::new(vec![EventInfo::default()]);
|
||||
|
||||
let (first_tx, first_rx) = mpsc::channel(1);
|
||||
let (second_tx, second_rx) = mpsc::channel(1);
|
||||
let (third_tx, third_rx) = mpsc::channel(1);
|
||||
|
||||
registry.register_listener(0, first_tx);
|
||||
registry.register_listener(0, second_tx);
|
||||
registry.register_listener(0, third_tx);
|
||||
|
||||
drop(first_rx);
|
||||
drop(second_rx);
|
||||
|
||||
let (fire, wait) = oneshot::channel();
|
||||
|
||||
crate::spawn(async {
|
||||
wait.await.expect("wait failed");
|
||||
|
||||
registry.record_event(0);
|
||||
registry.broadcast();
|
||||
|
||||
assert_eq!(1, registry.storage[0].recipients.lock().unwrap().len());
|
||||
drop(registry);
|
||||
});
|
||||
|
||||
let _ = fire.send(());
|
||||
let results = collect(third_rx).await;
|
||||
|
||||
assert_eq!(1, results.len());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broadcast_returns_if_at_least_one_event_fired() {
|
||||
let registry = Registry::new(vec![EventInfo::default()]);
|
||||
let registry = Registry::new(vec![EventInfo::default(), EventInfo::default()]);
|
||||
|
||||
registry.record_event(0);
|
||||
assert_eq!(false, registry.broadcast());
|
||||
|
||||
let (first_tx, first_rx) = mpsc::channel(1);
|
||||
let (second_tx, second_rx) = mpsc::channel(1);
|
||||
|
||||
registry.register_listener(0, first_tx);
|
||||
registry.register_listener(0, second_tx);
|
||||
let first = registry.register_listener(0);
|
||||
let second = registry.register_listener(1);
|
||||
|
||||
registry.record_event(0);
|
||||
assert_eq!(true, registry.broadcast());
|
||||
|
||||
drop(first_rx);
|
||||
drop(first);
|
||||
registry.record_event(0);
|
||||
assert_eq!(false, registry.broadcast());
|
||||
|
||||
drop(second_rx);
|
||||
drop(second);
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn collect(mut rx: crate::sync::mpsc::Receiver<()>) -> Vec<()> {
|
||||
async fn collect(mut rx: watch::Receiver<()>) -> Vec<()> {
|
||||
let mut ret = vec![];
|
||||
|
||||
while let Some(v) = rx.recv().await {
|
||||
while let Ok(v) = rx.changed().await {
|
||||
ret.push(v);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
use std::alloc::Layout;
|
||||
use std::future::Future;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, panic};
|
||||
|
||||
/// A reusable `Pin<Box<dyn Future<Output = T> + Send>>`.
|
||||
///
|
||||
/// This type lets you replace the future stored in the box without
|
||||
/// reallocating when the size and alignment permits this.
|
||||
pub(crate) struct ReusableBoxFuture<T> {
|
||||
boxed: NonNull<dyn Future<Output = T> + Send>,
|
||||
}
|
||||
|
||||
impl<T> ReusableBoxFuture<T> {
|
||||
/// Create a new `ReusableBoxFuture<T>` containing the provided future.
|
||||
pub(crate) fn new<F>(future: F) -> Self
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
let boxed: Box<dyn Future<Output = T> + Send> = Box::new(future);
|
||||
|
||||
let boxed = Box::into_raw(boxed);
|
||||
|
||||
// SAFETY: Box::into_raw does not return null pointers.
|
||||
let boxed = unsafe { NonNull::new_unchecked(boxed) };
|
||||
|
||||
Self { boxed }
|
||||
}
|
||||
|
||||
/// Replace the future currently stored in this box.
|
||||
///
|
||||
/// This reallocates if and only if the layout of the provided future is
|
||||
/// different from the layout of the currently stored future.
|
||||
pub(crate) fn set<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
if let Err(future) = self.try_set(future) {
|
||||
*self = Self::new(future);
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the future currently stored in this box.
|
||||
///
|
||||
/// This function never reallocates, but returns an error if the provided
|
||||
/// future has a different size or alignment from the currently stored
|
||||
/// future.
|
||||
pub(crate) fn try_set<F>(&mut self, future: F) -> Result<(), F>
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
// SAFETY: The pointer is not dangling.
|
||||
let self_layout = {
|
||||
let dyn_future: &(dyn Future<Output = T> + Send) = unsafe { self.boxed.as_ref() };
|
||||
Layout::for_value(dyn_future)
|
||||
};
|
||||
|
||||
if Layout::new::<F>() == self_layout {
|
||||
// SAFETY: We just checked that the layout of F is correct.
|
||||
unsafe {
|
||||
self.set_same_layout(future);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(future)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current future.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function requires that the layout of the provided future is the
|
||||
/// same as `self.layout`.
|
||||
unsafe fn set_same_layout<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
// Drop the existing future, catching any panics.
|
||||
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
ptr::drop_in_place(self.boxed.as_ptr());
|
||||
}));
|
||||
|
||||
// Overwrite the future behind the pointer. This is safe because the
|
||||
// allocation was allocated with the same size and alignment as the type F.
|
||||
let self_ptr: *mut F = self.boxed.as_ptr() as *mut F;
|
||||
ptr::write(self_ptr, future);
|
||||
|
||||
// Update the vtable of self.boxed. The pointer is not null because we
|
||||
// just got it from self.boxed, which is not null.
|
||||
self.boxed = NonNull::new_unchecked(self_ptr);
|
||||
|
||||
// If the old future's destructor panicked, resume unwinding.
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Err(payload) => {
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a pinned reference to the underlying future.
|
||||
pub(crate) fn get_pin(&mut self) -> Pin<&mut (dyn Future<Output = T> + Send)> {
|
||||
// SAFETY: The user of this box cannot move the box, and we do not move it
|
||||
// either.
|
||||
unsafe { Pin::new_unchecked(self.boxed.as_mut()) }
|
||||
}
|
||||
|
||||
/// Poll the future stored inside this box.
|
||||
pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<T> {
|
||||
self.get_pin().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for ReusableBoxFuture<T> {
|
||||
type Output = T;
|
||||
|
||||
/// Poll the future stored inside this box.
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
Pin::into_inner(self).get_pin().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// The future stored inside ReusableBoxFuture<T> must be Send.
|
||||
unsafe impl<T> Send for ReusableBoxFuture<T> {}
|
||||
|
||||
// The only method called on self.boxed is poll, which takes &mut self, so this
|
||||
// struct being Sync does not permit any invalid access to the Future, even if
|
||||
// the future is not Sync.
|
||||
unsafe impl<T> Sync for ReusableBoxFuture<T> {}
|
||||
|
||||
// Just like a Pin<Box<dyn Future>> is always Unpin, so is this type.
|
||||
impl<T> Unpin for ReusableBoxFuture<T> {}
|
||||
|
||||
impl<T> Drop for ReusableBoxFuture<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
drop(Box::from_raw(self.boxed.as_ptr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for ReusableBoxFuture<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ReusableBoxFuture").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ReusableBoxFuture;
|
||||
use futures::future::FutureExt;
|
||||
use std::alloc::Layout;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[test]
|
||||
fn test_different_futures() {
|
||||
let fut = async move { 10 };
|
||||
// Not zero sized!
|
||||
assert_eq!(Layout::for_value(&fut).size(), 1);
|
||||
|
||||
let mut b = ReusableBoxFuture::new(fut);
|
||||
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(10));
|
||||
|
||||
b.try_set(async move { 20 })
|
||||
.unwrap_or_else(|_| panic!("incorrect size"));
|
||||
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(20));
|
||||
|
||||
b.try_set(async move { 30 })
|
||||
.unwrap_or_else(|_| panic!("incorrect size"));
|
||||
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_different_sizes() {
|
||||
let fut1 = async move { 10 };
|
||||
let val = [0u32; 1000];
|
||||
let fut2 = async move { val[0] };
|
||||
let fut3 = ZeroSizedFuture {};
|
||||
|
||||
assert_eq!(Layout::for_value(&fut1).size(), 1);
|
||||
assert_eq!(Layout::for_value(&fut2).size(), 4004);
|
||||
assert_eq!(Layout::for_value(&fut3).size(), 0);
|
||||
|
||||
let mut b = ReusableBoxFuture::new(fut1);
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(10));
|
||||
b.set(fut2);
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(0));
|
||||
b.set(fut3);
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(5));
|
||||
}
|
||||
|
||||
struct ZeroSizedFuture {}
|
||||
impl Future for ZeroSizedFuture {
|
||||
type Output = u32;
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<u32> {
|
||||
Poll::Ready(5)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_sized() {
|
||||
let fut = ZeroSizedFuture {};
|
||||
// Zero sized!
|
||||
assert_eq!(Layout::for_value(&fut).size(), 0);
|
||||
|
||||
let mut b = ReusableBoxFuture::new(fut);
|
||||
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(5));
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(5));
|
||||
|
||||
b.try_set(ZeroSizedFuture {})
|
||||
.unwrap_or_else(|_| panic!("incorrect size"));
|
||||
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(5));
|
||||
assert_eq!(b.get_pin().now_or_never(), Some(5));
|
||||
}
|
||||
}
|
||||
+25
-31
@@ -6,8 +6,8 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
use crate::signal::registry::{globals, EventId, EventInfo, Globals, Init, Storage};
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
use crate::sync::mpsc::{channel, Receiver};
|
||||
use crate::signal::RxFuture;
|
||||
use crate::sync::watch;
|
||||
|
||||
use libc::c_int;
|
||||
use mio::net::UnixStream;
|
||||
@@ -222,7 +222,8 @@ fn action(globals: Pin<&'static Globals>, signal: c_int) {
|
||||
///
|
||||
/// This will register the signal handler if it hasn't already been registered,
|
||||
/// returning any error along the way if that fails.
|
||||
fn signal_enable(signal: c_int, handle: Handle) -> io::Result<()> {
|
||||
fn signal_enable(signal: SignalKind, handle: Handle) -> io::Result<()> {
|
||||
let signal = signal.0;
|
||||
if signal < 0 || signal_hook_registry::FORBIDDEN.contains(&signal) {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
@@ -325,7 +326,7 @@ fn signal_enable(signal: c_int, handle: Handle) -> io::Result<()> {
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub struct Signal {
|
||||
rx: Receiver<()>,
|
||||
inner: RxFuture,
|
||||
}
|
||||
|
||||
/// Creates a new stream which will receive notifications when the current
|
||||
@@ -351,21 +352,21 @@ pub struct Signal {
|
||||
/// * If the signal is one of
|
||||
/// [`signal_hook::FORBIDDEN`](fn@signal_hook_registry::register#panics)
|
||||
pub fn signal(kind: SignalKind) -> io::Result<Signal> {
|
||||
signal_with_handle(kind, Handle::current())
|
||||
let rx = signal_with_handle(kind, Handle::current())?;
|
||||
|
||||
Ok(Signal {
|
||||
inner: RxFuture::new(rx),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn signal_with_handle(kind: SignalKind, handle: Handle) -> io::Result<Signal> {
|
||||
let signal = kind.0;
|
||||
|
||||
pub(crate) fn signal_with_handle(
|
||||
kind: SignalKind,
|
||||
handle: Handle,
|
||||
) -> io::Result<watch::Receiver<()>> {
|
||||
// Turn the signal delivery on once we are ready for it
|
||||
signal_enable(signal, handle)?;
|
||||
signal_enable(kind, handle)?;
|
||||
|
||||
// One wakeup in a queue is enough, no need for us to buffer up any
|
||||
// more.
|
||||
let (tx, rx) = channel(1);
|
||||
globals().register_listener(signal as EventId, tx);
|
||||
|
||||
Ok(Signal { rx })
|
||||
Ok(globals().register_listener(kind.0 as EventId))
|
||||
}
|
||||
|
||||
impl Signal {
|
||||
@@ -393,8 +394,7 @@ impl Signal {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv(&mut self) -> Option<()> {
|
||||
use crate::future::poll_fn;
|
||||
poll_fn(|cx| self.poll_recv(cx)).await
|
||||
self.inner.recv().await
|
||||
}
|
||||
|
||||
/// Polls to receive the next signal notification event, outside of an
|
||||
@@ -432,29 +432,19 @@ impl Signal {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.rx.poll_recv(cx)
|
||||
}
|
||||
|
||||
/// Try to receive a signal notification without blocking or registering a waker.
|
||||
pub(crate) fn try_recv(&mut self) -> Result<(), TryRecvError> {
|
||||
self.rx.try_recv()
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Work around for abstracting streams internally
|
||||
pub(crate) trait InternalStream: Unpin {
|
||||
pub(crate) trait InternalStream {
|
||||
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>>;
|
||||
fn try_recv(&mut self) -> Result<(), TryRecvError>;
|
||||
}
|
||||
|
||||
impl InternalStream for Signal {
|
||||
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.poll_recv(cx)
|
||||
}
|
||||
|
||||
fn try_recv(&mut self) -> Result<(), TryRecvError> {
|
||||
self.try_recv()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ctrl_c() -> io::Result<Signal> {
|
||||
@@ -467,11 +457,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn signal_enable_error_on_invalid_input() {
|
||||
signal_enable(-1, Handle::default()).unwrap_err();
|
||||
signal_enable(SignalKind::from_raw(-1), Handle::default()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_enable_error_on_forbidden_input() {
|
||||
signal_enable(signal_hook_registry::FORBIDDEN[0], Handle::default()).unwrap_err();
|
||||
signal_enable(
|
||||
SignalKind::from_raw(signal_hook_registry::FORBIDDEN[0]),
|
||||
Handle::default(),
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-14
@@ -8,7 +8,7 @@
|
||||
#![cfg(windows)]
|
||||
|
||||
use crate::signal::registry::{globals, EventId, EventInfo, Init, Storage};
|
||||
use crate::sync::mpsc::{channel, Receiver};
|
||||
use crate::signal::RxFuture;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
@@ -76,22 +76,18 @@ impl Init for OsExtraData {
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Event {
|
||||
rx: Receiver<()>,
|
||||
inner: RxFuture,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn new(signum: DWORD) -> io::Result<Self> {
|
||||
global_init()?;
|
||||
|
||||
let (tx, rx) = channel(1);
|
||||
globals().register_listener(signum as EventId, tx);
|
||||
let rx = globals().register_listener(signum as EventId);
|
||||
|
||||
Ok(Event { rx })
|
||||
}
|
||||
|
||||
pub(crate) async fn recv(&mut self) -> Option<()> {
|
||||
use crate::future::poll_fn;
|
||||
poll_fn(|cx| self.rx.poll_recv(cx)).await
|
||||
Ok(Self {
|
||||
inner: RxFuture::new(rx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +191,7 @@ impl CtrlC {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv(&mut self) -> Option<()> {
|
||||
self.inner.recv().await
|
||||
self.inner.inner.recv().await
|
||||
}
|
||||
|
||||
/// Polls to receive the next signal notification event, outside of an
|
||||
@@ -227,7 +223,7 @@ impl CtrlC {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.inner.rx.poll_recv(cx)
|
||||
self.inner.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +263,7 @@ impl CtrlBreak {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv(&mut self) -> Option<()> {
|
||||
self.inner.recv().await
|
||||
self.inner.inner.recv().await
|
||||
}
|
||||
|
||||
/// Polls to receive the next signal notification event, outside of an
|
||||
@@ -299,7 +295,7 @@ impl CtrlBreak {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
self.inner.rx.poll_recv(cx)
|
||||
self.inner.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ use std::sync::Mutex;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::sync::Barrier;
|
||||
///
|
||||
/// use futures::future::join_all;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// let mut handles = Vec::with_capacity(10);
|
||||
@@ -18,17 +16,25 @@ use std::sync::Mutex;
|
||||
/// let c = barrier.clone();
|
||||
/// // The same messages will be printed together.
|
||||
/// // You will NOT see any interleaving.
|
||||
/// handles.push(async move {
|
||||
/// handles.push(tokio::spawn(async move {
|
||||
/// println!("before wait");
|
||||
/// let wr = c.wait().await;
|
||||
/// let wait_result = c.wait().await;
|
||||
/// println!("after wait");
|
||||
/// wr
|
||||
/// });
|
||||
/// wait_result
|
||||
/// }));
|
||||
/// }
|
||||
/// // Will not resolve until all "before wait" messages have been printed
|
||||
/// let wrs = join_all(handles).await;
|
||||
///
|
||||
/// // Will not resolve until all "after wait" messages have been printed
|
||||
/// let mut num_leaders = 0;
|
||||
/// for handle in handles {
|
||||
/// let wait_result = handle.await.unwrap();
|
||||
/// if wait_result.is_leader() {
|
||||
/// num_leaders += 1;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Exactly one barrier will resolve as the "leader"
|
||||
/// assert_eq!(wrs.into_iter().filter(|wr| wr.is_leader()).count(), 1);
|
||||
/// assert_eq!(num_leaders, 1);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
|
||||
+10
-13
@@ -450,7 +450,10 @@ cfg_sync! {
|
||||
pub use semaphore::{Semaphore, SemaphorePermit, OwnedSemaphorePermit};
|
||||
|
||||
mod rwlock;
|
||||
pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
pub use rwlock::RwLock;
|
||||
pub use rwlock::read_guard::RwLockReadGuard;
|
||||
pub use rwlock::write_guard::RwLockWriteGuard;
|
||||
pub use rwlock::write_guard_mapped::RwLockMappedWriteGuard;
|
||||
|
||||
mod task;
|
||||
pub(crate) use task::AtomicWaker;
|
||||
@@ -459,10 +462,8 @@ cfg_sync! {
|
||||
}
|
||||
|
||||
cfg_not_sync! {
|
||||
#[cfg(any(feature = "fs", feature = "signal", all(unix, feature = "process")))]
|
||||
pub(crate) mod batch_semaphore;
|
||||
|
||||
cfg_fs! {
|
||||
pub(crate) mod batch_semaphore;
|
||||
mod mutex;
|
||||
pub(crate) use mutex::Mutex;
|
||||
}
|
||||
@@ -470,20 +471,16 @@ cfg_not_sync! {
|
||||
#[cfg(any(feature = "rt", feature = "signal", all(unix, feature = "process")))]
|
||||
pub(crate) mod notify;
|
||||
|
||||
#[cfg(any(feature = "rt", all(windows, feature = "process")))]
|
||||
pub(crate) mod oneshot;
|
||||
|
||||
cfg_atomic_waker_impl! {
|
||||
mod task;
|
||||
pub(crate) use task::AtomicWaker;
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "rt",
|
||||
feature = "process",
|
||||
feature = "signal"))]
|
||||
pub(crate) mod oneshot;
|
||||
|
||||
cfg_signal_internal! {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
#[cfg(any(feature = "signal", all(unix, feature = "process")))]
|
||||
pub(crate) mod watch;
|
||||
}
|
||||
|
||||
/// Unit tests
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use crate::sync::batch_semaphore::{self as semaphore, TryAcquireError};
|
||||
use crate::sync::mpsc::chan;
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(feature = "signal", feature = "process"))]
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
use crate::sync::mpsc::error::{SendError, TrySendError};
|
||||
|
||||
cfg_time! {
|
||||
@@ -16,6 +13,11 @@ use std::task::{Context, Poll};
|
||||
/// Send values to the associated `Receiver`.
|
||||
///
|
||||
/// Instances are created by the [`channel`](channel) function.
|
||||
///
|
||||
/// To use the `Sender` in a poll function, you can use the [`PollSender`]
|
||||
/// utility.
|
||||
///
|
||||
/// [`PollSender`]: https://docs.rs/tokio-util/0.6/tokio_util/sync/struct.PollSender.html
|
||||
pub struct Sender<T> {
|
||||
chan: chan::Tx<T, Semaphore>,
|
||||
}
|
||||
@@ -219,23 +221,6 @@ impl<T> Receiver<T> {
|
||||
crate::future::block_on(self.recv())
|
||||
}
|
||||
|
||||
/// Attempts to return a pending value on this receiver without blocking.
|
||||
///
|
||||
/// This method will never block the caller in order to wait for data to
|
||||
/// become available. Instead, this will always return immediately with
|
||||
/// a possible option of pending data on the channel.
|
||||
///
|
||||
/// This is useful for a flavor of "optimistic check" before deciding to
|
||||
/// block on a receiver.
|
||||
///
|
||||
/// Compared with recv, this function has two failure cases instead of
|
||||
/// one (one for disconnection, one for an empty buffer).
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(feature = "signal", feature = "process"))]
|
||||
pub(crate) fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
||||
self.chan.try_recv()
|
||||
}
|
||||
|
||||
/// Closes the receiving half of a channel without dropping it.
|
||||
///
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
@@ -698,6 +683,22 @@ impl<T> Sender<T> {
|
||||
|
||||
Ok(Permit { chan: &self.chan })
|
||||
}
|
||||
|
||||
/// Returns `true` if senders belong to the same channel.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let (tx, rx) = tokio::sync::mpsc::channel::<()>(1);
|
||||
/// let tx2 = tx.clone();
|
||||
/// assert!(tx.same_channel(&tx2));
|
||||
///
|
||||
/// let (tx3, rx3) = tokio::sync::mpsc::channel::<()>(1);
|
||||
/// assert!(!tx3.same_channel(&tx2));
|
||||
/// ```
|
||||
pub fn same_channel(&self, other: &Self) -> bool {
|
||||
self.chan.same_channel(&other.chan)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Sender<T> {
|
||||
|
||||
@@ -139,6 +139,11 @@ impl<T, S> Tx<T, S> {
|
||||
pub(crate) fn wake_rx(&self) {
|
||||
self.inner.rx_waker.wake();
|
||||
}
|
||||
|
||||
/// Returns `true` if senders belong to the same channel.
|
||||
pub(crate) fn same_channel(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Semaphore> Tx<T, S> {
|
||||
@@ -260,30 +265,6 @@ impl<T, S: Semaphore> Rx<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
feature! {
|
||||
#![all(unix, any(feature = "signal", feature = "process"))]
|
||||
|
||||
use crate::sync::mpsc::error::TryRecvError;
|
||||
|
||||
impl<T, S: Semaphore> Rx<T, S> {
|
||||
/// Receives the next value without blocking
|
||||
pub(crate) fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
||||
use super::block::Read::*;
|
||||
self.inner.rx_fields.with_mut(|rx_fields_ptr| {
|
||||
let rx_fields = unsafe { &mut *rx_fields_ptr };
|
||||
match rx_fields.list.pop(&self.inner.tx) {
|
||||
Some(Value(value)) => {
|
||||
self.inner.semaphore.add_permit();
|
||||
Ok(value)
|
||||
}
|
||||
Some(Closed) => Err(TryRecvError::Closed),
|
||||
None => Err(TryRecvError::Empty),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Semaphore> Drop for Rx<T, S> {
|
||||
fn drop(&mut self) {
|
||||
use super::block::Read::Value;
|
||||
|
||||
@@ -65,39 +65,6 @@ impl fmt::Display for RecvError {
|
||||
|
||||
impl Error for RecvError {}
|
||||
|
||||
// ===== TryRecvError =====
|
||||
|
||||
feature! {
|
||||
#![all(unix, any(feature = "signal", feature = "process"))]
|
||||
|
||||
/// This enumeration is the list of the possible reasons that try_recv
|
||||
/// could not return data when called.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum TryRecvError {
|
||||
/// This channel is currently empty, but the Sender(s) have not yet
|
||||
/// disconnected, so data may yet become available.
|
||||
Empty,
|
||||
/// The channel's sending half has been closed, and there will
|
||||
/// never be any more data received on it.
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl fmt::Display for TryRecvError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"{}",
|
||||
match self {
|
||||
TryRecvError::Empty => "channel empty",
|
||||
TryRecvError::Closed => "channel closed",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TryRecvError {}
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
// ===== SendTimeoutError =====
|
||||
|
||||
|
||||
@@ -291,4 +291,20 @@ impl<T> UnboundedSender<T> {
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.chan.is_closed()
|
||||
}
|
||||
|
||||
/// Returns `true` if senders belong to the same channel.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<()>();
|
||||
/// let tx2 = tx.clone();
|
||||
/// assert!(tx.same_channel(&tx2));
|
||||
///
|
||||
/// let (tx3, rx3) = tokio::sync::mpsc::unbounded_channel::<()>();
|
||||
/// assert!(!tx3.same_channel(&tx2));
|
||||
/// ```
|
||||
pub fn same_channel(&self, other: &Self) -> bool {
|
||||
self.chan.same_channel(&other.chan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ use std::sync::Arc;
|
||||
/// async fn main() {
|
||||
/// let count = Arc::new(Mutex::new(0));
|
||||
///
|
||||
/// for _ in 0..5 {
|
||||
/// for i in 0..5 {
|
||||
/// let my_count = Arc::clone(&count);
|
||||
/// tokio::spawn(async move {
|
||||
/// for _ in 0..10 {
|
||||
/// for j in 0..10 {
|
||||
/// let mut lock = my_count.lock().await;
|
||||
/// *lock += 1;
|
||||
/// println!("{}", lock);
|
||||
/// println!("{} {} {}", i, j, lock);
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
@@ -100,9 +100,10 @@ use std::sync::Arc;
|
||||
/// Tokio's Mutex works in a simple FIFO (first in, first out) style where all
|
||||
/// calls to [`lock`] complete in the order they were performed. In that way the
|
||||
/// Mutex is "fair" and predictable in how it distributes the locks to inner
|
||||
/// data. This is why the output of the program above is an in-order count to
|
||||
/// 50. Locks are released and reacquired after every iteration, so basically,
|
||||
/// data. Locks are released and reacquired after every iteration, so basically,
|
||||
/// each thread goes to the back of the line after it increments the value once.
|
||||
/// Note that there's some unpredictability to the timing between when the
|
||||
/// threads are started, but once they are going they alternate predictably.
|
||||
/// Finally, since there is only a single valid lock at any given time, there is
|
||||
/// no possibility of a race condition when mutating the inner value.
|
||||
///
|
||||
|
||||
@@ -312,6 +312,8 @@ impl Notify {
|
||||
/// notify.notify_one();
|
||||
/// }
|
||||
/// ```
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "notify"))]
|
||||
pub fn notify_one(&self) {
|
||||
// Load the current state
|
||||
let mut curr = self.state.load(SeqCst);
|
||||
@@ -349,8 +351,8 @@ impl Notify {
|
||||
/// Notifies all waiting tasks
|
||||
///
|
||||
/// If a task is currently waiting, that task is notified. Unlike with
|
||||
/// `notify()`, no permit is stored to be used by the next call to
|
||||
/// [`notified().await`]. The purpose of this method is to notify all
|
||||
/// `notify_one()`, no permit is stored to be used by the next call to
|
||||
/// `notified().await`. The purpose of this method is to notify all
|
||||
/// already registered waiters. Registering for notification is done by
|
||||
/// acquiring an instance of the `Notified` future via calling `notified()`.
|
||||
///
|
||||
|
||||
+106
-7
@@ -1,6 +1,56 @@
|
||||
#![cfg_attr(not(feature = "sync"), allow(dead_code, unreachable_pub))]
|
||||
|
||||
//! A channel for sending a single message between asynchronous tasks.
|
||||
//! A one-shot channel is used for sending a single message between
|
||||
//! asynchronous tasks. The [`channel`] function is used to create a
|
||||
//! [`Sender`] and [`Receiver`] handle pair that form the channel.
|
||||
//!
|
||||
//! The `Sender` handle is used by the producer to send the value.
|
||||
//! The `Receiver` handle is used by the consumer to receive the value.
|
||||
//!
|
||||
//! Each handle can be used on separate tasks.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::sync::oneshot;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let (tx, rx) = oneshot::channel();
|
||||
//!
|
||||
//! tokio::spawn(async move {
|
||||
//! if let Err(_) = tx.send(3) {
|
||||
//! println!("the receiver dropped");
|
||||
//! }
|
||||
//! });
|
||||
//!
|
||||
//! match rx.await {
|
||||
//! Ok(v) => println!("got = {:?}", v),
|
||||
//! Err(_) => println!("the sender dropped"),
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If the sender is dropped without sending, the receiver will fail with
|
||||
//! [`error::RecvError`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::sync::oneshot;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let (tx, rx) = oneshot::channel::<u32>();
|
||||
//!
|
||||
//! tokio::spawn(async move {
|
||||
//! drop(tx);
|
||||
//! });
|
||||
//!
|
||||
//! match rx.await {
|
||||
//! Ok(_) => panic!("This doesn't happen"),
|
||||
//! Err(_) => println!("the sender dropped"),
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::loom::cell::UnsafeCell;
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
@@ -14,17 +64,62 @@ use std::sync::atomic::Ordering::{self, AcqRel, Acquire};
|
||||
use std::task::Poll::{Pending, Ready};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
|
||||
/// Sends a value to the associated `Receiver`.
|
||||
/// Sends a value to the associated [`Receiver`].
|
||||
///
|
||||
/// Instances are created by the [`channel`](fn@channel) function.
|
||||
/// A pair of both a [`Sender`] and a [`Receiver`] are created by the
|
||||
/// [`channel`](fn@channel) function.
|
||||
#[derive(Debug)]
|
||||
pub struct Sender<T> {
|
||||
inner: Option<Arc<Inner<T>>>,
|
||||
}
|
||||
|
||||
/// Receive a value from the associated `Sender`.
|
||||
/// Receive a value from the associated [`Sender`].
|
||||
///
|
||||
/// Instances are created by the [`channel`](fn@channel) function.
|
||||
/// A pair of both a [`Sender`] and a [`Receiver`] are created by the
|
||||
/// [`channel`](fn@channel) function.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// if let Err(_) = tx.send(3) {
|
||||
/// println!("the receiver dropped");
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// match rx.await {
|
||||
/// Ok(v) => println!("got = {:?}", v),
|
||||
/// Err(_) => println!("the sender dropped"),
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If the sender is dropped without sending, the receiver will fail with
|
||||
/// [`error::RecvError`]:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, rx) = oneshot::channel::<u32>();
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// drop(tx);
|
||||
/// });
|
||||
///
|
||||
/// match rx.await {
|
||||
/// Ok(_) => panic!("This doesn't happen"),
|
||||
/// Err(_) => println!("the sender dropped"),
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Receiver<T> {
|
||||
inner: Option<Arc<Inner<T>>>,
|
||||
@@ -443,6 +538,9 @@ impl<T> Receiver<T> {
|
||||
/// This function is useful to perform a graceful shutdown and ensure that a
|
||||
/// value will not be sent into the channel and never received.
|
||||
///
|
||||
/// `close` is no-op if a message is already received or the channel
|
||||
/// is already closed.
|
||||
///
|
||||
/// [`Sender`]: Sender
|
||||
/// [`try_recv`]: Receiver::try_recv
|
||||
///
|
||||
@@ -490,8 +588,9 @@ impl<T> Receiver<T> {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn close(&mut self) {
|
||||
let inner = self.inner.as_ref().unwrap();
|
||||
inner.close();
|
||||
if let Some(inner) = self.inner.as_ref() {
|
||||
inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to receive a value.
|
||||
|
||||
+11
-262
@@ -1,10 +1,14 @@
|
||||
use crate::sync::batch_semaphore::{Semaphore, TryAcquireError};
|
||||
use crate::sync::mutex::TryLockError;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
|
||||
pub(crate) mod read_guard;
|
||||
pub(crate) mod write_guard;
|
||||
pub(crate) mod write_guard_mapped;
|
||||
pub(crate) use read_guard::RwLockReadGuard;
|
||||
pub(crate) use write_guard::RwLockWriteGuard;
|
||||
pub(crate) use write_guard_mapped::RwLockMappedWriteGuard;
|
||||
|
||||
#[cfg(not(loom))]
|
||||
const MAX_READS: usize = 32;
|
||||
@@ -80,240 +84,6 @@ pub struct RwLock<T: ?Sized> {
|
||||
c: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
/// RAII structure used to release the shared read access of a lock when
|
||||
/// dropped.
|
||||
///
|
||||
/// This structure is created by the [`read`] method on
|
||||
/// [`RwLock`].
|
||||
///
|
||||
/// [`read`]: method@RwLock::read
|
||||
/// [`RwLock`]: struct@RwLock
|
||||
pub struct RwLockReadGuard<'a, T: ?Sized> {
|
||||
s: &'a Semaphore,
|
||||
data: *const T,
|
||||
marker: marker::PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> RwLockReadGuard<'a, T> {
|
||||
/// Make a new `RwLockReadGuard` for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockReadGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be
|
||||
/// used as `RwLockReadGuard::map(...)`. A method would interfere with
|
||||
/// methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockReadGuard::map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockReadGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockReadGuard.html#method.map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// let guard = lock.read().await;
|
||||
/// let guard = RwLockReadGuard::map(guard, |f| &f.0);
|
||||
///
|
||||
/// assert_eq!(1, *guard);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<F, U: ?Sized>(this: Self, f: F) -> RwLockReadGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&T) -> &U,
|
||||
{
|
||||
let data = f(&*this) as *const U;
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`RwLockReadGuard`] for a component of the
|
||||
/// locked data. The original guard is returned if the closure returns
|
||||
/// `None`.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockReadGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be used as
|
||||
/// `RwLockReadGuard::try_map(..)`. A method would interfere with methods of the
|
||||
/// same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockReadGuard::try_map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockReadGuard::try_map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockReadGuard.html#method.try_map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// let guard = lock.read().await;
|
||||
/// let guard = RwLockReadGuard::try_map(guard, |f| Some(&f.0)).expect("should not fail");
|
||||
///
|
||||
/// assert_eq!(1, *guard);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_map<F, U: ?Sized>(this: Self, f: F) -> Result<RwLockReadGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&T) -> Option<&U>,
|
||||
{
|
||||
let data = match f(&*this) {
|
||||
Some(data) => data as *const U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
Ok(RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Debug for RwLockReadGuard<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Display for RwLockReadGuard<'a, T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for RwLockReadGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
///
|
||||
/// This structure is created by the [`write`] and method
|
||||
/// on [`RwLock`].
|
||||
///
|
||||
/// [`write`]: method@RwLock::write
|
||||
/// [`RwLock`]: struct@RwLock
|
||||
pub struct RwLockWriteGuard<'a, T: ?Sized> {
|
||||
s: &'a Semaphore,
|
||||
data: *mut T,
|
||||
marker: marker::PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
/// Atomically downgrades a write lock into a read lock without allowing
|
||||
/// any writers to take exclusive access of the lock in the meantime.
|
||||
///
|
||||
/// **Note:** This won't *necessarily* allow any additional readers to acquire
|
||||
/// locks, since [`RwLock`] is fair and it is possible that a writer is next
|
||||
/// in line.
|
||||
///
|
||||
/// Returns an RAII guard which will drop this read access of the `RwLock`
|
||||
/// when dropped.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// #
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = Arc::new(RwLock::new(1));
|
||||
///
|
||||
/// let n = lock.write().await;
|
||||
///
|
||||
/// let cloned_lock = lock.clone();
|
||||
/// let handle = tokio::spawn(async move {
|
||||
/// *cloned_lock.write().await = 2;
|
||||
/// });
|
||||
///
|
||||
/// let n = n.downgrade();
|
||||
/// assert_eq!(*n, 1, "downgrade is atomic");
|
||||
///
|
||||
/// drop(n);
|
||||
/// handle.await.unwrap();
|
||||
/// assert_eq!(*lock.read().await, 2, "second writer obtained write lock");
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`RwLock`]: struct@RwLock
|
||||
pub fn downgrade(self) -> RwLockReadGuard<'a, T> {
|
||||
let RwLockWriteGuard { s, data, .. } = self;
|
||||
|
||||
// Release all but one of the permits held by the write guard
|
||||
s.release(MAX_READS - 1);
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(self);
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Debug for RwLockWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Display for RwLockWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for RwLockWriteGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(MAX_READS);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(loom))]
|
||||
fn bounds() {
|
||||
@@ -351,11 +121,13 @@ unsafe impl<T> Sync for RwLock<T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<T> Send for RwLockReadGuard<'_, T> where T: ?Sized + Sync {}
|
||||
unsafe impl<T> Sync for RwLockReadGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<T> Sync for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<T> Sync for RwLockMappedWriteGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
// Safety: Stores a raw pointer to `T`, so if `T` is `Sync`, the lock guard over
|
||||
// `T` is `Send` - but since this is also provides mutable access, we need to
|
||||
// make sure that `T` is `Send` since its value can be sent across thread
|
||||
// boundaries.
|
||||
unsafe impl<T> Send for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<T> Send for RwLockMappedWriteGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
|
||||
impl<T: ?Sized> RwLock<T> {
|
||||
/// Creates a new instance of an `RwLock<T>` which is unlocked.
|
||||
@@ -437,7 +209,6 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// drop(n);
|
||||
///}
|
||||
/// ```
|
||||
///
|
||||
pub async fn read(&self) -> RwLockReadGuard<'_, T> {
|
||||
self.s.acquire(1).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
@@ -500,8 +271,8 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// Locks this `RwLock` with exclusive write access, causing the current
|
||||
/// task to yield until the lock has been acquired.
|
||||
///
|
||||
/// The calling task will yield while other writers or readers
|
||||
/// currently have access to the lock.
|
||||
/// The calling task will yield while other writers or readers currently
|
||||
/// have access to the lock.
|
||||
///
|
||||
/// Returns an RAII guard which will drop the write access of this `RwLock`
|
||||
/// when dropped.
|
||||
@@ -602,28 +373,6 @@ impl<T: ?Sized> RwLock<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockReadGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockWriteGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::DerefMut for RwLockWriteGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for RwLock<T> {
|
||||
fn from(s: T) -> Self {
|
||||
Self::new(s)
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
use crate::sync::batch_semaphore::Semaphore;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
|
||||
/// RAII structure used to release the shared read access of a lock when
|
||||
/// dropped.
|
||||
///
|
||||
/// This structure is created by the [`read`] method on
|
||||
/// [`RwLock`].
|
||||
///
|
||||
/// [`read`]: method@crate::sync::RwLock::read
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
pub struct RwLockReadGuard<'a, T: ?Sized> {
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *const T,
|
||||
pub(super) marker: marker::PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> RwLockReadGuard<'a, T> {
|
||||
/// Make a new `RwLockReadGuard` for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockReadGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be
|
||||
/// used as `RwLockReadGuard::map(...)`. A method would interfere with
|
||||
/// methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockReadGuard::map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockReadGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockReadGuard.html#method.map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// let guard = lock.read().await;
|
||||
/// let guard = RwLockReadGuard::map(guard, |f| &f.0);
|
||||
///
|
||||
/// assert_eq!(1, *guard);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<F, U: ?Sized>(this: Self, f: F) -> RwLockReadGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&T) -> &U,
|
||||
{
|
||||
let data = f(&*this) as *const U;
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`RwLockReadGuard`] for a component of the
|
||||
/// locked data. The original guard is returned if the closure returns
|
||||
/// `None`.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockReadGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be used as
|
||||
/// `RwLockReadGuard::try_map(..)`. A method would interfere with methods of the
|
||||
/// same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockReadGuard::try_map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockReadGuard::try_map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockReadGuard.html#method.try_map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// let guard = lock.read().await;
|
||||
/// let guard = RwLockReadGuard::try_map(guard, |f| Some(&f.0)).expect("should not fail");
|
||||
///
|
||||
/// assert_eq!(1, *guard);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_map<F, U: ?Sized>(this: Self, f: F) -> Result<RwLockReadGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&T) -> Option<&U>,
|
||||
{
|
||||
let data = match f(&*this) {
|
||||
Some(data) => data as *const U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
Ok(RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockReadGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Debug for RwLockReadGuard<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Display for RwLockReadGuard<'a, T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for RwLockReadGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
use crate::sync::batch_semaphore::Semaphore;
|
||||
use crate::sync::rwlock::read_guard::RwLockReadGuard;
|
||||
use crate::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
|
||||
/// RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
///
|
||||
/// This structure is created by the [`write`] and method
|
||||
/// on [`RwLock`].
|
||||
///
|
||||
/// [`write`]: method@crate::sync::RwLock::write
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
pub struct RwLockWriteGuard<'a, T: ?Sized> {
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *mut T,
|
||||
pub(super) marker: marker::PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
/// Make a new [`RwLockMappedWriteGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockWriteGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be used as
|
||||
/// `RwLockWriteGuard::map(..)`. A method would interfere with methods of
|
||||
/// the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockWriteGuard::map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard
|
||||
/// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = RwLockWriteGuard::map(lock.write().await, |f| &mut f.0);
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *lock.read().await);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<F, U: ?Sized>(mut this: Self, f: F) -> RwLockMappedWriteGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
RwLockMappedWriteGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of
|
||||
/// the locked data. The original guard is returned if the closure returns
|
||||
/// `None`.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockWriteGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be
|
||||
/// used as `RwLockWriteGuard::try_map(...)`. A method would interfere with
|
||||
/// methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockWriteGuard::try_map`] from
|
||||
/// the [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard
|
||||
/// [`RwLockWriteGuard::try_map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.try_map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let guard = lock.write().await;
|
||||
/// let mut guard = RwLockWriteGuard::try_map(guard, |f| Some(&mut f.0)).expect("should not fail");
|
||||
/// *guard = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *lock.read().await);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_map<F, U: ?Sized>(
|
||||
mut this: Self,
|
||||
f: F,
|
||||
) -> Result<RwLockMappedWriteGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&mut T) -> Option<&mut U>,
|
||||
{
|
||||
let data = match f(&mut *this) {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
Ok(RwLockMappedWriteGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts this `RwLockWriteGuard` into an `RwLockMappedWriteGuard`. This
|
||||
/// method can be used to store a non-mapped guard in a struct field that
|
||||
/// expects a mapped guard.
|
||||
///
|
||||
/// This is equivalent to calling `RwLockWriteGuard::map(guard, |me| me)`.
|
||||
#[inline]
|
||||
pub fn into_mapped(this: Self) -> RwLockMappedWriteGuard<'a, T> {
|
||||
RwLockWriteGuard::map(this, |me| me)
|
||||
}
|
||||
|
||||
/// Atomically downgrades a write lock into a read lock without allowing
|
||||
/// any writers to take exclusive access of the lock in the meantime.
|
||||
///
|
||||
/// **Note:** This won't *necessarily* allow any additional readers to acquire
|
||||
/// locks, since [`RwLock`] is fair and it is possible that a writer is next
|
||||
/// in line.
|
||||
///
|
||||
/// Returns an RAII guard which will drop this read access of the `RwLock`
|
||||
/// when dropped.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// #
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = Arc::new(RwLock::new(1));
|
||||
///
|
||||
/// let n = lock.write().await;
|
||||
///
|
||||
/// let cloned_lock = lock.clone();
|
||||
/// let handle = tokio::spawn(async move {
|
||||
/// *cloned_lock.write().await = 2;
|
||||
/// });
|
||||
///
|
||||
/// let n = n.downgrade();
|
||||
/// assert_eq!(*n, 1, "downgrade is atomic");
|
||||
///
|
||||
/// drop(n);
|
||||
/// handle.await.unwrap();
|
||||
/// assert_eq!(*lock.read().await, 2, "second writer obtained write lock");
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
pub fn downgrade(self) -> RwLockReadGuard<'a, T> {
|
||||
let RwLockWriteGuard { s, data, .. } = self;
|
||||
|
||||
// Release all but one of the permits held by the write guard
|
||||
s.release(super::MAX_READS - 1);
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(self);
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockWriteGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::DerefMut for RwLockWriteGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Debug for RwLockWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Display for RwLockWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for RwLockWriteGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(super::MAX_READS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
use crate::sync::batch_semaphore::Semaphore;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
|
||||
/// RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
///
|
||||
/// This structure is created by [mapping] an [`RwLockWriteGuard`]. It is a
|
||||
/// separate type from `RwLockWriteGuard` to disallow downgrading a mapped
|
||||
/// guard, since doing so can cause undefined behavior.
|
||||
///
|
||||
/// [mapping]: method@crate::sync::RwLockWriteGuard::map
|
||||
/// [`RwLockWriteGuard`]: struct@crate::sync::RwLockWriteGuard
|
||||
pub struct RwLockMappedWriteGuard<'a, T: ?Sized> {
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *mut T,
|
||||
pub(super) marker: marker::PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
||||
/// Make a new `RwLockMappedWriteGuard` for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockMappedWriteGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be used as
|
||||
/// `RwLockWriteGuard::map(..)`. A method would interfere with methods of
|
||||
/// the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockWriteGuard::map`] from the
|
||||
/// [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = RwLockWriteGuard::map(lock.write().await, |f| &mut f.0);
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *lock.read().await);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<F, U: ?Sized>(mut this: Self, f: F) -> RwLockMappedWriteGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
RwLockMappedWriteGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of
|
||||
/// the locked data. The original guard is returned if the closure returns
|
||||
/// `None`.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockMappedWriteGuard` passed in already
|
||||
/// locked the data.
|
||||
///
|
||||
/// This is an associated function that needs to be
|
||||
/// used as `RwLockWriteGuard::try_map(...)`. A method would interfere with
|
||||
/// methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// This is an asynchronous version of [`RwLockWriteGuard::try_map`] from
|
||||
/// the [`parking_lot` crate].
|
||||
///
|
||||
/// [`RwLockWriteGuard::try_map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.try_map
|
||||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let lock = RwLock::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let guard = lock.write().await;
|
||||
/// let mut guard = RwLockWriteGuard::try_map(guard, |f| Some(&mut f.0)).expect("should not fail");
|
||||
/// *guard = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *lock.read().await);
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_map<F, U: ?Sized>(
|
||||
mut this: Self,
|
||||
f: F,
|
||||
) -> Result<RwLockMappedWriteGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&mut T) -> Option<&mut U>,
|
||||
{
|
||||
let data = match f(&mut *this) {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
Ok(RwLockMappedWriteGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockMappedWriteGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::DerefMut for RwLockMappedWriteGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Debug for RwLockMappedWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> fmt::Display for RwLockMappedWriteGuard<'a, T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for RwLockMappedWriteGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(super::MAX_READS);
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ impl Semaphore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to acquire n permits from the semaphore.
|
||||
/// Tries to acquire `n` permits from the semaphore.
|
||||
///
|
||||
/// If the semaphore has been closed, this returns a [`TryAcquireError::Closed`]
|
||||
/// and a [`TryAcquireError::NoPermits`] if there are no permits left. Otherwise,
|
||||
@@ -180,6 +180,27 @@ impl Semaphore {
|
||||
})
|
||||
}
|
||||
|
||||
/// Acquires `n` permits from the semaphore.
|
||||
///
|
||||
/// The semaphore must be wrapped in an [`Arc`] to call this method.
|
||||
/// If the semaphore has been closed, this returns an [`AcquireError`].
|
||||
/// Otherwise, this returns a [`OwnedSemaphorePermit`] representing the
|
||||
/// acquired permit.
|
||||
///
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
/// [`AcquireError`]: crate::sync::AcquireError
|
||||
/// [`OwnedSemaphorePermit`]: crate::sync::OwnedSemaphorePermit
|
||||
pub async fn acquire_many_owned(
|
||||
self: Arc<Self>,
|
||||
n: u32,
|
||||
) -> Result<OwnedSemaphorePermit, AcquireError> {
|
||||
self.ll_sem.acquire(n).await?;
|
||||
Ok(OwnedSemaphorePermit {
|
||||
sem: self,
|
||||
permits: n,
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to acquire a permit from the semaphore.
|
||||
///
|
||||
/// The semaphore must be wrapped in an [`Arc`] to call this method. If
|
||||
@@ -202,6 +223,31 @@ impl Semaphore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to acquire `n` permits from the semaphore.
|
||||
///
|
||||
/// The semaphore must be wrapped in an [`Arc`] to call this method. If
|
||||
/// the semaphore has been closed, this returns a [`TryAcquireError::Closed`]
|
||||
/// and a [`TryAcquireError::NoPermits`] if there are no permits left.
|
||||
/// Otherwise, this returns a [`OwnedSemaphorePermit`] representing the
|
||||
/// acquired permit.
|
||||
///
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
/// [`TryAcquireError::Closed`]: crate::sync::TryAcquireError::Closed
|
||||
/// [`TryAcquireError::NoPermits`]: crate::sync::TryAcquireError::NoPermits
|
||||
/// [`OwnedSemaphorePermit`]: crate::sync::OwnedSemaphorePermit
|
||||
pub fn try_acquire_many_owned(
|
||||
self: Arc<Self>,
|
||||
n: u32,
|
||||
) -> Result<OwnedSemaphorePermit, TryAcquireError> {
|
||||
match self.ll_sem.try_acquire(n) {
|
||||
Ok(_) => Ok(OwnedSemaphorePermit {
|
||||
sem: self,
|
||||
permits: n,
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the semaphore.
|
||||
///
|
||||
/// This prevents the semaphore from issuing new permits and notifies all pending waiters.
|
||||
|
||||
+27
-6
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(feature = "sync"), allow(dead_code, unreachable_pub))]
|
||||
|
||||
//! A single-producer, multi-consumer channel that only retains the *last* sent
|
||||
//! value.
|
||||
//!
|
||||
@@ -51,7 +53,7 @@
|
||||
//! [`Sender::is_closed`]: crate::sync::watch::Sender::is_closed
|
||||
//! [`Sender::closed`]: crate::sync::watch::Sender::closed
|
||||
|
||||
use crate::sync::Notify;
|
||||
use crate::sync::notify::Notify;
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::atomic::Ordering::{Relaxed, SeqCst};
|
||||
@@ -198,6 +200,14 @@ pub fn channel<T>(init: T) -> (Sender<T>, Receiver<T>) {
|
||||
}
|
||||
|
||||
impl<T> Receiver<T> {
|
||||
fn from_shared(version: usize, shared: Arc<Shared<T>>) -> Self {
|
||||
// No synchronization necessary as this is only used as a counter and
|
||||
// not memory access.
|
||||
shared.ref_count_rx.fetch_add(1, Relaxed);
|
||||
|
||||
Self { version, shared }
|
||||
}
|
||||
|
||||
/// Returns a reference to the most recently sent value
|
||||
///
|
||||
/// Outstanding borrows hold a read lock. This means that long lived borrows
|
||||
@@ -260,6 +270,12 @@ impl<T> Receiver<T> {
|
||||
// loop around again in case the wake-up was spurious
|
||||
}
|
||||
}
|
||||
|
||||
cfg_process_driver! {
|
||||
pub(crate) fn try_has_changed(&mut self) -> Option<Result<(), error::RecvError>> {
|
||||
maybe_changed(&self.shared, &mut self.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_changed<T>(
|
||||
@@ -289,11 +305,7 @@ impl<T> Clone for Receiver<T> {
|
||||
let version = self.version;
|
||||
let shared = self.shared.clone();
|
||||
|
||||
// No synchronization necessary as this is only used as a counter and
|
||||
// not memory access.
|
||||
shared.ref_count_rx.fetch_add(1, Relaxed);
|
||||
|
||||
Receiver { version, shared }
|
||||
Self::from_shared(version, shared)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,6 +408,15 @@ impl<T> Sender<T> {
|
||||
notified.await;
|
||||
debug_assert_eq!(0, self.shared.ref_count_rx.load(Relaxed));
|
||||
}
|
||||
|
||||
cfg_signal_internal! {
|
||||
pub(crate) fn subscribe(&self) -> Receiver<T> {
|
||||
let shared = self.shared.clone();
|
||||
let version = shared.version.load(SeqCst);
|
||||
|
||||
Receiver::from_shared(version, shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Sender<T> {
|
||||
|
||||
@@ -661,7 +661,7 @@ impl Shared {
|
||||
}
|
||||
|
||||
fn ptr_eq(&self, other: &Shared) -> bool {
|
||||
self as *const _ == other as *const _
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,11 +209,66 @@
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Cooperative scheduling
|
||||
//!
|
||||
//! A single call to [`poll`] on a top-level task may potentially do a lot of
|
||||
//! work before it returns `Poll::Pending`. If a task runs for a long period of
|
||||
//! time without yielding back to the executor, it can starve other tasks
|
||||
//! waiting on that executor to execute them, or drive underlying resources.
|
||||
//! Since Rust does not have a runtime, it is difficult to forcibly preempt a
|
||||
//! long-running task. Instead, this module provides an opt-in mechanism for
|
||||
//! futures to collaborate with the executor to avoid starvation.
|
||||
//!
|
||||
//! Consider a future like this one:
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_stream::{Stream, StreamExt};
|
||||
//! async fn drop_all<I: Stream + Unpin>(mut input: I) {
|
||||
//! while let Some(_) = input.next().await {}
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! It may look harmless, but consider what happens under heavy load if the
|
||||
//! input stream is _always_ ready. If we spawn `drop_all`, the task will never
|
||||
//! yield, and will starve other tasks and resources on the same executor.
|
||||
//!
|
||||
//! To account for this, Tokio has explicit yield points in a number of library
|
||||
//! functions, which force tasks to return to the executor periodically.
|
||||
//!
|
||||
//!
|
||||
//! #### unconstrained
|
||||
//!
|
||||
//! If necessary, [`task::unconstrained`] lets you opt out a future of Tokio's cooperative
|
||||
//! scheduling. When a future is wrapped with `unconstrained`, it will never be forced to yield to
|
||||
//! Tokio. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! use tokio::{task, sync::mpsc};
|
||||
//!
|
||||
//! let fut = async {
|
||||
//! let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
//!
|
||||
//! for i in 0..1000 {
|
||||
//! let _ = tx.send(());
|
||||
//! // This will always be ready. If coop was in effect, this code would be forced to yield
|
||||
//! // periodically. However, if left unconstrained, then this code will never yield.
|
||||
//! rx.recv().await;
|
||||
//! }
|
||||
//! };
|
||||
//!
|
||||
//! task::unconstrained(fut).await;
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [`task::spawn_blocking`]: crate::task::spawn_blocking
|
||||
//! [`task::block_in_place`]: crate::task::block_in_place
|
||||
//! [rt-multi-thread]: ../runtime/index.html#threaded-scheduler
|
||||
//! [`task::yield_now`]: crate::task::yield_now()
|
||||
//! [`thread::yield_now`]: std::thread::yield_now
|
||||
//! [`task::unconstrained`]: crate::task::unconstrained()
|
||||
//! [`poll`]: method@std::future::Future::poll
|
||||
|
||||
cfg_rt! {
|
||||
pub use crate::runtime::task::{JoinError, JoinHandle};
|
||||
@@ -236,4 +291,7 @@ cfg_rt! {
|
||||
|
||||
mod task_local;
|
||||
pub use task_local::LocalKey;
|
||||
|
||||
mod unconstrained;
|
||||
pub use unconstrained::{unconstrained, Unconstrained};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use pin_project_lite::pin_project;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// Future for the [`unconstrained`](unconstrained) method.
|
||||
#[must_use = "Unconstrained does nothing unless polled"]
|
||||
pub struct Unconstrained<F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Future for Unconstrained<F>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
type Output = <F as Future>::Output;
|
||||
|
||||
cfg_coop! {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let inner = self.project().inner;
|
||||
crate::coop::with_unconstrained(|| inner.poll(cx))
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_coop! {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let inner = self.project().inner;
|
||||
inner.poll(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn off cooperative scheduling for a future. The future will never be forced to yield by
|
||||
/// Tokio. Using this exposes your service to starvation if the unconstrained future never yields
|
||||
/// otherwise.
|
||||
///
|
||||
/// See also the usage example in the [task module](index.html#unconstrained).
|
||||
pub fn unconstrained<F>(inner: F) -> Unconstrained<F> {
|
||||
Unconstrained { inner }
|
||||
}
|
||||
@@ -543,6 +543,10 @@ impl TimerEntry {
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), super::Error>> {
|
||||
if self.driver.is_shutdown() {
|
||||
panic!(crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR);
|
||||
}
|
||||
|
||||
if let Some(deadline) = self.initial_deadline {
|
||||
self.as_mut().reset(deadline);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::loom::sync::{Arc, Mutex};
|
||||
use crate::loom::sync::Arc;
|
||||
use crate::time::driver::ClockTime;
|
||||
use std::fmt;
|
||||
|
||||
@@ -6,13 +6,13 @@ use std::fmt;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Handle {
|
||||
time_source: ClockTime,
|
||||
inner: Arc<Mutex<super::Inner>>,
|
||||
inner: Arc<super::Inner>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Creates a new timer `Handle` from a shared `Inner` timer state.
|
||||
pub(super) fn new(inner: Arc<Mutex<super::Inner>>) -> Self {
|
||||
let time_source = inner.lock().time_source.clone();
|
||||
pub(super) fn new(inner: Arc<super::Inner>) -> Self {
|
||||
let time_source = inner.state.lock().time_source.clone();
|
||||
Handle { time_source, inner }
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ impl Handle {
|
||||
&self.time_source
|
||||
}
|
||||
|
||||
/// Locks the driver's inner structure
|
||||
pub(super) fn lock(&self) -> crate::loom::sync::MutexGuard<'_, super::Inner> {
|
||||
self.inner.lock()
|
||||
/// Access the driver's inner structure
|
||||
pub(super) fn get(&self) -> &super::Inner {
|
||||
&*self.inner
|
||||
}
|
||||
|
||||
// Check whether the driver has been shutdown
|
||||
pub(super) fn is_shutdown(&self) -> bool {
|
||||
self.inner.is_shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ mod wheel;
|
||||
|
||||
pub(super) mod sleep;
|
||||
|
||||
use crate::loom::sync::atomic::{AtomicBool, Ordering};
|
||||
use crate::loom::sync::{Arc, Mutex};
|
||||
use crate::park::{Park, Unpark};
|
||||
use crate::time::error::Error;
|
||||
@@ -86,7 +87,7 @@ pub(crate) struct Driver<P: Park + 'static> {
|
||||
time_source: ClockTime,
|
||||
|
||||
/// Shared state
|
||||
inner: Handle,
|
||||
handle: Handle,
|
||||
|
||||
/// Parker to delegate to
|
||||
park: P,
|
||||
@@ -132,7 +133,16 @@ impl ClockTime {
|
||||
}
|
||||
|
||||
/// Timer state shared between `Driver`, `Handle`, and `Registration`.
|
||||
pub(self) struct Inner {
|
||||
struct Inner {
|
||||
// The state is split like this so `Handle` can access `is_shutdown` without locking the mutex
|
||||
pub(super) state: Mutex<InnerState>,
|
||||
|
||||
/// True if the driver is being shutdown
|
||||
pub(super) is_shutdown: AtomicBool,
|
||||
}
|
||||
|
||||
/// Time state shared which must be protected by a `Mutex`
|
||||
struct InnerState {
|
||||
/// Timing backend in use
|
||||
time_source: ClockTime,
|
||||
|
||||
@@ -145,9 +155,6 @@ pub(self) struct Inner {
|
||||
/// Timer wheel
|
||||
wheel: wheel::Wheel,
|
||||
|
||||
/// True if the driver is being shutdown
|
||||
is_shutdown: bool,
|
||||
|
||||
/// Unparker that can be used to wake the time driver
|
||||
unpark: Box<dyn Unpark>,
|
||||
}
|
||||
@@ -169,7 +176,7 @@ where
|
||||
|
||||
Driver {
|
||||
time_source,
|
||||
inner: Handle::new(Arc::new(Mutex::new(inner))),
|
||||
handle: Handle::new(Arc::new(inner)),
|
||||
park,
|
||||
}
|
||||
}
|
||||
@@ -181,15 +188,15 @@ where
|
||||
/// `with_default`, setting the timer as the default timer for the execution
|
||||
/// context.
|
||||
pub(crate) fn handle(&self) -> Handle {
|
||||
self.inner.clone()
|
||||
self.handle.clone()
|
||||
}
|
||||
|
||||
fn park_internal(&mut self, limit: Option<Duration>) -> Result<(), P::Error> {
|
||||
let clock = &self.time_source.clock;
|
||||
|
||||
let mut lock = self.inner.lock();
|
||||
let mut lock = self.handle.get().state.lock();
|
||||
|
||||
assert!(!lock.is_shutdown);
|
||||
assert!(!self.handle.is_shutdown());
|
||||
|
||||
let next_wake = lock.wheel.next_expiration_time();
|
||||
lock.next_wake =
|
||||
@@ -237,7 +244,7 @@ where
|
||||
}
|
||||
|
||||
// Process pending timers after waking up
|
||||
self.inner.process();
|
||||
self.handle.process();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -255,7 +262,7 @@ impl Handle {
|
||||
let mut waker_list: [Option<Waker>; 32] = Default::default();
|
||||
let mut waker_idx = 0;
|
||||
|
||||
let mut lock = self.lock();
|
||||
let mut lock = self.get().lock();
|
||||
|
||||
assert!(now >= lock.elapsed);
|
||||
|
||||
@@ -278,7 +285,7 @@ impl Handle {
|
||||
|
||||
waker_idx = 0;
|
||||
|
||||
lock = self.lock();
|
||||
lock = self.get().lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,7 +316,7 @@ impl Handle {
|
||||
/// `add_entry` must not be called concurrently.
|
||||
pub(self) unsafe fn clear_entry(&self, entry: NonNull<TimerShared>) {
|
||||
unsafe {
|
||||
let mut lock = self.lock();
|
||||
let mut lock = self.get().lock();
|
||||
|
||||
if entry.as_ref().might_be_registered() {
|
||||
lock.wheel.remove(entry);
|
||||
@@ -327,7 +334,7 @@ impl Handle {
|
||||
/// the `TimerEntry`)
|
||||
pub(self) unsafe fn reregister(&self, new_tick: u64, entry: NonNull<TimerShared>) {
|
||||
let waker = unsafe {
|
||||
let mut lock = self.lock();
|
||||
let mut lock = self.get().lock();
|
||||
|
||||
// We may have raced with a firing/deregistration, so check before
|
||||
// deregistering.
|
||||
@@ -338,7 +345,7 @@ impl Handle {
|
||||
// Now that we have exclusive control of this entry, mint a handle to reinsert it.
|
||||
let entry = entry.as_ref().handle();
|
||||
|
||||
if lock.is_shutdown {
|
||||
if self.is_shutdown() {
|
||||
unsafe { entry.fire(Err(crate::time::error::Error::shutdown())) }
|
||||
} else {
|
||||
entry.set_expiration(new_tick);
|
||||
@@ -396,19 +403,15 @@ where
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
let mut lock = self.inner.lock();
|
||||
|
||||
if lock.is_shutdown {
|
||||
if self.handle.is_shutdown() {
|
||||
return;
|
||||
}
|
||||
|
||||
lock.is_shutdown = true;
|
||||
|
||||
drop(lock);
|
||||
self.handle.get().is_shutdown.store(true, Ordering::SeqCst);
|
||||
|
||||
// Advance time forward to the end of time.
|
||||
|
||||
self.inner.process_at_time(u64::MAX);
|
||||
self.handle.process_at_time(u64::MAX);
|
||||
|
||||
self.park.shutdown();
|
||||
}
|
||||
@@ -428,14 +431,26 @@ where
|
||||
impl Inner {
|
||||
pub(self) fn new(time_source: ClockTime, unpark: Box<dyn Unpark>) -> Self {
|
||||
Inner {
|
||||
time_source,
|
||||
elapsed: 0,
|
||||
next_wake: None,
|
||||
unpark,
|
||||
wheel: wheel::Wheel::new(),
|
||||
is_shutdown: false,
|
||||
state: Mutex::new(InnerState {
|
||||
time_source,
|
||||
elapsed: 0,
|
||||
next_wake: None,
|
||||
unpark,
|
||||
wheel: wheel::Wheel::new(),
|
||||
}),
|
||||
is_shutdown: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Locks the driver's inner structure
|
||||
pub(super) fn lock(&self) -> crate::loom::sync::MutexGuard<'_, InnerState> {
|
||||
self.state.lock()
|
||||
}
|
||||
|
||||
// Check whether the driver has been shutdown
|
||||
pub(super) fn is_shutdown(&self) -> bool {
|
||||
self.is_shutdown.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Inner {
|
||||
|
||||
@@ -16,6 +16,8 @@ use std::task::{self, Poll};
|
||||
///
|
||||
/// Canceling a sleep instance is done by dropping the returned future. No additional
|
||||
/// cleanup work is required.
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "delay_until"))]
|
||||
pub fn sleep_until(deadline: Instant) -> Sleep {
|
||||
Sleep::new_timeout(deadline)
|
||||
}
|
||||
@@ -53,8 +55,13 @@ pub fn sleep_until(deadline: Instant) -> Sleep {
|
||||
/// ```
|
||||
///
|
||||
/// [`interval`]: crate::time::interval()
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "delay_for"))]
|
||||
pub fn sleep(duration: Duration) -> Sleep {
|
||||
sleep_until(Instant::now() + duration)
|
||||
match Instant::now().checked_add(duration) {
|
||||
Some(deadline) => sleep_until(deadline),
|
||||
None => sleep_until(Instant::far_future()),
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
@@ -145,6 +152,8 @@ pin_project! {
|
||||
///
|
||||
/// [`select!`]: ../macro.select.html
|
||||
/// [`tokio::pin!`]: ../macro.pin.html
|
||||
// Alias for old name in 0.2
|
||||
#[cfg_attr(docsrs, doc(alias = "Delay"))]
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Sleep {
|
||||
@@ -164,6 +173,10 @@ impl Sleep {
|
||||
Sleep { deadline, entry }
|
||||
}
|
||||
|
||||
pub(crate) fn far_future() -> Sleep {
|
||||
Self::new_timeout(Instant::far_future())
|
||||
}
|
||||
|
||||
/// Returns the instant at which the future will complete.
|
||||
pub fn deadline(&self) -> Instant {
|
||||
self.deadline
|
||||
@@ -185,7 +198,7 @@ impl Sleep {
|
||||
/// completed.
|
||||
///
|
||||
/// To call this method, you will usually combine the call with
|
||||
/// [`Pin::as_mut`], which lets you call the method with consuming the
|
||||
/// [`Pin::as_mut`], which lets you call the method without consuming the
|
||||
/// `Sleep` itself.
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{task::Context, time::Duration};
|
||||
#[cfg(not(loom))]
|
||||
use futures::task::noop_waker_ref;
|
||||
|
||||
use crate::loom::sync::{Arc, Mutex};
|
||||
use crate::loom::sync::Arc;
|
||||
use crate::loom::thread;
|
||||
use crate::{
|
||||
loom::sync::atomic::{AtomicBool, Ordering},
|
||||
@@ -45,7 +45,7 @@ fn single_timer() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let handle_ = handle.clone();
|
||||
let jh = thread::spawn(move || {
|
||||
@@ -76,7 +76,7 @@ fn drop_timer() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let handle_ = handle.clone();
|
||||
let jh = thread::spawn(move || {
|
||||
@@ -107,7 +107,7 @@ fn change_waker() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let handle_ = handle.clone();
|
||||
let jh = thread::spawn(move || {
|
||||
@@ -142,7 +142,7 @@ fn reset_future() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let handle_ = handle.clone();
|
||||
let finished_early_ = finished_early.clone();
|
||||
@@ -191,7 +191,7 @@ fn poll_process_levels() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source, MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let mut entries = vec![];
|
||||
|
||||
@@ -232,7 +232,7 @@ fn poll_process_levels_targeted() {
|
||||
let time_source = super::ClockTime::new(clock.clone());
|
||||
|
||||
let inner = super::Inner::new(time_source, MockUnpark::mock());
|
||||
let handle = Handle::new(Arc::new(Mutex::new(inner)));
|
||||
let handle = Handle::new(Arc::new(inner));
|
||||
|
||||
let e1 = TimerEntry::new(&handle, clock.now() + Duration::from_millis(193));
|
||||
pin!(e1);
|
||||
|
||||
@@ -54,6 +54,14 @@ impl Instant {
|
||||
Instant { std }
|
||||
}
|
||||
|
||||
pub(crate) fn far_future() -> Instant {
|
||||
// Roughly 30 years from now.
|
||||
// API does not provide a way to obtain max `Instant`
|
||||
// or convert specific date in the future to instant.
|
||||
// 1000 years overflows on macOS, 100 years overflows on FreeBSD.
|
||||
Self::now() + Duration::from_secs(86400 * 365 * 30)
|
||||
}
|
||||
|
||||
/// Convert the value into a `std::time::Instant`.
|
||||
pub fn into_std(self) -> std::time::Instant {
|
||||
self.std
|
||||
|
||||
+7
-11
@@ -24,10 +24,8 @@
|
||||
//! Wait 100ms and print "100 ms have elapsed"
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::time::sleep;
|
||||
//!
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! use tokio::time::sleep;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
@@ -56,10 +54,10 @@
|
||||
//!
|
||||
//! A simple example using [`interval`] to execute a task every two seconds.
|
||||
//!
|
||||
//! The difference between [`interval`] and [`sleep`] is that an
|
||||
//! [`interval`] measures the time since the last tick, which means that
|
||||
//! `.tick().await` may wait for a shorter time than the duration specified
|
||||
//! for the interval if some time has passed between calls to `.tick().await`.
|
||||
//! The difference between [`interval`] and [`sleep`] is that an [`interval`]
|
||||
//! measures the time since the last tick, which means that `.tick().await`
|
||||
//! may wait for a shorter time than the duration specified for the interval
|
||||
//! if some time has passed between calls to `.tick().await`.
|
||||
//!
|
||||
//! If the tick in the example below was replaced with [`sleep`], the task
|
||||
//! would only be executed once every three seconds, and not every two
|
||||
@@ -75,11 +73,9 @@
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let interval = time::interval(time::Duration::from_secs(2));
|
||||
//! tokio::pin!(interval);
|
||||
//!
|
||||
//! let mut interval = time::interval(time::Duration::from_secs(2));
|
||||
//! for _i in 0..5 {
|
||||
//! interval.as_mut().tick().await;
|
||||
//! interval.tick().await;
|
||||
//! task_that_takes_a_second().await;
|
||||
//! }
|
||||
//! }
|
||||
|
||||
@@ -49,7 +49,11 @@ pub fn timeout<T>(duration: Duration, future: T) -> Timeout<T>
|
||||
where
|
||||
T: Future,
|
||||
{
|
||||
let delay = Sleep::new_timeout(Instant::now() + duration);
|
||||
let deadline = Instant::now().checked_add(duration);
|
||||
let delay = match deadline {
|
||||
Some(deadline) => Sleep::new_timeout(deadline),
|
||||
None => Sleep::far_future(),
|
||||
};
|
||||
Timeout::new_with_delay(future, delay)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/// Error string explaining that the Tokio context hasn't been instantiated.
|
||||
pub(crate) const CONTEXT_MISSING_ERROR: &str =
|
||||
"there is no reactor running, must be called from the context of a Tokio 1.x runtime";
|
||||
|
||||
// some combinations of features might not use this
|
||||
#[allow(dead_code)]
|
||||
/// Error string explaining that the Tokio context is shutting down and cannot drive timers.
|
||||
pub(crate) const RUNTIME_SHUTTING_DOWN_ERROR: &str =
|
||||
"A Tokio 1.x context was found, but it is being shutdown.";
|
||||
|
||||
@@ -481,3 +481,62 @@ async fn mut_on_left_hand_side() {
|
||||
.await;
|
||||
assert_eq!(v, 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn biased_one_not_ready() {
|
||||
let (_tx1, rx1) = oneshot::channel::<i32>();
|
||||
let (tx2, rx2) = oneshot::channel::<i32>();
|
||||
let (tx3, rx3) = oneshot::channel::<i32>();
|
||||
|
||||
tx2.send(2).unwrap();
|
||||
tx3.send(3).unwrap();
|
||||
|
||||
let v = tokio::select! {
|
||||
biased;
|
||||
|
||||
_ = rx1 => unreachable!(),
|
||||
res = rx2 => {
|
||||
assert_ok!(res)
|
||||
},
|
||||
_ = rx3 => {
|
||||
panic!("This branch should never be activated because `rx2` should be polled before `rx3` due to `biased;`.")
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(2, v);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn biased_eventually_ready() {
|
||||
use tokio::task::yield_now;
|
||||
|
||||
let one = async {};
|
||||
let two = async { yield_now().await };
|
||||
let three = async { yield_now().await };
|
||||
|
||||
let mut count = 0u8;
|
||||
|
||||
tokio::pin!(one, two, three);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
_ = &mut two, if count < 2 => {
|
||||
count += 1;
|
||||
assert_eq!(count, 2);
|
||||
}
|
||||
_ = &mut three, if count < 3 => {
|
||||
count += 1;
|
||||
assert_eq!(count, 3);
|
||||
}
|
||||
_ = &mut one, if count < 1 => {
|
||||
count += 1;
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
else => break,
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(count, 3);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user