mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46f974d8cf | ||
|
|
018d0450c7 | ||
|
|
ee09e04c31 | ||
|
|
d07027f5bc | ||
|
|
2e0372be6f | ||
|
|
eca24068f7 | ||
|
|
795754a846 | ||
|
|
0f17d69303 | ||
|
|
2e7f996f17 | ||
|
|
901f6d26c6 | ||
|
|
a8fda87058 | ||
|
|
d7abdbb315 | ||
|
|
24aac0add3 | ||
|
|
b921fe45ac | ||
|
|
0dc1b71e6e | ||
|
|
d19f2f2d39 | ||
|
|
e106c4d32b | ||
|
|
28d6f4d509 | ||
|
|
d1da6c20d8 | ||
|
|
e629ad7c9a | ||
|
|
36fdccc3bc | ||
|
|
01bb1ecf4d | ||
|
|
36d2233579 | ||
|
|
74fb9e387a | ||
|
|
8b44077ebc | ||
|
|
d6dbefcdc0 | ||
|
|
d96bbf0465 | ||
|
|
09b2653e71 | ||
|
|
061325ba7e | ||
|
|
d7d5d05333 | ||
|
|
1dcfe1cc9b | ||
|
|
5653b4583c | ||
|
|
abf5d28f2c | ||
|
|
a7945b469d | ||
|
|
80ec80165b |
+3
-4
@@ -1,5 +1,7 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image: freebsd-12-4-release-amd64
|
||||
image_family: freebsd-12-4
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2022-10-25
|
||||
@@ -11,7 +13,6 @@ env:
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 64-bit
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
@@ -26,7 +27,6 @@ task:
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
@@ -44,7 +44,6 @@ task:
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
- test-unstable
|
||||
- miri
|
||||
- asan
|
||||
- semver
|
||||
- cross-check
|
||||
- cross-test
|
||||
- no-atomic-u64
|
||||
@@ -208,7 +209,6 @@ jobs:
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
PROPTEST_CASES: 10
|
||||
|
||||
asan:
|
||||
name: asan
|
||||
@@ -230,6 +230,29 @@ jobs:
|
||||
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
|
||||
TRYBUILD: overwrite
|
||||
|
||||
semver:
|
||||
name: semver
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-semver-checks
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-semver-checks
|
||||
- name: Check semver compatibility
|
||||
run: |
|
||||
cargo semver-checks check-release \
|
||||
--release-type minor \
|
||||
--exclude benches \
|
||||
--exclude examples \
|
||||
--exclude stress-test \
|
||||
--exclude tests-build \
|
||||
--exclude tests-integration
|
||||
|
||||
cross-check:
|
||||
name: cross-check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
+26
-1
@@ -187,7 +187,7 @@ LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" PROPTEST_CASES=10 \
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
```
|
||||
|
||||
@@ -209,6 +209,31 @@ utilities available to use in tests, no matter the crate being tested.
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
````
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
|
||||
@@ -27,6 +27,16 @@ name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_mpsc_oneshot"
|
||||
path = "sync_mpsc_oneshot.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_watch"
|
||||
path = "sync_watch.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
fn request_reply_current_thread(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
}
|
||||
|
||||
fn request_reply_multi_threaded(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
}
|
||||
|
||||
fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
let tx = rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
|
||||
tokio::spawn(async move {
|
||||
while let Some(reply) = rx.recv().await {
|
||||
reply.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
tx
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
sync_mpsc_oneshot_group,
|
||||
request_reply_current_thread,
|
||||
request_reply_multi_threaded,
|
||||
);
|
||||
|
||||
benchmark_main!(sync_mpsc_oneshot_group);
|
||||
@@ -0,0 +1,64 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use rand::prelude::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_resubscribe(b: &mut Bencher) {
|
||||
const NTASK: u64 = 1000;
|
||||
|
||||
let rt = rt();
|
||||
let (snd, rcv) = watch::channel(0i32);
|
||||
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
|
||||
for n in 0..NTASK {
|
||||
let mut rcv = rcv.clone();
|
||||
let wg = wg.clone();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n);
|
||||
rt.spawn(async move {
|
||||
while rcv.changed().await.is_ok() {
|
||||
let _ = *rcv.borrow(); // contend on rwlock
|
||||
let r = do_work(&mut rng);
|
||||
let _ = black_box(r);
|
||||
if wg.0.fetch_sub(1, Ordering::Release) == 1 {
|
||||
wg.1.notify_one();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..100 {
|
||||
assert_eq!(wg.0.fetch_add(NTASK, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(contention, contention_resubscribe);
|
||||
|
||||
bencher::benchmark_main!(contention);
|
||||
+1
-1
@@ -25,7 +25,7 @@ once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.42.0"
|
||||
version = "0.45"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# 0.1.12 (January 20, 2022)
|
||||
|
||||
- time: remove `Unpin` bound on `Throttle` methods ([#5105])
|
||||
- time: document that `throttle` operates on ms granularity ([#5101])
|
||||
|
||||
[#5105]: https://github.com/tokio-rs/tokio/pull/5105
|
||||
[#5101]: https://github.com/tokio-rs/tokio/pull/5101
|
||||
|
||||
# 0.1.11 (October 11, 2022)
|
||||
|
||||
- time: allow `StreamExt::chunks_timeout` outside of a runtime ([#5036])
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -38,9 +38,6 @@ parking_lot = "0.12.0"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
proptest = "1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tokio-stream-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
tokio-test = { path = "../../tokio-test" }
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
path = ".."
|
||||
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_stream_map"
|
||||
path = "fuzz_targets/fuzz_stream_map.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,80 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::pin::Pin;
|
||||
|
||||
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
|
||||
macro_rules! assert_ready_some {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
Some(v) => v,
|
||||
None => panic!("expected `Some`, got `None`"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_ready_none {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
None => {}
|
||||
Some(v) => panic!("expected `None`, got `Some({:?})`", v),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T::Item>> {
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, is_empty) in data.iter().map(|x| *x != 0).enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -10,8 +10,9 @@ 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,
|
||||
/// regardless of whether it was the initial value or sent afterwards.
|
||||
/// This stream will start by yielding the current value when the WatchStream is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards,
|
||||
/// unless you use [`WatchStream<T>::from_changes`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -40,6 +41,28 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::new(rx);
|
||||
///
|
||||
/// // existing rx output with "hello" is ignored here
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Example with [`WatchStream<T>::from_changes`]:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use futures::future::FutureExt;
|
||||
/// use tokio::sync::watch;
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
///
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::from_changes(rx);
|
||||
///
|
||||
/// // no output from rx is available at this point - let's check this:
|
||||
/// assert!(rx.next().now_or_never().is_none());
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
@@ -66,6 +89,13 @@ impl<T: 'static + Clone + Send + Sync> WatchStream<T> {
|
||||
inner: ReusableBoxFuture::new(async move { (Ok(()), rx) }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `WatchStream` that waits for the value to be changed.
|
||||
pub fn from_changes(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
|
||||
|
||||
@@ -325,63 +325,6 @@ fn one_ready_many_none() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn fuzz_pending_complete_mix(kinds: Vec<bool>) {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
|
||||
-> Poll<Option<T::Item>>
|
||||
{
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, &is_empty) in kinds.iter().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
use tokio::sync::watch;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_test::assert_pending;
|
||||
use tokio_test::task::spawn;
|
||||
|
||||
#[tokio::test]
|
||||
async fn message_not_twice() {
|
||||
async fn watch_stream_message_not_twice() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut counter = 0;
|
||||
@@ -27,3 +29,29 @@ async fn message_not_twice() {
|
||||
drop(tx);
|
||||
task.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watch_stream_from_rx() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut stream = WatchStream::from(rx);
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "hello");
|
||||
|
||||
tx.send("bye").unwrap();
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "bye");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watch_stream_from_changes() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut stream = WatchStream::from_changes(rx);
|
||||
|
||||
assert_pending!(spawn(&mut stream).poll_next());
|
||||
|
||||
tx.send("bye").unwrap();
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "bye");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
# 0.7.7 (February 12, 2023)
|
||||
|
||||
This release reverts the removal of the `Encoder` bound on the `FramedParts`
|
||||
constructor from [#5280] since it turned out to be a breaking change. ([#5450])
|
||||
|
||||
[#5450]: https://github.com/tokio-rs/tokio/pull/5450
|
||||
|
||||
# 0.7.6 (February 10, 2023)
|
||||
|
||||
This release fixes a compilation failure in 0.7.5 when it is used together with
|
||||
Tokio version 1.21 and unstable features are enabled. ([#5445])
|
||||
|
||||
[#5445]: https://github.com/tokio-rs/tokio/pull/5445
|
||||
|
||||
# 0.7.5 (February 9, 2023)
|
||||
|
||||
This release fixes an accidental breaking change where `UnwindSafe` was
|
||||
accidentally removed from `CancellationToken`.
|
||||
|
||||
### Added
|
||||
- codec: add `Framed::backpressure_boundary` ([#5124])
|
||||
- io: add `InspectReader` and `InspectWriter` ([#5033])
|
||||
- io: add `tokio_util::io::{CopyToBytes, SinkWriter}` ([#5070], [#5436])
|
||||
- io: impl `std::io::BufRead` on `SyncIoBridge` ([#5265])
|
||||
- sync: add `PollSemaphore::poll_acquire_many` ([#5137])
|
||||
- sync: add owned future for `CancellationToken` ([#5153])
|
||||
- time: add `DelayQueue::try_remove` ([#5052])
|
||||
|
||||
### Fixed
|
||||
- codec: fix `LengthDelimitedCodec` buffer over-reservation ([#4997])
|
||||
- sync: impl `UnwindSafe` on `CancellationToken` ([#5438])
|
||||
- util: remove `Encoder` bound on `FramedParts` constructor ([#5280])
|
||||
|
||||
### Documented
|
||||
- io: add lines example for `StreamReader` ([#5145])
|
||||
|
||||
[#4997]: https://github.com/tokio-rs/tokio/pull/4997
|
||||
[#5033]: https://github.com/tokio-rs/tokio/pull/5033
|
||||
[#5052]: https://github.com/tokio-rs/tokio/pull/5052
|
||||
[#5070]: https://github.com/tokio-rs/tokio/pull/5070
|
||||
[#5124]: https://github.com/tokio-rs/tokio/pull/5124
|
||||
[#5137]: https://github.com/tokio-rs/tokio/pull/5137
|
||||
[#5145]: https://github.com/tokio-rs/tokio/pull/5145
|
||||
[#5153]: https://github.com/tokio-rs/tokio/pull/5153
|
||||
[#5265]: https://github.com/tokio-rs/tokio/pull/5265
|
||||
[#5280]: https://github.com/tokio-rs/tokio/pull/5280
|
||||
[#5436]: https://github.com/tokio-rs/tokio/pull/5436
|
||||
[#5438]: https://github.com/tokio-rs/tokio/pull/5438
|
||||
|
||||
# 0.7.4 (September 8, 2022)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.4"
|
||||
version = "0.7.7"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -34,7 +34,7 @@ rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.21.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.22.0", path = "../tokio", features = ["sync"] }
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
|
||||
@@ -368,7 +368,10 @@ pub struct FramedParts<T, U> {
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
pub fn new<I>(io: T, codec: U) -> FramedParts<T, U>
|
||||
where
|
||||
U: Encoder<I>,
|
||||
{
|
||||
FramedParts {
|
||||
io,
|
||||
codec,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
@@ -98,19 +99,11 @@ where
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
let mut this = self.project();
|
||||
match this.inner.as_mut().poll_ready(cx) {
|
||||
Poll::Ready(Ok(())) => {
|
||||
if let Err(e) = this.inner.as_mut().start_send(buf) {
|
||||
Poll::Ready(Err(e.into()))
|
||||
} else {
|
||||
Poll::Ready(Ok(buf.len()))
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
|
||||
Poll::Pending => {
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
ready!(this.inner.as_mut().poll_ready(cx).map_err(Into::into))?;
|
||||
match this.inner.as_mut().start_send(buf) {
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(e) => Poll::Ready(Err(e.into())),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ pub struct CancellationToken {
|
||||
inner: Arc<tree_node::TreeNode>,
|
||||
}
|
||||
|
||||
impl std::panic::UnwindSafe for CancellationToken {}
|
||||
impl std::panic::RefUnwindSafe for CancellationToken {}
|
||||
|
||||
pin_project! {
|
||||
/// A Future that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled.
|
||||
|
||||
+2
-3
@@ -125,11 +125,11 @@ libc = { version = "0.2.42" }
|
||||
nix = { version = "0.26", default-features = false, features = ["fs", "socket"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.42.0"
|
||||
version = "0.45"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(docsrs)'.dependencies.windows-sys]
|
||||
version = "0.42.0"
|
||||
version = "0.45"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security_Authorization",
|
||||
@@ -147,7 +147,6 @@ tempfile = "3.1.0"
|
||||
async-stream = "0.3"
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dev-dependencies]
|
||||
proptest = "1"
|
||||
socket2 = "0.4"
|
||||
|
||||
[target.'cfg(not(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown")))'.dev-dependencies]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tokio-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
[dependencies.tokio]
|
||||
path = ".."
|
||||
features = ["fs","net","process","rt","sync","signal","time"]
|
||||
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_linked_list"
|
||||
path = "fuzz_targets/fuzz_linked_list.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,7 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
tokio::fuzz::fuzz_linked_list(data);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
pub use crate::util::linked_list::tests::fuzz_linked_list;
|
||||
@@ -65,8 +65,8 @@ use std::{task::Context, task::Poll};
|
||||
/// # Examples
|
||||
///
|
||||
/// This example shows how to turn [`std::net::TcpStream`] asynchronous using
|
||||
/// `AsyncFd`. It implements `read` as an async fn, and `AsyncWrite` as a trait
|
||||
/// to show how to implement both approaches.
|
||||
/// `AsyncFd`. It implements the read/write operations both as an `async fn`
|
||||
/// and using the IO traits [`AsyncRead`] and [`AsyncWrite`].
|
||||
///
|
||||
/// ```no_run
|
||||
/// use futures::ready;
|
||||
@@ -74,7 +74,7 @@ use std::{task::Context, task::Poll};
|
||||
/// use std::net::TcpStream;
|
||||
/// use std::pin::Pin;
|
||||
/// use std::task::{Context, Poll};
|
||||
/// use tokio::io::AsyncWrite;
|
||||
/// use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
///
|
||||
/// pub struct AsyncTcpStream {
|
||||
@@ -99,6 +99,39 @@ use std::{task::Context, task::Poll};
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub async fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
/// loop {
|
||||
/// let mut guard = self.inner.writable().await?;
|
||||
///
|
||||
/// match guard.try_io(|inner| inner.get_ref().write(buf)) {
|
||||
/// Ok(result) => return result,
|
||||
/// Err(_would_block) => continue,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl AsyncRead for AsyncTcpStream {
|
||||
/// fn poll_read(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>,
|
||||
/// buf: &mut ReadBuf<'_>
|
||||
/// ) -> Poll<io::Result<()>> {
|
||||
/// loop {
|
||||
/// let mut guard = ready!(self.inner.poll_read_ready(cx))?;
|
||||
///
|
||||
/// let unfilled = buf.initialize_unfilled();
|
||||
/// match guard.try_io(|inner| inner.get_ref().read(unfilled)) {
|
||||
/// Ok(Ok(len)) => {
|
||||
/// buf.advance(len);
|
||||
/// return Poll::Ready(Ok(()));
|
||||
/// },
|
||||
/// Ok(Err(err)) => return Poll::Ready(Err(err)),
|
||||
/// Err(_would_block) => continue,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl AsyncWrite for AsyncTcpStream {
|
||||
@@ -139,6 +172,8 @@ use std::{task::Context, task::Poll};
|
||||
/// [`writable`]: method@Self::writable
|
||||
/// [`AsyncFdReadyGuard`]: struct@self::AsyncFdReadyGuard
|
||||
/// [`TcpStream::poll_read_ready`]: struct@crate::net::TcpStream
|
||||
/// [`AsyncRead`]: trait@crate::io::AsyncRead
|
||||
/// [`AsyncWrite`]: trait@crate::io::AsyncWrite
|
||||
pub struct AsyncFd<T: AsRawFd> {
|
||||
registration: Registration,
|
||||
inner: Option<T>,
|
||||
|
||||
@@ -631,3 +631,6 @@ cfg_macros! {
|
||||
#[cfg(feature = "io-util")]
|
||||
#[cfg(test)]
|
||||
fn is_unpin<T: Unpin>() {}
|
||||
|
||||
#[cfg(fuzzing)]
|
||||
pub mod fuzz;
|
||||
|
||||
@@ -1645,19 +1645,6 @@ impl AsRawHandle for NamedPipeClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to set a boolean flag as a bitfield.
|
||||
macro_rules! bool_flag {
|
||||
($f:expr, $t:expr, $flag:expr) => {{
|
||||
let current = $f;
|
||||
|
||||
if $t {
|
||||
$f = current | $flag;
|
||||
} else {
|
||||
$f = current & !$flag;
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
/// A builder structure for construct a named pipe with named pipe-specific
|
||||
/// options. This is required to use for named pipe servers who wants to modify
|
||||
/// pipe-related options.
|
||||
@@ -1665,8 +1652,17 @@ macro_rules! bool_flag {
|
||||
/// See [`ServerOptions::create`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerOptions {
|
||||
open_mode: u32,
|
||||
pipe_mode: u32,
|
||||
// dwOpenMode
|
||||
access_inbound: bool,
|
||||
access_outbound: bool,
|
||||
first_pipe_instance: bool,
|
||||
write_dac: bool,
|
||||
write_owner: bool,
|
||||
access_system_security: bool,
|
||||
// dwPipeMode
|
||||
pipe_mode: PipeMode,
|
||||
reject_remote_clients: bool,
|
||||
// other options
|
||||
max_instances: u32,
|
||||
out_buffer_size: u32,
|
||||
in_buffer_size: u32,
|
||||
@@ -1687,8 +1683,14 @@ impl ServerOptions {
|
||||
/// ```
|
||||
pub fn new() -> ServerOptions {
|
||||
ServerOptions {
|
||||
open_mode: windows_sys::PIPE_ACCESS_DUPLEX | windows_sys::FILE_FLAG_OVERLAPPED,
|
||||
pipe_mode: windows_sys::PIPE_TYPE_BYTE | windows_sys::PIPE_REJECT_REMOTE_CLIENTS,
|
||||
access_inbound: true,
|
||||
access_outbound: true,
|
||||
first_pipe_instance: false,
|
||||
write_dac: false,
|
||||
write_owner: false,
|
||||
access_system_security: false,
|
||||
pipe_mode: PipeMode::Byte,
|
||||
reject_remote_clients: true,
|
||||
max_instances: windows_sys::PIPE_UNLIMITED_INSTANCES,
|
||||
out_buffer_size: 65536,
|
||||
in_buffer_size: 65536,
|
||||
@@ -1701,14 +1703,11 @@ impl ServerOptions {
|
||||
/// The default pipe mode is [`PipeMode::Byte`]. See [`PipeMode`] for
|
||||
/// documentation of what each mode means.
|
||||
///
|
||||
/// This corresponding to specifying [`dwPipeMode`].
|
||||
/// This corresponds to specifying `PIPE_TYPE_` and `PIPE_READMODE_` in [`dwPipeMode`].
|
||||
///
|
||||
/// [`dwPipeMode`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
|
||||
pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self {
|
||||
let is_msg = matches!(pipe_mode, PipeMode::Message);
|
||||
// Pipe mode is implemented as a bit flag 0x4. Set is message and unset
|
||||
// is byte.
|
||||
bool_flag!(self.pipe_mode, is_msg, windows_sys::PIPE_TYPE_MESSAGE);
|
||||
self.pipe_mode = pipe_mode;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1804,7 +1803,7 @@ impl ServerOptions {
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn access_inbound(&mut self, allowed: bool) -> &mut Self {
|
||||
bool_flag!(self.open_mode, allowed, windows_sys::PIPE_ACCESS_INBOUND);
|
||||
self.access_inbound = allowed;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1902,7 +1901,7 @@ impl ServerOptions {
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn access_outbound(&mut self, allowed: bool) -> &mut Self {
|
||||
bool_flag!(self.open_mode, allowed, windows_sys::PIPE_ACCESS_OUTBOUND);
|
||||
self.access_outbound = allowed;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1970,11 +1969,7 @@ impl ServerOptions {
|
||||
/// [`create`]: ServerOptions::create
|
||||
/// [`FILE_FLAG_FIRST_PIPE_INSTANCE`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance
|
||||
pub fn first_pipe_instance(&mut self, first: bool) -> &mut Self {
|
||||
bool_flag!(
|
||||
self.open_mode,
|
||||
first,
|
||||
windows_sys::FILE_FLAG_FIRST_PIPE_INSTANCE
|
||||
);
|
||||
self.first_pipe_instance = first;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2056,7 +2051,7 @@ impl ServerOptions {
|
||||
///
|
||||
/// [`WRITE_DAC`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
|
||||
pub fn write_dac(&mut self, requested: bool) -> &mut Self {
|
||||
bool_flag!(self.open_mode, requested, windows_sys::WRITE_DAC);
|
||||
self.write_dac = requested;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2066,7 +2061,7 @@ impl ServerOptions {
|
||||
///
|
||||
/// [`WRITE_OWNER`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
|
||||
pub fn write_owner(&mut self, requested: bool) -> &mut Self {
|
||||
bool_flag!(self.open_mode, requested, windows_sys::WRITE_OWNER);
|
||||
self.write_owner = requested;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2076,11 +2071,7 @@ impl ServerOptions {
|
||||
///
|
||||
/// [`ACCESS_SYSTEM_SECURITY`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
|
||||
pub fn access_system_security(&mut self, requested: bool) -> &mut Self {
|
||||
bool_flag!(
|
||||
self.open_mode,
|
||||
requested,
|
||||
windows_sys::ACCESS_SYSTEM_SECURITY
|
||||
);
|
||||
self.access_system_security = requested;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2091,11 +2082,7 @@ impl ServerOptions {
|
||||
///
|
||||
/// [`PIPE_REJECT_REMOTE_CLIENTS`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients
|
||||
pub fn reject_remote_clients(&mut self, reject: bool) -> &mut Self {
|
||||
bool_flag!(
|
||||
self.pipe_mode,
|
||||
reject,
|
||||
windows_sys::PIPE_REJECT_REMOTE_CLIENTS
|
||||
);
|
||||
self.reject_remote_clients = reject;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2241,10 +2228,46 @@ impl ServerOptions {
|
||||
) -> io::Result<NamedPipeServer> {
|
||||
let addr = encode_addr(addr);
|
||||
|
||||
let pipe_mode = {
|
||||
let mut mode = if matches!(self.pipe_mode, PipeMode::Message) {
|
||||
windows_sys::PIPE_TYPE_MESSAGE | windows_sys::PIPE_READMODE_MESSAGE
|
||||
} else {
|
||||
windows_sys::PIPE_TYPE_BYTE | windows_sys::PIPE_READMODE_BYTE
|
||||
};
|
||||
if self.reject_remote_clients {
|
||||
mode |= windows_sys::PIPE_REJECT_REMOTE_CLIENTS;
|
||||
} else {
|
||||
mode |= windows_sys::PIPE_ACCEPT_REMOTE_CLIENTS;
|
||||
}
|
||||
mode
|
||||
};
|
||||
let open_mode = {
|
||||
let mut mode = windows_sys::FILE_FLAG_OVERLAPPED;
|
||||
if self.access_inbound {
|
||||
mode |= windows_sys::PIPE_ACCESS_INBOUND;
|
||||
}
|
||||
if self.access_outbound {
|
||||
mode |= windows_sys::PIPE_ACCESS_OUTBOUND;
|
||||
}
|
||||
if self.first_pipe_instance {
|
||||
mode |= windows_sys::FILE_FLAG_FIRST_PIPE_INSTANCE;
|
||||
}
|
||||
if self.write_dac {
|
||||
mode |= windows_sys::WRITE_DAC;
|
||||
}
|
||||
if self.write_owner {
|
||||
mode |= windows_sys::WRITE_OWNER;
|
||||
}
|
||||
if self.access_system_security {
|
||||
mode |= windows_sys::ACCESS_SYSTEM_SECURITY;
|
||||
}
|
||||
mode
|
||||
};
|
||||
|
||||
let h = windows_sys::CreateNamedPipeW(
|
||||
addr.as_ptr(),
|
||||
self.open_mode,
|
||||
self.pipe_mode,
|
||||
open_mode,
|
||||
pipe_mode,
|
||||
self.max_instances,
|
||||
self.out_buffer_size,
|
||||
self.in_buffer_size,
|
||||
@@ -2266,8 +2289,10 @@ impl ServerOptions {
|
||||
/// See [`ClientOptions::open`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientOptions {
|
||||
desired_access: u32,
|
||||
generic_read: bool,
|
||||
generic_write: bool,
|
||||
security_qos_flags: u32,
|
||||
pipe_mode: PipeMode,
|
||||
}
|
||||
|
||||
impl ClientOptions {
|
||||
@@ -2286,9 +2311,11 @@ impl ClientOptions {
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
desired_access: windows_sys::GENERIC_READ | windows_sys::GENERIC_WRITE,
|
||||
generic_read: true,
|
||||
generic_write: true,
|
||||
security_qos_flags: windows_sys::SECURITY_IDENTIFICATION
|
||||
| windows_sys::SECURITY_SQOS_PRESENT,
|
||||
pipe_mode: PipeMode::Byte,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2299,7 +2326,7 @@ impl ClientOptions {
|
||||
/// [`GENERIC_READ`]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
pub fn read(&mut self, allowed: bool) -> &mut Self {
|
||||
bool_flag!(self.desired_access, allowed, windows_sys::GENERIC_READ);
|
||||
self.generic_read = allowed;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2310,7 +2337,7 @@ impl ClientOptions {
|
||||
/// [`GENERIC_WRITE`]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
pub fn write(&mut self, allowed: bool) -> &mut Self {
|
||||
bool_flag!(self.desired_access, allowed, windows_sys::GENERIC_WRITE);
|
||||
self.generic_write = allowed;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -2341,6 +2368,15 @@ impl ClientOptions {
|
||||
self
|
||||
}
|
||||
|
||||
/// The pipe mode.
|
||||
///
|
||||
/// The default pipe mode is [`PipeMode::Byte`]. See [`PipeMode`] for
|
||||
/// documentation of what each mode means.
|
||||
pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self {
|
||||
self.pipe_mode = pipe_mode;
|
||||
self
|
||||
}
|
||||
|
||||
/// Opens the named pipe identified by `addr`.
|
||||
///
|
||||
/// This opens the client using [`CreateFile`] with the
|
||||
@@ -2419,13 +2455,24 @@ impl ClientOptions {
|
||||
) -> io::Result<NamedPipeClient> {
|
||||
let addr = encode_addr(addr);
|
||||
|
||||
let desired_access = {
|
||||
let mut access = 0;
|
||||
if self.generic_read {
|
||||
access |= windows_sys::GENERIC_READ;
|
||||
}
|
||||
if self.generic_write {
|
||||
access |= windows_sys::GENERIC_WRITE;
|
||||
}
|
||||
access
|
||||
};
|
||||
|
||||
// NB: We could use a platform specialized `OpenOptions` here, but since
|
||||
// we have access to windows_sys it ultimately doesn't hurt to use
|
||||
// `CreateFile` explicitly since it allows the use of our already
|
||||
// well-structured wide `addr` to pass into CreateFileW.
|
||||
let h = windows_sys::CreateFileW(
|
||||
addr.as_ptr(),
|
||||
self.desired_access,
|
||||
desired_access,
|
||||
0,
|
||||
attrs as *mut _,
|
||||
windows_sys::OPEN_EXISTING,
|
||||
@@ -2437,6 +2484,16 @@ impl ClientOptions {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
if matches!(self.pipe_mode, PipeMode::Message) {
|
||||
let mode = windows_sys::PIPE_READMODE_MESSAGE;
|
||||
let result =
|
||||
windows_sys::SetNamedPipeHandleState(h, &mode, ptr::null_mut(), ptr::null_mut());
|
||||
|
||||
if result == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
NamedPipeClient::from_raw_handle(h as _)
|
||||
}
|
||||
|
||||
@@ -2553,48 +2610,3 @@ unsafe fn named_pipe_info(handle: RawHandle) -> io::Result<PipeInfo> {
|
||||
max_instances,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use self::windows_sys::{PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn opts_default_pipe_mode() {
|
||||
let opts = ServerOptions::new();
|
||||
assert_eq!(opts.pipe_mode, PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opts_unset_reject_remote() {
|
||||
let mut opts = ServerOptions::new();
|
||||
opts.reject_remote_clients(false);
|
||||
assert_eq!(opts.pipe_mode & PIPE_REJECT_REMOTE_CLIENTS, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opts_set_pipe_mode_maintains_reject_remote_clients() {
|
||||
let mut opts = ServerOptions::new();
|
||||
opts.pipe_mode(PipeMode::Byte);
|
||||
assert_eq!(opts.pipe_mode, PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS);
|
||||
|
||||
opts.reject_remote_clients(false);
|
||||
opts.pipe_mode(PipeMode::Byte);
|
||||
assert_eq!(opts.pipe_mode, PIPE_TYPE_BYTE);
|
||||
|
||||
opts.reject_remote_clients(true);
|
||||
opts.pipe_mode(PipeMode::Byte);
|
||||
assert_eq!(opts.pipe_mode, PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS);
|
||||
|
||||
opts.reject_remote_clients(false);
|
||||
opts.pipe_mode(PipeMode::Message);
|
||||
assert_eq!(opts.pipe_mode, PIPE_TYPE_MESSAGE);
|
||||
|
||||
opts.reject_remote_clients(true);
|
||||
opts.pipe_mode(PipeMode::Message);
|
||||
assert_eq!(
|
||||
opts.pipe_mode,
|
||||
PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+67
-28
@@ -309,7 +309,8 @@ impl Command {
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::process::Command;
|
||||
/// let command = Command::new("sh");
|
||||
/// let mut command = Command::new("sh");
|
||||
/// # let _ = command.output(); // assert borrow checker
|
||||
/// ```
|
||||
///
|
||||
/// [rust-lang/rust#37519]: https://github.com/rust-lang/rust/issues/37519
|
||||
@@ -328,16 +329,20 @@ impl Command {
|
||||
/// Only one argument can be passed per use. So instead of:
|
||||
///
|
||||
/// ```no_run
|
||||
/// tokio::process::Command::new("sh")
|
||||
/// .arg("-C /path/to/repo");
|
||||
/// let mut command = tokio::process::Command::new("sh");
|
||||
/// command.arg("-C /path/to/repo");
|
||||
///
|
||||
/// # let _ = command.output(); // assert borrow checker
|
||||
/// ```
|
||||
///
|
||||
/// usage would be:
|
||||
///
|
||||
/// ```no_run
|
||||
/// tokio::process::Command::new("sh")
|
||||
/// .arg("-C")
|
||||
/// .arg("/path/to/repo");
|
||||
/// let mut command = tokio::process::Command::new("sh");
|
||||
/// command.arg("-C");
|
||||
/// command.arg("/path/to/repo");
|
||||
///
|
||||
/// # let _ = command.output(); // assert borrow checker
|
||||
/// ```
|
||||
///
|
||||
/// To pass multiple arguments see [`args`].
|
||||
@@ -349,11 +354,15 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// let output = Command::new("ls")
|
||||
/// .arg("-l")
|
||||
/// .arg("-a");
|
||||
/// .arg("-a")
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
///
|
||||
/// ```
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
|
||||
self.std.arg(arg);
|
||||
@@ -371,10 +380,13 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .args(&["-l", "-a"]);
|
||||
/// let output = Command::new("ls")
|
||||
/// .args(&["-l", "-a"])
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn args<I, S>(&mut self, args: I) -> &mut Command
|
||||
where
|
||||
@@ -395,10 +407,13 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .env("PATH", "/bin");
|
||||
/// let output = Command::new("ls")
|
||||
/// .env("PATH", "/bin")
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
|
||||
where
|
||||
@@ -416,6 +431,7 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
/// use std::process::{Stdio};
|
||||
/// use std::env;
|
||||
@@ -426,11 +442,13 @@ impl Command {
|
||||
/// k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH"
|
||||
/// ).collect();
|
||||
///
|
||||
/// let command = Command::new("printenv")
|
||||
/// let output = Command::new("printenv")
|
||||
/// .stdin(Stdio::null())
|
||||
/// .stdout(Stdio::inherit())
|
||||
/// .env_clear()
|
||||
/// .envs(&filtered_env);
|
||||
/// .envs(&filtered_env)
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Command
|
||||
where
|
||||
@@ -449,10 +467,13 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .env_remove("PATH");
|
||||
/// let output = Command::new("ls")
|
||||
/// .env_remove("PATH")
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Command {
|
||||
self.std.env_remove(key);
|
||||
@@ -466,10 +487,13 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .env_clear();
|
||||
/// let output = Command::new("ls")
|
||||
/// .env_clear()
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn env_clear(&mut self) -> &mut Command {
|
||||
self.std.env_clear();
|
||||
@@ -493,10 +517,13 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .current_dir("/bin");
|
||||
/// let output = Command::new("ls")
|
||||
/// .current_dir("/bin")
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Command {
|
||||
self.std.current_dir(dir);
|
||||
@@ -516,11 +543,14 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use std::process::{Stdio};
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .stdin(Stdio::null());
|
||||
/// let output = Command::new("ls")
|
||||
/// .stdin(Stdio::null())
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
|
||||
self.std.stdin(cfg);
|
||||
@@ -540,11 +570,14 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
/// use std::process::Stdio;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .stdout(Stdio::null());
|
||||
/// let output = Command::new("ls")
|
||||
/// .stdout(Stdio::null())
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
|
||||
self.std.stdout(cfg);
|
||||
@@ -564,11 +597,14 @@ impl Command {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
/// use std::process::{Stdio};
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .stderr(Stdio::null());
|
||||
/// let output = Command::new("ls")
|
||||
/// .stderr(Stdio::null())
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
|
||||
self.std.stderr(cfg);
|
||||
@@ -707,10 +743,13 @@ impl Command {
|
||||
/// [`tokio::process::Command`]: crate::process::Command
|
||||
///
|
||||
/// ```no_run
|
||||
/// # async fn test() { // allow using await
|
||||
/// use tokio::process::Command;
|
||||
///
|
||||
/// let command = Command::new("ls")
|
||||
/// .process_group(0);
|
||||
/// let output = Command::new("ls")
|
||||
/// .process_group(0)
|
||||
/// .output().await.unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(unix)]
|
||||
#[cfg(tokio_unstable)]
|
||||
|
||||
@@ -79,7 +79,7 @@ tokio_thread_local! {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg(any(feature = "macros", all(feature = "sync", feature = "rt")))]
|
||||
pub(crate) fn thread_rng_n(n: u32) -> u32 {
|
||||
CONTEXT.with(|ctx| ctx.rng.fastrand_n(n))
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ impl Driver {
|
||||
|
||||
let clock = create_clock(cfg.enable_pause_time, cfg.start_paused);
|
||||
|
||||
let (time_driver, time_handle) =
|
||||
create_time_driver(cfg.enable_time, io_stack, clock.clone());
|
||||
let (time_driver, time_handle) = create_time_driver(cfg.enable_time, io_stack, &clock);
|
||||
|
||||
Ok((
|
||||
Self { inner: time_driver },
|
||||
@@ -111,10 +110,8 @@ impl Handle {
|
||||
.expect("A Tokio 1.x context was found, but timers are disabled. Call `enable_time` on the runtime builder to enable timers.")
|
||||
}
|
||||
|
||||
cfg_test_util! {
|
||||
pub(crate) fn clock(&self) -> &Clock {
|
||||
&self.clock
|
||||
}
|
||||
pub(crate) fn clock(&self) -> &Clock {
|
||||
&self.clock
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +286,7 @@ cfg_time! {
|
||||
fn create_time_driver(
|
||||
enable: bool,
|
||||
io_stack: IoStack,
|
||||
clock: Clock,
|
||||
clock: &Clock,
|
||||
) -> (TimeDriver, TimeHandle) {
|
||||
if enable {
|
||||
let (driver, handle) = crate::runtime::time::Driver::new(io_stack, clock);
|
||||
@@ -337,7 +334,7 @@ cfg_not_time! {
|
||||
fn create_time_driver(
|
||||
_enable: bool,
|
||||
io_stack: IoStack,
|
||||
_clock: Clock,
|
||||
_clock: &Clock,
|
||||
) -> (TimeDriver, TimeHandle) {
|
||||
(io_stack, ())
|
||||
}
|
||||
|
||||
@@ -156,9 +156,11 @@
|
||||
//! multi-thread scheduler spawns threads to schedule tasks and for `spawn_blocking`
|
||||
//! calls.
|
||||
//!
|
||||
//! While the `Runtime` is active, threads may shutdown after periods of being
|
||||
//! idle. Once `Runtime` is dropped, all runtime threads are forcibly shutdown.
|
||||
//! Any tasks that have not yet completed will be dropped.
|
||||
//! While the `Runtime` is active, threads may shut down after periods of being
|
||||
//! idle. Once `Runtime` is dropped, all runtime threads have usually been
|
||||
//! terminated, but in the presence of unstoppable spawned work are not
|
||||
//! guaranteed to have been terminated. See the
|
||||
//! [struct level documentation](Runtime#shutdown) for more details.
|
||||
//!
|
||||
//! [tasks]: crate::task
|
||||
//! [`Runtime`]: Runtime
|
||||
|
||||
@@ -24,16 +24,29 @@ cfg_rt_multi_thread! {
|
||||
///
|
||||
/// # Shutdown
|
||||
///
|
||||
/// Shutting down the runtime is done by dropping the value. The current
|
||||
/// thread will block until the shut down operation has completed.
|
||||
/// Shutting down the runtime is done by dropping the value, or calling
|
||||
/// [`Runtime::shutdown_background`] or [`Runtime::shutdown_timeout`].
|
||||
///
|
||||
/// * Drain any scheduled work queues.
|
||||
/// * Drop any futures that have not yet completed.
|
||||
/// * Drop the reactor.
|
||||
/// Tasks spawned through [`Runtime::spawn`] keep running until they yield.
|
||||
/// Then they are dropped. They are not *guaranteed* to run to completion, but
|
||||
/// *might* do so if they do not yield until completion.
|
||||
///
|
||||
/// Once the reactor has dropped, any outstanding I/O resources bound to
|
||||
/// that reactor will no longer function. Calling any method on them will
|
||||
/// result in an error.
|
||||
/// Blocking functions spawned through [`Runtime::spawn_blocking`] keep running
|
||||
/// until they return.
|
||||
///
|
||||
/// The thread initiating the shutdown blocks until all spawned work has been
|
||||
/// stopped. This can take an indefinite amount of time. The `Drop`
|
||||
/// implementation waits forever for this.
|
||||
///
|
||||
/// `shutdown_background` and `shutdown_timeout` can be used if waiting forever
|
||||
/// is undesired. When the timeout is reached, spawned work that did not stop
|
||||
/// in time and threads running it are leaked. The work continues to run until
|
||||
/// one of the stopping conditions is fulfilled, but the thread initiating the
|
||||
/// shutdown is unblocked.
|
||||
///
|
||||
/// Once the runtime has been dropped, any outstanding I/O resources bound to
|
||||
/// it will no longer function. Calling any method on them will result in an
|
||||
/// error.
|
||||
///
|
||||
/// # Sharing
|
||||
///
|
||||
@@ -322,18 +335,9 @@ impl Runtime {
|
||||
}
|
||||
|
||||
/// Shuts down the runtime, waiting for at most `duration` for all spawned
|
||||
/// task to shutdown.
|
||||
/// work to stop.
|
||||
///
|
||||
/// Usually, dropping a `Runtime` handle is sufficient as tasks are able to
|
||||
/// shutdown in a timely fashion. However, dropping a `Runtime` will wait
|
||||
/// indefinitely for all tasks to terminate, and there are cases where a long
|
||||
/// blocking task has been spawned, which can block dropping `Runtime`.
|
||||
///
|
||||
/// In this case, calling `shutdown_timeout` with an explicit wait timeout
|
||||
/// can work. The `shutdown_timeout` will signal all tasks to shutdown and
|
||||
/// will wait for at most `duration` for all spawned tasks to terminate. If
|
||||
/// `timeout` elapses before all tasks are dropped, the function returns and
|
||||
/// outstanding tasks are potentially leaked.
|
||||
/// See the [struct level documentation](Runtime#shutdown) for more details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -362,7 +366,7 @@ impl Runtime {
|
||||
self.blocking_pool.shutdown(Some(duration));
|
||||
}
|
||||
|
||||
/// Shuts down the runtime, without waiting for any spawned tasks to shutdown.
|
||||
/// Shuts down the runtime, without waiting for any spawned work to stop.
|
||||
///
|
||||
/// This can be useful if you want to drop a runtime from within another runtime.
|
||||
/// Normally, dropping a runtime will block indefinitely for spawned blocking tasks
|
||||
@@ -373,6 +377,8 @@ impl Runtime {
|
||||
/// may result in a resource leak (in that any blocking tasks are still running until they
|
||||
/// return.
|
||||
///
|
||||
/// See the [struct level documentation](Runtime#shutdown) for more details.
|
||||
///
|
||||
/// This function is equivalent to calling `shutdown_timeout(Duration::from_nanos(0))`.
|
||||
///
|
||||
/// ```
|
||||
|
||||
+58
-139
@@ -94,7 +94,7 @@ pub(super) struct StateCell {
|
||||
/// without holding the driver lock is undefined behavior.
|
||||
result: UnsafeCell<TimerResult>,
|
||||
/// The currently-registered waker
|
||||
waker: CachePadded<AtomicWaker>,
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl Default for StateCell {
|
||||
@@ -114,7 +114,7 @@ impl StateCell {
|
||||
Self {
|
||||
state: AtomicU64::new(STATE_DEREGISTERED),
|
||||
result: UnsafeCell::new(Ok(())),
|
||||
waker: CachePadded(AtomicWaker::new()),
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ impl StateCell {
|
||||
// We must register first. This ensures that either `fire` will
|
||||
// observe the new waker, or we will observe a racing fire to have set
|
||||
// the state, or both.
|
||||
self.waker.0.register_by_ref(waker);
|
||||
self.waker.register_by_ref(waker);
|
||||
|
||||
self.read_state()
|
||||
}
|
||||
@@ -227,7 +227,7 @@ impl StateCell {
|
||||
|
||||
self.state.store(STATE_DEREGISTERED, Ordering::Release);
|
||||
|
||||
self.waker.0.take_waker()
|
||||
self.waker.take_waker()
|
||||
}
|
||||
|
||||
/// Marks the timer as registered (poll will return None) and sets the
|
||||
@@ -298,9 +298,11 @@ pub(crate) struct TimerEntry {
|
||||
/// This is manipulated only under the inner mutex. TODO: Can we use loom
|
||||
/// cells for this?
|
||||
inner: StdUnsafeCell<TimerShared>,
|
||||
/// Initial deadline for the timer. This is used to register on the first
|
||||
/// Deadline for the timer. This is used to register on the first
|
||||
/// poll, as we can't register prior to being pinned.
|
||||
initial_deadline: Option<Instant>,
|
||||
deadline: Instant,
|
||||
/// Whether the deadline has been registered.
|
||||
registered: bool,
|
||||
/// Ensure the type is !Unpin
|
||||
_m: std::marker::PhantomPinned,
|
||||
}
|
||||
@@ -329,11 +331,20 @@ pub(super) type EntryList = crate::util::linked_list::LinkedList<TimerShared, Ti
|
||||
/// frontend (`Entry`) and driver backend.
|
||||
///
|
||||
/// Note that this structure is located inside the `TimerEntry` structure.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct TimerShared {
|
||||
/// Data manipulated by the driver thread itself, only.
|
||||
driver_state: CachePadded<TimerSharedPadded>,
|
||||
/// A link within the doubly-linked list of timers on a particular level and
|
||||
/// slot. Valid only if state is equal to Registered.
|
||||
///
|
||||
/// Only accessed under the entry lock.
|
||||
pointers: linked_list::Pointers<TimerShared>,
|
||||
|
||||
/// The expiration time for which this entry is currently registered.
|
||||
/// Generally owned by the driver, but is accessed by the entry when not
|
||||
/// registered.
|
||||
cached_when: AtomicU64,
|
||||
|
||||
/// The true expiration time. Set by the timer future, read by the driver.
|
||||
true_when: AtomicU64,
|
||||
|
||||
/// Current state. This records whether the timer entry is currently under
|
||||
/// the ownership of the driver, and if not, its current state (not
|
||||
@@ -343,10 +354,23 @@ pub(crate) struct TimerShared {
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
|
||||
unsafe impl Send for TimerShared {}
|
||||
unsafe impl Sync for TimerShared {}
|
||||
|
||||
impl std::fmt::Debug for TimerShared {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TimerShared")
|
||||
.field("when", &self.true_when.load(Ordering::Relaxed))
|
||||
.field("cached_when", &self.cached_when.load(Ordering::Relaxed))
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
generate_addr_of_methods! {
|
||||
impl<> TimerShared {
|
||||
unsafe fn addr_of_pointers(self: NonNull<Self>) -> NonNull<linked_list::Pointers<TimerShared>> {
|
||||
&self.driver_state.0.pointers
|
||||
&self.pointers
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,8 +378,10 @@ generate_addr_of_methods! {
|
||||
impl TimerShared {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
cached_when: AtomicU64::new(0),
|
||||
true_when: AtomicU64::new(0),
|
||||
pointers: linked_list::Pointers::new(),
|
||||
state: StateCell::default(),
|
||||
driver_state: CachePadded(TimerSharedPadded::new()),
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
}
|
||||
@@ -363,7 +389,7 @@ impl TimerShared {
|
||||
/// Gets the cached time-of-expiration value.
|
||||
pub(super) fn cached_when(&self) -> u64 {
|
||||
// Cached-when is only accessed under the driver lock, so we can use relaxed
|
||||
self.driver_state.0.cached_when.load(Ordering::Relaxed)
|
||||
self.cached_when.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Gets the true time-of-expiration value, and copies it into the cached
|
||||
@@ -374,10 +400,7 @@ impl TimerShared {
|
||||
pub(super) unsafe fn sync_when(&self) -> u64 {
|
||||
let true_when = self.true_when();
|
||||
|
||||
self.driver_state
|
||||
.0
|
||||
.cached_when
|
||||
.store(true_when, Ordering::Relaxed);
|
||||
self.cached_when.store(true_when, Ordering::Relaxed);
|
||||
|
||||
true_when
|
||||
}
|
||||
@@ -387,10 +410,7 @@ impl TimerShared {
|
||||
/// SAFETY: Must be called with the driver lock held, and when this entry is
|
||||
/// not in any timer wheel lists.
|
||||
unsafe fn set_cached_when(&self, when: u64) {
|
||||
self.driver_state
|
||||
.0
|
||||
.cached_when
|
||||
.store(when, Ordering::Relaxed);
|
||||
self.cached_when.store(when, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Returns the true time-of-expiration value, with relaxed memory ordering.
|
||||
@@ -405,7 +425,7 @@ impl TimerShared {
|
||||
/// in the timer wheel.
|
||||
pub(super) unsafe fn set_expiration(&self, t: u64) {
|
||||
self.state.set_expiration(t);
|
||||
self.driver_state.0.cached_when.store(t, Ordering::Relaxed);
|
||||
self.cached_when.store(t, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Sets the true time-of-expiration only if it is after the current.
|
||||
@@ -429,48 +449,6 @@ impl TimerShared {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional shared state between the driver and the timer which is cache
|
||||
/// padded. This contains the information that the driver thread accesses most
|
||||
/// frequently to minimize contention. In particular, we move it away from the
|
||||
/// waker, as the waker is updated on every poll.
|
||||
struct TimerSharedPadded {
|
||||
/// A link within the doubly-linked list of timers on a particular level and
|
||||
/// slot. Valid only if state is equal to Registered.
|
||||
///
|
||||
/// Only accessed under the entry lock.
|
||||
pointers: linked_list::Pointers<TimerShared>,
|
||||
|
||||
/// The expiration time for which this entry is currently registered.
|
||||
/// Generally owned by the driver, but is accessed by the entry when not
|
||||
/// registered.
|
||||
cached_when: AtomicU64,
|
||||
|
||||
/// The true expiration time. Set by the timer future, read by the driver.
|
||||
true_when: AtomicU64,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TimerSharedPadded {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TimerSharedPadded")
|
||||
.field("when", &self.true_when.load(Ordering::Relaxed))
|
||||
.field("cached_when", &self.cached_when.load(Ordering::Relaxed))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerSharedPadded {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
cached_when: AtomicU64::new(0),
|
||||
true_when: AtomicU64::new(0),
|
||||
pointers: linked_list::Pointers::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for TimerShared {}
|
||||
unsafe impl Sync for TimerShared {}
|
||||
|
||||
unsafe impl linked_list::Link for TimerShared {
|
||||
type Handle = TimerHandle;
|
||||
|
||||
@@ -504,7 +482,8 @@ impl TimerEntry {
|
||||
Self {
|
||||
driver,
|
||||
inner: StdUnsafeCell::new(TimerShared::new()),
|
||||
initial_deadline: Some(deadline),
|
||||
deadline,
|
||||
registered: false,
|
||||
_m: std::marker::PhantomPinned,
|
||||
}
|
||||
}
|
||||
@@ -513,8 +492,12 @@ impl TimerEntry {
|
||||
unsafe { &*self.inner.get() }
|
||||
}
|
||||
|
||||
pub(crate) fn deadline(&self) -> Instant {
|
||||
self.deadline
|
||||
}
|
||||
|
||||
pub(crate) fn is_elapsed(&self) -> bool {
|
||||
!self.inner().state.might_be_registered() && self.initial_deadline.is_none()
|
||||
!self.inner().state.might_be_registered() && self.registered
|
||||
}
|
||||
|
||||
/// Cancels and deregisters the timer. This operation is irreversible.
|
||||
@@ -545,7 +528,8 @@ impl TimerEntry {
|
||||
}
|
||||
|
||||
pub(crate) fn reset(mut self: Pin<&mut Self>, new_time: Instant) {
|
||||
unsafe { self.as_mut().get_unchecked_mut() }.initial_deadline = None;
|
||||
unsafe { self.as_mut().get_unchecked_mut() }.deadline = new_time;
|
||||
unsafe { self.as_mut().get_unchecked_mut() }.registered = true;
|
||||
|
||||
let tick = self.driver().time_source().deadline_to_tick(new_time);
|
||||
|
||||
@@ -567,7 +551,8 @@ impl TimerEntry {
|
||||
panic!("{}", crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR);
|
||||
}
|
||||
|
||||
if let Some(deadline) = self.initial_deadline {
|
||||
if !self.registered {
|
||||
let deadline = self.deadline;
|
||||
self.as_mut().reset(deadline);
|
||||
}
|
||||
|
||||
@@ -579,6 +564,11 @@ impl TimerEntry {
|
||||
pub(crate) fn driver(&self) -> &super::Handle {
|
||||
self.driver.driver().time()
|
||||
}
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(crate) fn clock(&self) -> &super::Clock {
|
||||
self.driver.driver().clock()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerHandle {
|
||||
@@ -646,74 +636,3 @@ impl Drop for TimerEntry {
|
||||
unsafe { Pin::new_unchecked(self) }.as_mut().cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from [crossbeam/cache_padded](https://github.com/crossbeam-rs/crossbeam/blob/fa35346b7c789bba045ad789e894c68c466d1779/crossbeam-utils/src/cache_padded.rs#L62-L127)
|
||||
//
|
||||
// Starting from Intel's Sandy Bridge, spatial prefetcher is now pulling pairs of 64-byte cache
|
||||
// lines at a time, so we have to align to 128 bytes rather than 64.
|
||||
//
|
||||
// Sources:
|
||||
// - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
|
||||
// - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107
|
||||
//
|
||||
// ARM's big.LITTLE architecture has asymmetric cores and "big" cores have 128-byte cache line size.
|
||||
//
|
||||
// Sources:
|
||||
// - https://www.mono-project.com/news/2016/09/12/arm64-icache/
|
||||
//
|
||||
// powerpc64 has 128-byte cache line size.
|
||||
//
|
||||
// Sources:
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_ppc64x.go#L9
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_arch = "x86_64",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "powerpc64",
|
||||
),
|
||||
repr(align(128))
|
||||
)]
|
||||
// arm, mips, mips64, and riscv64 have 32-byte cache line size.
|
||||
//
|
||||
// Sources:
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_arm.go#L7
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips.go#L7
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mipsle.go#L7
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips64x.go#L9
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_riscv64.go#L7
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_arch = "arm",
|
||||
target_arch = "mips",
|
||||
target_arch = "mips64",
|
||||
target_arch = "riscv64",
|
||||
),
|
||||
repr(align(32))
|
||||
)]
|
||||
// s390x has 256-byte cache line size.
|
||||
//
|
||||
// Sources:
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_s390x.go#L7
|
||||
#[cfg_attr(target_arch = "s390x", repr(align(256)))]
|
||||
// x86 and wasm have 64-byte cache line size.
|
||||
//
|
||||
// Sources:
|
||||
// - https://github.com/golang/go/blob/dda2991c2ea0c5914714469c4defc2562a907230/src/internal/cpu/cpu_x86.go#L9
|
||||
// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7
|
||||
//
|
||||
// All others are assumed to have 64-byte cache line size.
|
||||
#[cfg_attr(
|
||||
not(any(
|
||||
target_arch = "x86_64",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "powerpc64",
|
||||
target_arch = "arm",
|
||||
target_arch = "mips",
|
||||
target_arch = "mips64",
|
||||
target_arch = "riscv64",
|
||||
target_arch = "s390x",
|
||||
)),
|
||||
repr(align(64))
|
||||
)]
|
||||
#[derive(Debug, Default)]
|
||||
struct CachePadded<T>(T);
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Driver {
|
||||
/// thread and `time_source` to get the current time and convert to ticks.
|
||||
///
|
||||
/// Specifying the source of time is useful when testing.
|
||||
pub(crate) fn new(park: IoStack, clock: Clock) -> (Driver, Handle) {
|
||||
pub(crate) fn new(park: IoStack, clock: &Clock) -> (Driver, Handle) {
|
||||
let time_source = TimeSource::new(clock);
|
||||
|
||||
let handle = Handle {
|
||||
@@ -186,7 +186,7 @@ impl Driver {
|
||||
|
||||
match next_wake {
|
||||
Some(when) => {
|
||||
let now = handle.time_source.now();
|
||||
let now = handle.time_source.now(rt_handle.clock());
|
||||
// Note that we effectively round up to 1ms here - this avoids
|
||||
// very short-duration microsecond-resolution sleeps that the OS
|
||||
// might treat as zero-length.
|
||||
@@ -214,13 +214,13 @@ impl Driver {
|
||||
}
|
||||
|
||||
// Process pending timers after waking up
|
||||
handle.process();
|
||||
handle.process(rt_handle.clock());
|
||||
}
|
||||
|
||||
cfg_test_util! {
|
||||
fn park_thread_timeout(&mut self, rt_handle: &driver::Handle, duration: Duration) {
|
||||
let handle = rt_handle.time();
|
||||
let clock = &handle.time_source.clock;
|
||||
let clock = rt_handle.clock();
|
||||
|
||||
if clock.can_auto_advance() {
|
||||
self.park.park_timeout(rt_handle, Duration::from_secs(0));
|
||||
@@ -231,7 +231,9 @@ impl Driver {
|
||||
// advance the clock.
|
||||
if !handle.did_wake() {
|
||||
// Simulate advancing time
|
||||
clock.advance(duration);
|
||||
if let Err(msg) = clock.advance(duration) {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.park.park_timeout(rt_handle, duration);
|
||||
@@ -248,8 +250,8 @@ impl Driver {
|
||||
|
||||
impl Handle {
|
||||
/// Runs timer related logic, and returns the next wakeup time
|
||||
pub(self) fn process(&self) {
|
||||
let now = self.time_source().now();
|
||||
pub(self) fn process(&self, clock: &Clock) {
|
||||
let now = self.time_source().now(clock);
|
||||
|
||||
self.process_at_time(now)
|
||||
}
|
||||
|
||||
@@ -5,15 +5,13 @@ use std::convert::TryInto;
|
||||
/// A structure which handles conversion from Instants to u64 timestamps.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TimeSource {
|
||||
pub(crate) clock: Clock,
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
impl TimeSource {
|
||||
pub(crate) fn new(clock: Clock) -> Self {
|
||||
pub(crate) fn new(clock: &Clock) -> Self {
|
||||
Self {
|
||||
start_time: clock.now(),
|
||||
clock,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +34,7 @@ impl TimeSource {
|
||||
Duration::from_millis(t)
|
||||
}
|
||||
|
||||
pub(crate) fn now(&self) -> u64 {
|
||||
self.instant_to_tick(self.clock.now())
|
||||
pub(crate) fn now(&self, clock: &Clock) -> u64 {
|
||||
self.instant_to_tick(clock.now())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,13 @@ fn single_timer() {
|
||||
|
||||
thread::yield_now();
|
||||
|
||||
let handle = handle.inner.driver().time();
|
||||
let time = handle.inner.driver().time();
|
||||
let clock = handle.inner.driver().clock();
|
||||
|
||||
// This may or may not return Some (depending on how it races with the
|
||||
// thread). If it does return None, however, the timer should complete
|
||||
// synchronously.
|
||||
handle.process_at_time(handle.time_source().now() + 2_000_000_000);
|
||||
time.process_at_time(time.time_source().now(clock) + 2_000_000_000);
|
||||
|
||||
jh.join().unwrap();
|
||||
})
|
||||
@@ -97,10 +98,11 @@ fn drop_timer() {
|
||||
|
||||
thread::yield_now();
|
||||
|
||||
let handle = handle.inner.driver().time();
|
||||
let time = handle.inner.driver().time();
|
||||
let clock = handle.inner.driver().clock();
|
||||
|
||||
// advance 2s in the future.
|
||||
handle.process_at_time(handle.time_source().now() + 2_000_000_000);
|
||||
time.process_at_time(time.time_source().now(clock) + 2_000_000_000);
|
||||
|
||||
jh.join().unwrap();
|
||||
})
|
||||
@@ -132,10 +134,11 @@ fn change_waker() {
|
||||
|
||||
thread::yield_now();
|
||||
|
||||
let handle = handle.inner.driver().time();
|
||||
let time = handle.inner.driver().time();
|
||||
let clock = handle.inner.driver().clock();
|
||||
|
||||
// advance 2s
|
||||
handle.process_at_time(handle.time_source().now() + 2_000_000_000);
|
||||
time.process_at_time(time.time_source().now(clock) + 2_000_000_000);
|
||||
|
||||
jh.join().unwrap();
|
||||
})
|
||||
|
||||
@@ -444,6 +444,7 @@ impl Semaphore {
|
||||
}
|
||||
|
||||
assert_eq!(acquired, 0);
|
||||
let mut old_waker = None;
|
||||
|
||||
// Otherwise, register the waker & enqueue the node.
|
||||
node.waker.with_mut(|waker| {
|
||||
@@ -455,7 +456,7 @@ impl Semaphore {
|
||||
.map(|waker| !waker.will_wake(cx.waker()))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
*waker = Some(cx.waker().clone());
|
||||
old_waker = std::mem::replace(waker, Some(cx.waker().clone()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -468,6 +469,8 @@ impl Semaphore {
|
||||
|
||||
waiters.queue.push_front(node);
|
||||
}
|
||||
drop(waiters);
|
||||
drop(old_waker);
|
||||
|
||||
Pending
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! A [`Sender`] is used to broadcast values to **all** connected [`Receiver`]
|
||||
//! values. [`Sender`] handles are clone-able, allowing concurrent send and
|
||||
//! receive actions. [`Sender`] and [`Receiver`] are both `Send` and `Sync` as
|
||||
//! long as `T` is also `Send` or `Sync` respectively.
|
||||
//! long as `T` is `Send`.
|
||||
//!
|
||||
//! When a value is sent, **all** [`Receiver`] handles are notified and will
|
||||
//! receive the value. The value is stored once inside the channel and cloned on
|
||||
|
||||
@@ -449,7 +449,7 @@ cfg_sync! {
|
||||
pub mod mpsc;
|
||||
|
||||
mod mutex;
|
||||
pub use mutex::{Mutex, MutexGuard, TryLockError, OwnedMutexGuard, MappedMutexGuard};
|
||||
pub use mutex::{Mutex, MutexGuard, TryLockError, OwnedMutexGuard, MappedMutexGuard, OwnedMappedMutexGuard};
|
||||
|
||||
pub(crate) mod notify;
|
||||
pub use notify::Notify;
|
||||
|
||||
@@ -326,6 +326,7 @@ impl<T> Receiver<T> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(alias = "recv_blocking"))]
|
||||
pub fn blocking_recv(&mut self) -> Option<T> {
|
||||
crate::future::block_on(self.recv())
|
||||
}
|
||||
@@ -696,6 +697,7 @@ impl<T> Sender<T> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(alias = "send_blocking"))]
|
||||
pub fn blocking_send(&self, value: T) -> Result<(), SendError<T>> {
|
||||
crate::future::block_on(self.send(value))
|
||||
}
|
||||
|
||||
@@ -243,6 +243,7 @@ impl<T> UnboundedReceiver<T> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(alias = "recv_blocking"))]
|
||||
pub fn blocking_recv(&mut self) -> Option<T> {
|
||||
crate::future::block_on(self.recv())
|
||||
}
|
||||
|
||||
+408
-64
@@ -6,9 +6,10 @@ use crate::util::trace;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::error::Error;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, marker, mem};
|
||||
use std::{fmt, mem, ptr};
|
||||
|
||||
/// An asynchronous `Mutex`-like type.
|
||||
///
|
||||
@@ -141,8 +142,11 @@ pub struct Mutex<T: ?Sized> {
|
||||
///
|
||||
/// The lock is automatically released whenever the guard is dropped, at which
|
||||
/// point `lock` will succeed yet again.
|
||||
#[clippy::has_significant_drop]
|
||||
#[must_use = "if unused the Mutex will immediately unlock"]
|
||||
pub struct MutexGuard<'a, T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
lock: &'a Mutex<T>,
|
||||
@@ -163,7 +167,10 @@ pub struct MutexGuard<'a, T: ?Sized> {
|
||||
/// point `lock` will succeed yet again.
|
||||
///
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct OwnedMutexGuard<T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
lock: Arc<Mutex<T>>,
|
||||
@@ -174,12 +181,71 @@ pub struct OwnedMutexGuard<T: ?Sized> {
|
||||
/// This can be used to hold a subfield of the protected data.
|
||||
///
|
||||
/// [`MutexGuard::map`]: method@MutexGuard::map
|
||||
#[clippy::has_significant_drop]
|
||||
#[must_use = "if unused the Mutex will immediately unlock"]
|
||||
pub struct MappedMutexGuard<'a, T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
s: &'a semaphore::Semaphore,
|
||||
data: *mut T,
|
||||
// Needed to tell the borrow checker that we are holding a `&mut T`
|
||||
marker: marker::PhantomData<&'a mut T>,
|
||||
marker: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
/// A owned handle to a held `Mutex` that has had a function applied to it via
|
||||
/// [`OwnedMutexGuard::map`].
|
||||
///
|
||||
/// This can be used to hold a subfield of the protected data.
|
||||
///
|
||||
/// [`OwnedMutexGuard::map`]: method@OwnedMutexGuard::map
|
||||
#[clippy::has_significant_drop]
|
||||
#[must_use = "if unused the Mutex will immediately unlock"]
|
||||
pub struct OwnedMappedMutexGuard<T: ?Sized, U: ?Sized = T> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
data: *mut U,
|
||||
lock: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
/// A helper type used when taking apart a `MutexGuard` without running its
|
||||
/// Drop implementation.
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct MutexGuardInner<'a, T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
lock: &'a Mutex<T>,
|
||||
}
|
||||
|
||||
/// A helper type used when taking apart a `OwnedMutexGuard` without running
|
||||
/// its Drop implementation.
|
||||
struct OwnedMutexGuardInner<T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
lock: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
/// A helper type used when taking apart a `MappedMutexGuard` without running
|
||||
/// its Drop implementation.
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct MappedMutexGuardInner<'a, T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
s: &'a semaphore::Semaphore,
|
||||
data: *mut T,
|
||||
}
|
||||
|
||||
/// A helper type used when taking apart a `OwnedMappedMutexGuard` without running
|
||||
/// its Drop implementation.
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct OwnedMappedMutexGuardInner<T: ?Sized, U: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
data: *mut U,
|
||||
lock: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
// As long as T: Send, it's fine to send and share Mutex<T> between threads.
|
||||
@@ -192,6 +258,19 @@ unsafe impl<T> Sync for OwnedMutexGuard<T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<'a, T> Sync for MappedMutexGuard<'a, T> where T: ?Sized + Sync + 'a {}
|
||||
unsafe impl<'a, T> Send for MappedMutexGuard<'a, T> where T: ?Sized + Send + 'a {}
|
||||
|
||||
unsafe impl<T, U> Sync for OwnedMappedMutexGuard<T, U>
|
||||
where
|
||||
T: ?Sized + Send + Sync,
|
||||
U: ?Sized + Send + Sync,
|
||||
{
|
||||
}
|
||||
unsafe impl<T, U> Send for OwnedMappedMutexGuard<T, U>
|
||||
where
|
||||
T: ?Sized + Send,
|
||||
U: ?Sized + Send,
|
||||
{
|
||||
}
|
||||
|
||||
/// Error returned from the [`Mutex::try_lock`], [`RwLock::try_read`] and
|
||||
/// [`RwLock::try_write`] functions.
|
||||
///
|
||||
@@ -337,15 +416,27 @@ impl<T: ?Sized> Mutex<T> {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn lock(&self) -> MutexGuard<'_, T> {
|
||||
let acquire_fut = async {
|
||||
self.acquire().await;
|
||||
|
||||
MutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
trace::async_op(
|
||||
|| self.acquire(),
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
self.resource_span.clone(),
|
||||
"Mutex::lock",
|
||||
"poll",
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
@@ -355,14 +446,7 @@ impl<T: ?Sized> Mutex<T> {
|
||||
);
|
||||
});
|
||||
|
||||
#[cfg(any(not(tokio_unstable), not(feature = "tracing")))]
|
||||
self.acquire().await;
|
||||
|
||||
MutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Blockingly locks this `Mutex`. When the lock has been acquired, function returns a
|
||||
@@ -414,6 +498,7 @@ impl<T: ?Sized> Mutex<T> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(alias = "lock_blocking"))]
|
||||
pub fn blocking_lock(&self) -> MutexGuard<'_, T> {
|
||||
crate::future::block_on(self.lock())
|
||||
}
|
||||
@@ -508,34 +593,39 @@ impl<T: ?Sized> Mutex<T> {
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
pub async fn lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
trace::async_op(
|
||||
|| self.acquire(),
|
||||
self.resource_span.clone(),
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
let acquire_fut = async {
|
||||
self.acquire().await;
|
||||
|
||||
OwnedMutexGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
lock: self,
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
resource_span,
|
||||
"Mutex::lock_owned",
|
||||
"poll",
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
locked = true,
|
||||
);
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
#[cfg(any(not(tokio_unstable), not(feature = "tracing")))]
|
||||
self.acquire().await;
|
||||
|
||||
OwnedMutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
async fn acquire(&self) {
|
||||
@@ -566,6 +656,12 @@ impl<T: ?Sized> Mutex<T> {
|
||||
pub fn try_lock(&self) -> Result<MutexGuard<'_, T>, TryLockError> {
|
||||
match self.s.try_acquire(1) {
|
||||
Ok(_) => {
|
||||
let guard = MutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -574,11 +670,7 @@ impl<T: ?Sized> Mutex<T> {
|
||||
);
|
||||
});
|
||||
|
||||
Ok(MutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
Err(_) => Err(TryLockError(())),
|
||||
}
|
||||
@@ -635,22 +727,21 @@ impl<T: ?Sized> Mutex<T> {
|
||||
pub fn try_lock_owned(self: Arc<Self>) -> Result<OwnedMutexGuard<T>, TryLockError> {
|
||||
match self.s.try_acquire(1) {
|
||||
Ok(_) => {
|
||||
let guard = OwnedMutexGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
lock: self,
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
locked = true,
|
||||
);
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
Ok(OwnedMutexGuard {
|
||||
lock: self,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
Err(_) => Err(TryLockError(())),
|
||||
}
|
||||
@@ -710,6 +801,17 @@ where
|
||||
// === impl MutexGuard ===
|
||||
|
||||
impl<'a, T: ?Sized> MutexGuard<'a, T> {
|
||||
fn skip_drop(self) -> MutexGuardInner<'a, T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the `resource_span` and then forgets the
|
||||
// original. In the end, we have not duplicated or forgotten any values.
|
||||
MutexGuardInner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: unsafe { std::ptr::read(&me.resource_span) },
|
||||
lock: me.lock,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`MappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`MutexGuard`] passed in already locked the mutex.
|
||||
@@ -746,12 +848,13 @@ impl<'a, T: ?Sized> MutexGuard<'a, T> {
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = &this.lock.s;
|
||||
mem::forget(this);
|
||||
let inner = this.skip_drop();
|
||||
MappedMutexGuard {
|
||||
s,
|
||||
s: &inner.lock.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,12 +899,13 @@ impl<'a, T: ?Sized> MutexGuard<'a, T> {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = &this.lock.s;
|
||||
mem::forget(this);
|
||||
let inner = this.skip_drop();
|
||||
Ok(MappedMutexGuard {
|
||||
s,
|
||||
s: &inner.lock.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -834,6 +938,8 @@ impl<'a, T: ?Sized> MutexGuard<'a, T> {
|
||||
|
||||
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(1);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -841,7 +947,6 @@ impl<T: ?Sized> Drop for MutexGuard<'_, T> {
|
||||
locked = false,
|
||||
);
|
||||
});
|
||||
self.lock.s.release(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,6 +978,116 @@ impl<T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'_, T> {
|
||||
// === impl OwnedMutexGuard ===
|
||||
|
||||
impl<T: ?Sized> OwnedMutexGuard<T> {
|
||||
fn skip_drop(self) -> OwnedMutexGuardInner<T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
unsafe {
|
||||
OwnedMutexGuardInner {
|
||||
lock: ptr::read(&me.lock),
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: ptr::read(&me.resource_span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`OwnedMappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`OwnedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `OwnedMutexGuard::map(...)`. A method
|
||||
/// would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let foo = Arc::new(Mutex::new(Foo(1)));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = OwnedMutexGuard::map(foo.clone().lock_owned().await, |f| &mut f.0);
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *foo.lock().await);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`OwnedMutexGuard`]: struct@OwnedMutexGuard
|
||||
/// [`OwnedMappedMutexGuard`]: struct@OwnedMappedMutexGuard
|
||||
#[inline]
|
||||
pub fn map<U, F>(mut this: Self, f: F) -> OwnedMappedMutexGuard<T, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let inner = this.skip_drop();
|
||||
OwnedMappedMutexGuard {
|
||||
data,
|
||||
lock: inner.lock,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`OwnedMappedMutexGuard`] for a component of the locked data. The
|
||||
/// original guard is returned if the closure returns `None`.
|
||||
///
|
||||
/// This operation cannot fail as the [`OwnedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `OwnedMutexGuard::try_map(...)`. A
|
||||
/// method would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let foo = Arc::new(Mutex::new(Foo(1)));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = OwnedMutexGuard::try_map(foo.clone().lock_owned().await, |f| Some(&mut f.0))
|
||||
/// .expect("should not fail");
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *foo.lock().await);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`OwnedMutexGuard`]: struct@OwnedMutexGuard
|
||||
/// [`OwnedMappedMutexGuard`]: struct@OwnedMappedMutexGuard
|
||||
#[inline]
|
||||
pub fn try_map<U, F>(mut this: Self, f: F) -> Result<OwnedMappedMutexGuard<T, 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 inner = this.skip_drop();
|
||||
Ok(OwnedMappedMutexGuard {
|
||||
data,
|
||||
lock: inner.lock,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the original `Arc<Mutex>`.
|
||||
///
|
||||
/// ```
|
||||
@@ -903,6 +1118,8 @@ impl<T: ?Sized> OwnedMutexGuard<T> {
|
||||
|
||||
impl<T: ?Sized> Drop for OwnedMutexGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(1);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -910,7 +1127,6 @@ impl<T: ?Sized> Drop for OwnedMutexGuard<T> {
|
||||
locked = false,
|
||||
);
|
||||
});
|
||||
self.lock.s.release(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,6 +1158,16 @@ impl<T: ?Sized + fmt::Display> fmt::Display for OwnedMutexGuard<T> {
|
||||
// === impl MappedMutexGuard ===
|
||||
|
||||
impl<'a, T: ?Sized> MappedMutexGuard<'a, T> {
|
||||
fn skip_drop(self) -> MappedMutexGuardInner<'a, T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
MappedMutexGuardInner {
|
||||
s: me.s,
|
||||
data: me.data,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: unsafe { std::ptr::read(&me.resource_span) },
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`MappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`MappedMutexGuard`] passed in already locked the mutex.
|
||||
@@ -956,12 +1182,13 @@ impl<'a, T: ?Sized> MappedMutexGuard<'a, T> {
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
mem::forget(this);
|
||||
let inner = this.skip_drop();
|
||||
MappedMutexGuard {
|
||||
s,
|
||||
s: inner.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,19 +1210,28 @@ impl<'a, T: ?Sized> MappedMutexGuard<'a, T> {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
mem::forget(this);
|
||||
let inner = this.skip_drop();
|
||||
Ok(MappedMutexGuard {
|
||||
s,
|
||||
s: inner.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for MappedMutexGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(1)
|
||||
self.s.release(1);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
locked = false,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1023,3 +1259,111 @@ impl<'a, T: ?Sized + fmt::Display> fmt::Display for MappedMutexGuard<'a, T> {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
// === impl OwnedMappedMutexGuard ===
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> OwnedMappedMutexGuard<T, U> {
|
||||
fn skip_drop(self) -> OwnedMappedMutexGuardInner<T, U> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
unsafe {
|
||||
OwnedMappedMutexGuardInner {
|
||||
data: me.data,
|
||||
lock: ptr::read(&me.lock),
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: ptr::read(&me.resource_span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`OwnedMappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`OwnedMappedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `OwnedMappedMutexGuard::map(...)`. A method
|
||||
/// would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// [`OwnedMappedMutexGuard`]: struct@OwnedMappedMutexGuard
|
||||
#[inline]
|
||||
pub fn map<S, F>(mut this: Self, f: F) -> OwnedMappedMutexGuard<T, S>
|
||||
where
|
||||
F: FnOnce(&mut U) -> &mut S,
|
||||
{
|
||||
let data = f(&mut *this) as *mut S;
|
||||
let inner = this.skip_drop();
|
||||
OwnedMappedMutexGuard {
|
||||
data,
|
||||
lock: inner.lock,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`OwnedMappedMutexGuard`] for a component of the locked data. The
|
||||
/// original guard is returned if the closure returns `None`.
|
||||
///
|
||||
/// This operation cannot fail as the [`OwnedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `OwnedMutexGuard::try_map(...)`. A
|
||||
/// method would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// [`OwnedMutexGuard`]: struct@OwnedMutexGuard
|
||||
/// [`OwnedMappedMutexGuard`]: struct@OwnedMappedMutexGuard
|
||||
#[inline]
|
||||
pub fn try_map<S, F>(mut this: Self, f: F) -> Result<OwnedMappedMutexGuard<T, S>, Self>
|
||||
where
|
||||
F: FnOnce(&mut U) -> Option<&mut S>,
|
||||
{
|
||||
let data = match f(&mut *this) {
|
||||
Some(data) => data as *mut S,
|
||||
None => return Err(this),
|
||||
};
|
||||
let inner = this.skip_drop();
|
||||
Ok(OwnedMappedMutexGuard {
|
||||
data,
|
||||
lock: inner.lock,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: inner.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> Drop for OwnedMappedMutexGuard<T, U> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(1);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
locked = false,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> Deref for OwnedMappedMutexGuard<T, U> {
|
||||
type Target = U;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> DerefMut for OwnedMappedMutexGuard<T, U> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized + fmt::Debug> fmt::Debug for OwnedMappedMutexGuard<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized + fmt::Display> fmt::Display for OwnedMappedMutexGuard<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
+165
-44
@@ -7,7 +7,7 @@
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::Mutex;
|
||||
use crate::util::linked_list::{self, LinkedList};
|
||||
use crate::util::linked_list::{self, GuardedLinkedList, LinkedList};
|
||||
use crate::util::WakeList;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
@@ -20,6 +20,7 @@ use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
|
||||
type WaitList = LinkedList<Waiter, <Waiter as linked_list::Link>::Target>;
|
||||
type GuardedWaitList = GuardedLinkedList<Waiter, <Waiter as linked_list::Link>::Target>;
|
||||
|
||||
/// Notifies a single task to wake up.
|
||||
///
|
||||
@@ -198,10 +199,16 @@ type WaitList = LinkedList<Waiter, <Waiter as linked_list::Link>::Target>;
|
||||
/// [`Semaphore`]: crate::sync::Semaphore
|
||||
#[derive(Debug)]
|
||||
pub struct Notify {
|
||||
// This uses 2 bits to store one of `EMPTY`,
|
||||
// `state` uses 2 bits to store one of `EMPTY`,
|
||||
// `WAITING` or `NOTIFIED`. The rest of the bits
|
||||
// are used to store the number of times `notify_waiters`
|
||||
// was called.
|
||||
//
|
||||
// Throughout the code there are two assumptions:
|
||||
// - state can be transitioned *from* `WAITING` only if
|
||||
// `waiters` lock is held
|
||||
// - number of times `notify_waiters` was called can
|
||||
// be modified only if `waiters` lock is held
|
||||
state: AtomicUsize,
|
||||
waiters: Mutex<WaitList>,
|
||||
}
|
||||
@@ -229,6 +236,17 @@ struct Waiter {
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
|
||||
impl Waiter {
|
||||
fn new() -> Waiter {
|
||||
Waiter {
|
||||
pointers: linked_list::Pointers::new(),
|
||||
waker: None,
|
||||
notified: None,
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate_addr_of_methods! {
|
||||
impl<> Waiter {
|
||||
unsafe fn addr_of_pointers(self: NonNull<Self>) -> NonNull<linked_list::Pointers<Waiter>> {
|
||||
@@ -237,6 +255,59 @@ generate_addr_of_methods! {
|
||||
}
|
||||
}
|
||||
|
||||
/// List used in `Notify::notify_waiters`. It wraps a guarded linked list
|
||||
/// and gates the access to it on `notify.waiters` mutex. It also empties
|
||||
/// the list on drop.
|
||||
struct NotifyWaitersList<'a> {
|
||||
list: GuardedWaitList,
|
||||
is_empty: bool,
|
||||
notify: &'a Notify,
|
||||
}
|
||||
|
||||
impl<'a> NotifyWaitersList<'a> {
|
||||
fn new(
|
||||
unguarded_list: WaitList,
|
||||
guard: Pin<&'a mut UnsafeCell<Waiter>>,
|
||||
notify: &'a Notify,
|
||||
) -> NotifyWaitersList<'a> {
|
||||
// Safety: pointer to the guarding waiter is not null.
|
||||
let guard_ptr = unsafe { NonNull::new_unchecked(guard.get()) };
|
||||
let list = unguarded_list.into_guarded(guard_ptr);
|
||||
NotifyWaitersList {
|
||||
list,
|
||||
is_empty: false,
|
||||
notify,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the last element from the guarded list. Modifying this list
|
||||
/// requires an exclusive access to the main list in `Notify`.
|
||||
fn pop_back_locked(&mut self, _waiters: &mut WaitList) -> Option<NonNull<Waiter>> {
|
||||
let result = self.list.pop_back();
|
||||
if result.is_none() {
|
||||
// Save information about emptiness to avoid waiting for lock
|
||||
// in the destructor.
|
||||
self.is_empty = true;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NotifyWaitersList<'_> {
|
||||
fn drop(&mut self) {
|
||||
// If the list is not empty, we unlink all waiters from it.
|
||||
// We do not wake the waiters to avoid double panics.
|
||||
if !self.is_empty {
|
||||
let _lock_guard = self.notify.waiters.lock();
|
||||
while let Some(mut waiter) = self.list.pop_back() {
|
||||
// Safety: we hold the lock.
|
||||
let waiter = unsafe { waiter.as_mut() };
|
||||
waiter.notified = Some(NotificationType::AllWaiters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future returned from [`Notify::notified()`].
|
||||
///
|
||||
/// This future is fused, so once it has completed, any future calls to poll
|
||||
@@ -249,6 +320,9 @@ pub struct Notified<'a> {
|
||||
/// The current state of the receiving process.
|
||||
state: State,
|
||||
|
||||
/// Number of calls to `notify_waiters` at the time of creation.
|
||||
notify_waiters_calls: usize,
|
||||
|
||||
/// Entry in the waiter `LinkedList`.
|
||||
waiter: UnsafeCell<Waiter>,
|
||||
}
|
||||
@@ -258,7 +332,7 @@ unsafe impl<'a> Sync for Notified<'a> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Init(usize),
|
||||
Init,
|
||||
Waiting,
|
||||
Done,
|
||||
}
|
||||
@@ -383,17 +457,13 @@ impl Notify {
|
||||
/// ```
|
||||
pub fn notified(&self) -> Notified<'_> {
|
||||
// we load the number of times notify_waiters
|
||||
// was called and store that in our initial state
|
||||
// was called and store that in the future.
|
||||
let state = self.state.load(SeqCst);
|
||||
Notified {
|
||||
notify: self,
|
||||
state: State::Init(state >> NOTIFY_WAITERS_SHIFT),
|
||||
waiter: UnsafeCell::new(Waiter {
|
||||
pointers: linked_list::Pointers::new(),
|
||||
waker: None,
|
||||
notified: None,
|
||||
_p: PhantomPinned,
|
||||
}),
|
||||
state: State::Init,
|
||||
notify_waiters_calls: get_num_notify_waiters_calls(state),
|
||||
waiter: UnsafeCell::new(Waiter::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,12 +570,9 @@ impl Notify {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn notify_waiters(&self) {
|
||||
let mut wakers = WakeList::new();
|
||||
|
||||
// There are waiters, the lock must be acquired to notify.
|
||||
let mut waiters = self.waiters.lock();
|
||||
|
||||
// The state must be reloaded while the lock is held. The state may only
|
||||
// The state must be loaded while the lock is held. The state may only
|
||||
// transition out of WAITING while the lock is held.
|
||||
let curr = self.state.load(SeqCst);
|
||||
|
||||
@@ -516,12 +583,30 @@ impl Notify {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, it is guaranteed that the state will not
|
||||
// concurrently change, as holding the lock is required to
|
||||
// transition **out** of `WAITING`.
|
||||
// Increment the number of times this method was called
|
||||
// and transition to empty.
|
||||
let new_state = set_state(inc_num_notify_waiters_calls(curr), EMPTY);
|
||||
self.state.store(new_state, SeqCst);
|
||||
|
||||
// It is critical for `GuardedLinkedList` safety that the guard node is
|
||||
// pinned in memory and is not dropped until the guarded list is dropped.
|
||||
let guard = UnsafeCell::new(Waiter::new());
|
||||
pin!(guard);
|
||||
|
||||
// We move all waiters to a secondary list. It uses a `GuardedLinkedList`
|
||||
// underneath to allow every waiter to safely remove itself from it.
|
||||
//
|
||||
// * This list will be still guarded by the `waiters` lock.
|
||||
// `NotifyWaitersList` wrapper makes sure we hold the lock to modify it.
|
||||
// * This wrapper will empty the list on drop. It is critical for safety
|
||||
// that we will not leave any list entry with a pointer to the local
|
||||
// guard node after this function returns / panics.
|
||||
let mut list = NotifyWaitersList::new(std::mem::take(&mut *waiters), guard, self);
|
||||
|
||||
let mut wakers = WakeList::new();
|
||||
'outer: loop {
|
||||
while wakers.can_push() {
|
||||
match waiters.pop_back() {
|
||||
match list.pop_back_locked(&mut waiters) {
|
||||
Some(mut waiter) => {
|
||||
// Safety: `waiters` lock is still held.
|
||||
let waiter = unsafe { waiter.as_mut() };
|
||||
@@ -540,20 +625,17 @@ impl Notify {
|
||||
}
|
||||
}
|
||||
|
||||
// Release the lock before notifying.
|
||||
drop(waiters);
|
||||
|
||||
// One of the wakers may panic, but the remaining waiters will still
|
||||
// be unlinked from the list in `NotifyWaitersList` destructor.
|
||||
wakers.wake_all();
|
||||
|
||||
// Acquire the lock again.
|
||||
waiters = self.waiters.lock();
|
||||
}
|
||||
|
||||
// All waiters will be notified, the state must be transitioned to
|
||||
// `EMPTY`. As transitioning **from** `WAITING` requires the lock to be
|
||||
// held, a `store` is sufficient.
|
||||
let new = set_state(inc_num_notify_waiters_calls(curr), EMPTY);
|
||||
self.state.store(new, SeqCst);
|
||||
|
||||
// Release the lock before notifying
|
||||
drop(waiters);
|
||||
|
||||
@@ -730,26 +812,32 @@ impl Notified<'_> {
|
||||
|
||||
/// A custom `project` implementation is used in place of `pin-project-lite`
|
||||
/// as a custom drop implementation is needed.
|
||||
fn project(self: Pin<&mut Self>) -> (&Notify, &mut State, &UnsafeCell<Waiter>) {
|
||||
fn project(self: Pin<&mut Self>) -> (&Notify, &mut State, &usize, &UnsafeCell<Waiter>) {
|
||||
unsafe {
|
||||
// Safety: both `notify` and `state` are `Unpin`.
|
||||
// Safety: `notify`, `state` and `notify_waiters_calls` are `Unpin`.
|
||||
|
||||
is_unpin::<&Notify>();
|
||||
is_unpin::<AtomicUsize>();
|
||||
is_unpin::<usize>();
|
||||
|
||||
let me = self.get_unchecked_mut();
|
||||
(me.notify, &mut me.state, &me.waiter)
|
||||
(
|
||||
me.notify,
|
||||
&mut me.state,
|
||||
&me.notify_waiters_calls,
|
||||
&me.waiter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_notified(self: Pin<&mut Self>, waker: Option<&Waker>) -> Poll<()> {
|
||||
use State::*;
|
||||
|
||||
let (notify, state, waiter) = self.project();
|
||||
let (notify, state, notify_waiters_calls, waiter) = self.project();
|
||||
|
||||
loop {
|
||||
match *state {
|
||||
Init(initial_notify_waiters_calls) => {
|
||||
Init => {
|
||||
let curr = notify.state.load(SeqCst);
|
||||
|
||||
// Optimistically try acquiring a pending notification
|
||||
@@ -779,7 +867,7 @@ impl Notified<'_> {
|
||||
|
||||
// if notify_waiters has been called after the future
|
||||
// was created, then we are done
|
||||
if get_num_notify_waiters_calls(curr) != initial_notify_waiters_calls {
|
||||
if get_num_notify_waiters_calls(curr) != *notify_waiters_calls {
|
||||
*state = Done;
|
||||
return Poll::Ready(());
|
||||
}
|
||||
@@ -829,10 +917,14 @@ impl Notified<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_waker = None;
|
||||
if waker.is_some() {
|
||||
// Safety: called while locked.
|
||||
//
|
||||
// The use of `old_waiter` here is not necessary, as the field is always
|
||||
// None when we reach this line.
|
||||
unsafe {
|
||||
(*waiter.get()).waker = waker;
|
||||
old_waker = std::mem::replace(&mut (*waiter.get()).waker, waker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,24 +935,44 @@ impl Notified<'_> {
|
||||
|
||||
*state = Waiting;
|
||||
|
||||
drop(waiters);
|
||||
drop(old_waker);
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
Waiting => {
|
||||
// Currently in the "Waiting" state, implying the caller has
|
||||
// a waiter stored in the waiter list (guarded by
|
||||
// `notify.waiters`). In order to access the waker fields,
|
||||
// we must hold the lock.
|
||||
// Currently in the "Waiting" state, implying the caller has a waiter stored in
|
||||
// a waiter list (guarded by `notify.waiters`). In order to access the waker
|
||||
// fields, we must acquire the lock.
|
||||
|
||||
let waiters = notify.waiters.lock();
|
||||
let mut waiters = notify.waiters.lock();
|
||||
|
||||
// Load the state with the lock held.
|
||||
let curr = notify.state.load(SeqCst);
|
||||
|
||||
// Safety: called while locked
|
||||
let w = unsafe { &mut *waiter.get() };
|
||||
let mut old_waker = None;
|
||||
|
||||
if w.notified.is_some() {
|
||||
// Our waker has been notified. Reset the fields and
|
||||
// remove it from the list.
|
||||
w.waker = None;
|
||||
// Our waker has been notified and our waiter is already removed from
|
||||
// the list. Reset the notification and convert to `Done`.
|
||||
old_waker = std::mem::take(&mut w.waker);
|
||||
w.notified = None;
|
||||
*state = Done;
|
||||
} else if get_num_notify_waiters_calls(curr) != *notify_waiters_calls {
|
||||
// Before we add a waiter to the list we check if these numbers are
|
||||
// different while holding the lock. If these numbers are different now,
|
||||
// it means that there is a call to `notify_waiters` in progress and this
|
||||
// waiter must be contained by a guarded list used in `notify_waiters`.
|
||||
// We can treat the waiter as notified and remove it from the list, as
|
||||
// it would have been notified in the `notify_waiters` call anyways.
|
||||
|
||||
old_waker = std::mem::take(&mut w.waker);
|
||||
|
||||
// Safety: we hold the lock, so we have an exclusive access to the list.
|
||||
// The list is used in `notify_waiters`, so it must be guarded.
|
||||
unsafe { waiters.remove(NonNull::new_unchecked(w)) };
|
||||
|
||||
*state = Done;
|
||||
} else {
|
||||
@@ -871,10 +983,14 @@ impl Notified<'_> {
|
||||
None => true,
|
||||
};
|
||||
if should_update {
|
||||
w.waker = Some(waker.clone());
|
||||
old_waker = std::mem::replace(&mut w.waker, Some(waker.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the old waker after releasing the lock.
|
||||
drop(waiters);
|
||||
drop(old_waker);
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
@@ -884,6 +1000,9 @@ impl Notified<'_> {
|
||||
// is helpful to visualize the scope of the critical
|
||||
// section.
|
||||
drop(waiters);
|
||||
|
||||
// Drop the old waker after releasing the lock.
|
||||
drop(old_waker);
|
||||
}
|
||||
Done => {
|
||||
return Poll::Ready(());
|
||||
@@ -906,7 +1025,7 @@ impl Drop for Notified<'_> {
|
||||
use State::*;
|
||||
|
||||
// Safety: The type only transitions to a "Waiting" state when pinned.
|
||||
let (notify, state, waiter) = unsafe { Pin::new_unchecked(self).project() };
|
||||
let (notify, state, _, waiter) = unsafe { Pin::new_unchecked(self).project() };
|
||||
|
||||
// This is where we ensure safety. The `Notified` value is being
|
||||
// dropped, which means we must ensure that the waiter entry is no
|
||||
@@ -917,8 +1036,10 @@ impl Drop for Notified<'_> {
|
||||
|
||||
// remove the entry from the list (if not already removed)
|
||||
//
|
||||
// safety: the waiter is only added to `waiters` by virtue of it
|
||||
// being the only `LinkedList` available to the type.
|
||||
// Safety: we hold the lock, so we have an exclusive access to every list the
|
||||
// waiter may be contained in. If the node is not contained in the `waiters`
|
||||
// list, then it is contained by a guarded list used by `notify_waiters` and
|
||||
// in such case it must be a middle node.
|
||||
unsafe { waiters.remove(NonNull::new_unchecked(waiter.get())) };
|
||||
|
||||
if waiters.is_empty() && get_state(notify_state) == WAITING {
|
||||
|
||||
@@ -1056,6 +1056,7 @@ impl<T> Receiver<T> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(alias = "recv_blocking"))]
|
||||
pub fn blocking_recv(self) -> Result<T, RecvError> {
|
||||
crate::future::block_on(self)
|
||||
}
|
||||
|
||||
+136
-119
@@ -5,7 +5,6 @@ use crate::util::trace;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod owned_read_guard;
|
||||
@@ -423,23 +422,33 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn read(&self) -> RwLockReadGuard<'_, T> {
|
||||
let acquire_fut = async {
|
||||
self.s.acquire(1).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
|
||||
RwLockReadGuard {
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let inner = trace::async_op(
|
||||
|| self.s.acquire(1),
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
self.resource_span.clone(),
|
||||
"RwLock::read",
|
||||
"poll",
|
||||
false,
|
||||
);
|
||||
|
||||
#[cfg(not(all(tokio_unstable, feature = "tracing")))]
|
||||
let inner = self.s.acquire(1);
|
||||
|
||||
inner.await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
@@ -450,13 +459,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
RwLockReadGuard {
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Blockingly locks this `RwLock` with shared read access.
|
||||
@@ -565,25 +568,38 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// ```
|
||||
pub async fn read_owned(self: Arc<Self>) -> OwnedRwLockReadGuard<T> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let inner = trace::async_op(
|
||||
|| self.s.acquire(1),
|
||||
self.resource_span.clone(),
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
let acquire_fut = async {
|
||||
self.s.acquire(1).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
|
||||
OwnedRwLockReadGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
data: self.c.get(),
|
||||
lock: self,
|
||||
_p: PhantomData,
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
resource_span,
|
||||
"RwLock::read_owned",
|
||||
"poll",
|
||||
false,
|
||||
);
|
||||
|
||||
#[cfg(not(all(tokio_unstable, feature = "tracing")))]
|
||||
let inner = self.s.acquire(1);
|
||||
|
||||
inner.await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
current_readers = 1,
|
||||
@@ -591,16 +607,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
OwnedRwLockReadGuard {
|
||||
data: self.c.get(),
|
||||
lock: ManuallyDrop::new(self),
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Attempts to acquire this `RwLock` with shared read access.
|
||||
@@ -642,6 +649,14 @@ impl<T: ?Sized> RwLock<T> {
|
||||
Err(TryAcquireError::Closed) => unreachable!(),
|
||||
}
|
||||
|
||||
let guard = RwLockReadGuard {
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -651,13 +666,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
Ok(RwLockReadGuard {
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
/// Attempts to acquire this `RwLock` with shared read access.
|
||||
@@ -705,8 +714,16 @@ impl<T: ?Sized> RwLock<T> {
|
||||
Err(TryAcquireError::Closed) => unreachable!(),
|
||||
}
|
||||
|
||||
let guard = OwnedRwLockReadGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
data: self.c.get(),
|
||||
lock: self,
|
||||
_p: PhantomData,
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
current_readers = 1,
|
||||
@@ -714,16 +731,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
Ok(OwnedRwLockReadGuard {
|
||||
data: self.c.get(),
|
||||
lock: ManuallyDrop::new(self),
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
/// Locks this `RwLock` with exclusive write access, causing the current
|
||||
@@ -755,23 +763,34 @@ impl<T: ?Sized> RwLock<T> {
|
||||
///}
|
||||
/// ```
|
||||
pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
|
||||
let acquire_fut = async {
|
||||
self.s.acquire(self.mr).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
|
||||
RwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let inner = trace::async_op(
|
||||
|| self.s.acquire(self.mr),
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
self.resource_span.clone(),
|
||||
"RwLock::write",
|
||||
"poll",
|
||||
false,
|
||||
);
|
||||
|
||||
#[cfg(not(all(tokio_unstable, feature = "tracing")))]
|
||||
let inner = self.s.acquire(self.mr);
|
||||
|
||||
inner.await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
@@ -782,14 +801,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
RwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Blockingly locks this `RwLock` with exclusive write access.
|
||||
@@ -884,25 +896,39 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// ```
|
||||
pub async fn write_owned(self: Arc<Self>) -> OwnedRwLockWriteGuard<T> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let inner = trace::async_op(
|
||||
|| self.s.acquire(self.mr),
|
||||
self.resource_span.clone(),
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
let acquire_fut = async {
|
||||
self.s.acquire(self.mr).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
|
||||
OwnedRwLockWriteGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
permits_acquired: self.mr,
|
||||
data: self.c.get(),
|
||||
lock: self,
|
||||
_p: PhantomData,
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let acquire_fut = trace::async_op(
|
||||
move || acquire_fut,
|
||||
resource_span,
|
||||
"RwLock::write_owned",
|
||||
"poll",
|
||||
false,
|
||||
);
|
||||
|
||||
#[cfg(not(all(tokio_unstable, feature = "tracing")))]
|
||||
let inner = self.s.acquire(self.mr);
|
||||
|
||||
inner.await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
unreachable!()
|
||||
});
|
||||
#[allow(clippy::let_and_return)] // this lint triggers when disabling tracing
|
||||
let guard = acquire_fut.await;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
write_locked = true,
|
||||
@@ -910,17 +936,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
OwnedRwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
data: self.c.get(),
|
||||
lock: ManuallyDrop::new(self),
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Attempts to acquire this `RwLock` with exclusive write access.
|
||||
@@ -953,6 +969,15 @@ impl<T: ?Sized> RwLock<T> {
|
||||
Err(TryAcquireError::Closed) => unreachable!(),
|
||||
}
|
||||
|
||||
let guard = RwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -962,14 +987,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
Ok(RwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
s: &self.s,
|
||||
data: self.c.get(),
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
/// Attempts to acquire this `RwLock` with exclusive write access.
|
||||
@@ -1009,8 +1027,17 @@ impl<T: ?Sized> RwLock<T> {
|
||||
Err(TryAcquireError::Closed) => unreachable!(),
|
||||
}
|
||||
|
||||
let guard = OwnedRwLockWriteGuard {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: self.resource_span.clone(),
|
||||
permits_acquired: self.mr,
|
||||
data: self.c.get(),
|
||||
lock: self,
|
||||
_p: PhantomData,
|
||||
};
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
write_locked = true,
|
||||
@@ -1018,17 +1045,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
Ok(OwnedRwLockWriteGuard {
|
||||
permits_acquired: self.mr,
|
||||
data: self.c.get(),
|
||||
lock: ManuallyDrop::new(self),
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
})
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying data.
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use crate::sync::rwlock::RwLock;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, ops, ptr};
|
||||
|
||||
/// Owned RAII structure used to release the shared read access of a lock when
|
||||
/// dropped.
|
||||
@@ -14,16 +11,40 @@ use std::sync::Arc;
|
||||
///
|
||||
/// [`read_owned`]: method@crate::sync::RwLock::read_owned
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct OwnedRwLockReadGuard<T: ?Sized, U: ?Sized = T> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
// ManuallyDrop allows us to destructure into this field without running the destructor.
|
||||
pub(super) lock: ManuallyDrop<Arc<RwLock<T>>>,
|
||||
pub(super) lock: Arc<RwLock<T>>,
|
||||
pub(super) data: *const U,
|
||||
pub(super) _p: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<T: ?Sized, U: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
lock: Arc<RwLock<T>>,
|
||||
data: *const U,
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> OwnedRwLockReadGuard<T, U> {
|
||||
fn skip_drop(self) -> Inner<T, U> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
unsafe {
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: ptr::read(&me.resource_span),
|
||||
lock: ptr::read(&me.lock),
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `OwnedRwLockReadGuard` for a component of the locked data.
|
||||
/// This operation cannot fail as the `OwnedRwLockReadGuard` passed in
|
||||
/// already locked the data.
|
||||
@@ -52,23 +73,19 @@ impl<T: ?Sized, U: ?Sized> OwnedRwLockReadGuard<T, U> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<F, V: ?Sized>(mut this: Self, f: F) -> OwnedRwLockReadGuard<T, V>
|
||||
pub fn map<F, V: ?Sized>(this: Self, f: F) -> OwnedRwLockReadGuard<T, V>
|
||||
where
|
||||
F: FnOnce(&U) -> &V,
|
||||
{
|
||||
let data = f(&*this) as *const V;
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
OwnedRwLockReadGuard {
|
||||
lock: ManuallyDrop::new(lock),
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +120,7 @@ impl<T: ?Sized, U: ?Sized> OwnedRwLockReadGuard<T, U> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_map<F, V: ?Sized>(mut this: Self, f: F) -> Result<OwnedRwLockReadGuard<T, V>, Self>
|
||||
pub fn try_map<F, V: ?Sized>(this: Self, f: F) -> Result<OwnedRwLockReadGuard<T, V>, Self>
|
||||
where
|
||||
F: FnOnce(&U) -> Option<&V>,
|
||||
{
|
||||
@@ -111,18 +128,14 @@ impl<T: ?Sized, U: ?Sized> OwnedRwLockReadGuard<T, U> {
|
||||
Some(data) => data as *const V,
|
||||
None => return Err(this),
|
||||
};
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(OwnedRwLockReadGuard {
|
||||
lock: ManuallyDrop::new(lock),
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -156,7 +169,6 @@ where
|
||||
impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockReadGuard<T, U> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(1);
|
||||
unsafe { ManuallyDrop::drop(&mut self.lock) };
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard;
|
||||
use crate::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard;
|
||||
use crate::sync::rwlock::RwLock;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{self, ManuallyDrop};
|
||||
use std::ops;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, ops, ptr};
|
||||
|
||||
/// Owned RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
@@ -15,17 +13,43 @@ use std::sync::Arc;
|
||||
///
|
||||
/// [`write_owned`]: method@crate::sync::RwLock::write_owned
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct OwnedRwLockWriteGuard<T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
pub(super) permits_acquired: u32,
|
||||
// ManuallyDrop allows us to destructure into this field without running the destructor.
|
||||
pub(super) lock: ManuallyDrop<Arc<RwLock<T>>>,
|
||||
pub(super) lock: Arc<RwLock<T>>,
|
||||
pub(super) data: *mut T,
|
||||
pub(super) _p: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
permits_acquired: u32,
|
||||
lock: Arc<RwLock<T>>,
|
||||
data: *const T,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
fn skip_drop(self) -> Inner<T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
unsafe {
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: ptr::read(&me.resource_span),
|
||||
permits_acquired: me.permits_acquired,
|
||||
lock: ptr::read(&me.lock),
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`OwnedRwLockMappedWriteGuard`] for a component of the locked
|
||||
/// data.
|
||||
///
|
||||
@@ -64,20 +88,15 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
OwnedRwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
lock: ManuallyDrop::new(lock),
|
||||
permits_acquired: this.permits_acquired,
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,21 +147,15 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let permits_acquired = this.permits_acquired;
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(OwnedRwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
lock: ManuallyDrop::new(lock),
|
||||
permits_acquired: this.permits_acquired,
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -191,15 +204,22 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
/// assert_eq!(*lock.read().await, 2, "second writer obtained write lock");
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn downgrade(mut self) -> OwnedRwLockReadGuard<T> {
|
||||
let lock = unsafe { ManuallyDrop::take(&mut self.lock) };
|
||||
let data = self.data;
|
||||
let to_release = (self.permits_acquired - 1) as usize;
|
||||
pub fn downgrade(self) -> OwnedRwLockReadGuard<T> {
|
||||
let this = self.skip_drop();
|
||||
let guard = OwnedRwLockReadGuard {
|
||||
lock: this.lock,
|
||||
data: this.data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: this.resource_span,
|
||||
};
|
||||
|
||||
// Release all but one of the permits held by the write guard
|
||||
lock.s.release(to_release);
|
||||
let to_release = (this.permits_acquired - 1) as usize;
|
||||
guard.lock.s.release(to_release);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
write_locked = false,
|
||||
@@ -208,7 +228,7 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
current_readers = 1,
|
||||
@@ -216,18 +236,7 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(self);
|
||||
|
||||
OwnedRwLockReadGuard {
|
||||
lock: ManuallyDrop::new(lock),
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
}
|
||||
guard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +275,7 @@ where
|
||||
impl<T: ?Sized> Drop for OwnedRwLockWriteGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(self.permits_acquired as usize);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -274,6 +284,5 @@ impl<T: ?Sized> Drop for OwnedRwLockWriteGuard<T> {
|
||||
write_locked.op = "override",
|
||||
)
|
||||
});
|
||||
unsafe { ManuallyDrop::drop(&mut self.lock) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use crate::sync::rwlock::RwLock;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{self, ManuallyDrop};
|
||||
use std::ops;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, ops, ptr};
|
||||
|
||||
/// Owned RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
@@ -14,17 +12,43 @@ use std::sync::Arc;
|
||||
///
|
||||
/// [mapping]: method@crate::sync::OwnedRwLockWriteGuard::map
|
||||
/// [`OwnedRwLockWriteGuard`]: struct@crate::sync::OwnedRwLockWriteGuard
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct OwnedRwLockMappedWriteGuard<T: ?Sized, U: ?Sized = T> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
pub(super) permits_acquired: u32,
|
||||
// ManuallyDrop allows us to destructure into this field without running the destructor.
|
||||
pub(super) lock: ManuallyDrop<Arc<RwLock<T>>>,
|
||||
pub(super) lock: Arc<RwLock<T>>,
|
||||
pub(super) data: *mut U,
|
||||
pub(super) _p: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<T: ?Sized, U: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
permits_acquired: u32,
|
||||
lock: Arc<RwLock<T>>,
|
||||
data: *const U,
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U: ?Sized> OwnedRwLockMappedWriteGuard<T, U> {
|
||||
fn skip_drop(self) -> Inner<T, U> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
unsafe {
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: ptr::read(&me.resource_span),
|
||||
permits_acquired: me.permits_acquired,
|
||||
lock: ptr::read(&me.lock),
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `OwnedRwLockMappedWriteGuard` for a component of the locked
|
||||
/// data.
|
||||
///
|
||||
@@ -63,20 +87,15 @@ impl<T: ?Sized, U: ?Sized> OwnedRwLockMappedWriteGuard<T, U> {
|
||||
F: FnOnce(&mut U) -> &mut V,
|
||||
{
|
||||
let data = f(&mut *this) as *mut V;
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
OwnedRwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
lock: ManuallyDrop::new(lock),
|
||||
permits_acquired: this.permits_acquired,
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,20 +144,15 @@ impl<T: ?Sized, U: ?Sized> OwnedRwLockMappedWriteGuard<T, U> {
|
||||
Some(data) => data as *mut V,
|
||||
None => return Err(this),
|
||||
};
|
||||
let lock = unsafe { ManuallyDrop::take(&mut this.lock) };
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(OwnedRwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
lock: ManuallyDrop::new(lock),
|
||||
permits_acquired: this.permits_acquired,
|
||||
lock: this.lock,
|
||||
data,
|
||||
_p: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -178,6 +192,7 @@ where
|
||||
impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockMappedWriteGuard<T, U> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(self.permits_acquired as usize);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
@@ -186,6 +201,5 @@ impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockMappedWriteGuard<T, U> {
|
||||
write_locked.op = "override",
|
||||
)
|
||||
});
|
||||
unsafe { ManuallyDrop::drop(&mut self.lock) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::sync::batch_semaphore::Semaphore;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, mem, ops};
|
||||
|
||||
/// RAII structure used to release the shared read access of a lock when
|
||||
/// dropped.
|
||||
@@ -12,16 +10,39 @@ use std::ops;
|
||||
///
|
||||
/// [`read`]: method@crate::sync::RwLock::read
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
#[clippy::has_significant_drop]
|
||||
#[must_use = "if unused the RwLock will immediately unlock"]
|
||||
pub struct RwLockReadGuard<'a, T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *const T,
|
||||
pub(super) marker: marker::PhantomData<&'a T>,
|
||||
pub(super) marker: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<'a, T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
s: &'a Semaphore,
|
||||
data: *const T,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockReadGuard<'a, T> {
|
||||
fn skip_drop(self) -> Inner<'a, T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: unsafe { std::ptr::read(&me.resource_span) },
|
||||
s: me.s,
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `RwLockReadGuard` for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockReadGuard` passed in already
|
||||
@@ -61,18 +82,14 @@ impl<'a, T: ?Sized> RwLockReadGuard<'a, T> {
|
||||
F: FnOnce(&T) -> &U,
|
||||
{
|
||||
let data = f(&*this) as *const U;
|
||||
let s = this.s;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,18 +137,14 @@ impl<'a, T: ?Sized> RwLockReadGuard<'a, T> {
|
||||
Some(data) => data as *const U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(RwLockReadGuard {
|
||||
s,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, mem, ops};
|
||||
|
||||
/// RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
@@ -14,17 +12,42 @@ use std::ops;
|
||||
///
|
||||
/// [`write`]: method@crate::sync::RwLock::write
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
#[clippy::has_significant_drop]
|
||||
#[must_use = "if unused the RwLock will immediately unlock"]
|
||||
pub struct RwLockWriteGuard<'a, T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
pub(super) permits_acquired: u32,
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *mut T,
|
||||
pub(super) marker: marker::PhantomData<&'a mut T>,
|
||||
pub(super) marker: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<'a, T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
permits_acquired: u32,
|
||||
s: &'a Semaphore,
|
||||
data: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
fn skip_drop(self) -> Inner<'a, T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: unsafe { std::ptr::read(&me.resource_span) },
|
||||
permits_acquired: me.permits_acquired,
|
||||
s: me.s,
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`RwLockMappedWriteGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockWriteGuard` passed in already
|
||||
@@ -67,19 +90,15 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
RwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
s,
|
||||
permits_acquired: this.permits_acquired,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,19 +153,15 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(RwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
s,
|
||||
permits_acquired: this.permits_acquired,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -198,12 +213,21 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
///
|
||||
/// [`RwLock`]: struct@crate::sync::RwLock
|
||||
pub fn downgrade(self) -> RwLockReadGuard<'a, T> {
|
||||
let RwLockWriteGuard { s, data, .. } = self;
|
||||
let to_release = (self.permits_acquired - 1) as usize;
|
||||
let this = self.skip_drop();
|
||||
let guard = RwLockReadGuard {
|
||||
s: this.s,
|
||||
data: this.data,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: this.resource_span,
|
||||
};
|
||||
|
||||
// Release all but one of the permits held by the write guard
|
||||
s.release(to_release);
|
||||
let to_release = (this.permits_acquired - 1) as usize;
|
||||
this.s.release(to_release);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
write_locked = false,
|
||||
@@ -212,7 +236,7 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
self.resource_span.in_scope(|| {
|
||||
guard.resource_span.in_scope(|| {
|
||||
tracing::trace!(
|
||||
target: "runtime::resource::state_update",
|
||||
current_readers = 1,
|
||||
@@ -220,18 +244,7 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = self.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(self);
|
||||
|
||||
RwLockReadGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
}
|
||||
guard
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::sync::batch_semaphore::Semaphore;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, mem, ops};
|
||||
|
||||
/// RAII structure used to release the exclusive write access of a lock when
|
||||
/// dropped.
|
||||
@@ -13,16 +11,41 @@ use std::ops;
|
||||
///
|
||||
/// [mapping]: method@crate::sync::RwLockWriteGuard::map
|
||||
/// [`RwLockWriteGuard`]: struct@crate::sync::RwLockWriteGuard
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct RwLockMappedWriteGuard<'a, T: ?Sized> {
|
||||
// When changing the fields in this struct, make sure to update the
|
||||
// `skip_drop` method.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) resource_span: tracing::Span,
|
||||
pub(super) permits_acquired: u32,
|
||||
pub(super) s: &'a Semaphore,
|
||||
pub(super) data: *mut T,
|
||||
pub(super) marker: marker::PhantomData<&'a mut T>,
|
||||
pub(super) marker: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // Unused fields are still used in Drop.
|
||||
struct Inner<'a, T: ?Sized> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: tracing::Span,
|
||||
permits_acquired: u32,
|
||||
s: &'a Semaphore,
|
||||
data: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
||||
fn skip_drop(self) -> Inner<'a, T> {
|
||||
let me = mem::ManuallyDrop::new(self);
|
||||
// SAFETY: This duplicates the values in every field of the guard, then
|
||||
// forgets the originals, so in the end no value is duplicated.
|
||||
Inner {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span: unsafe { std::ptr::read(&me.resource_span) },
|
||||
permits_acquired: me.permits_acquired,
|
||||
s: me.s,
|
||||
data: me.data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `RwLockMappedWriteGuard` for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the `RwLockMappedWriteGuard` passed in already
|
||||
@@ -64,20 +87,15 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
RwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
s,
|
||||
permits_acquired: this.permits_acquired,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,20 +149,15 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
let permits_acquired = this.permits_acquired;
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let resource_span = this.resource_span.clone();
|
||||
// NB: Forget to avoid drop impl from being called.
|
||||
mem::forget(this);
|
||||
let this = this.skip_drop();
|
||||
|
||||
Ok(RwLockMappedWriteGuard {
|
||||
permits_acquired,
|
||||
s,
|
||||
permits_acquired: this.permits_acquired,
|
||||
s: this.s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
marker: PhantomData,
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
resource_span,
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ pub struct Semaphore {
|
||||
///
|
||||
/// [`acquire`]: crate::sync::Semaphore::acquire()
|
||||
#[must_use]
|
||||
#[clippy::has_significant_drop]
|
||||
#[derive(Debug)]
|
||||
pub struct SemaphorePermit<'a> {
|
||||
sem: &'a Semaphore,
|
||||
@@ -101,6 +102,7 @@ pub struct SemaphorePermit<'a> {
|
||||
///
|
||||
/// [`acquire_owned`]: crate::sync::Semaphore::acquire_owned()
|
||||
#[must_use]
|
||||
#[clippy::has_significant_drop]
|
||||
#[derive(Debug)]
|
||||
pub struct OwnedSemaphorePermit {
|
||||
sem: Arc<Semaphore>,
|
||||
|
||||
@@ -4,6 +4,11 @@ use loom::future::block_on;
|
||||
use loom::sync::Arc;
|
||||
use loom::thread;
|
||||
|
||||
use tokio_test::{assert_pending, assert_ready};
|
||||
|
||||
/// `util::wake_list::NUM_WAKERS`
|
||||
const WAKE_LIST_SIZE: usize = 32;
|
||||
|
||||
#[test]
|
||||
fn notify_one() {
|
||||
loom::model(|| {
|
||||
@@ -138,3 +143,189 @@ fn notify_drop() {
|
||||
th2.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
/// Polls two `Notified` futures and checks if poll results are consistent
|
||||
/// with each other. If the first future is notified by a `notify_waiters`
|
||||
/// call, then the second one must be notified as well.
|
||||
#[test]
|
||||
fn notify_waiters_poll_consistency() {
|
||||
fn notify_waiters_poll_consistency_variant(poll_setting: [bool; 2]) {
|
||||
let notify = Arc::new(Notify::new());
|
||||
let mut notified = [
|
||||
tokio_test::task::spawn(notify.notified()),
|
||||
tokio_test::task::spawn(notify.notified()),
|
||||
];
|
||||
for i in 0..2 {
|
||||
if poll_setting[i] {
|
||||
assert_pending!(notified[i].poll());
|
||||
}
|
||||
}
|
||||
|
||||
let tx = notify.clone();
|
||||
let th = thread::spawn(move || {
|
||||
tx.notify_waiters();
|
||||
});
|
||||
|
||||
let res1 = notified[0].poll();
|
||||
let res2 = notified[1].poll();
|
||||
|
||||
// If res1 is ready, then res2 must also be ready.
|
||||
assert!(res1.is_pending() || res2.is_ready());
|
||||
|
||||
th.join().unwrap();
|
||||
}
|
||||
|
||||
// We test different scenarios in which pending futures had or had not
|
||||
// been polled before the call to `notify_waiters`.
|
||||
loom::model(|| notify_waiters_poll_consistency_variant([false, false]));
|
||||
loom::model(|| notify_waiters_poll_consistency_variant([true, false]));
|
||||
loom::model(|| notify_waiters_poll_consistency_variant([false, true]));
|
||||
loom::model(|| notify_waiters_poll_consistency_variant([true, true]));
|
||||
}
|
||||
|
||||
/// Polls two `Notified` futures and checks if poll results are consistent
|
||||
/// with each other. If the first future is notified by a `notify_waiters`
|
||||
/// call, then the second one must be notified as well.
|
||||
///
|
||||
/// Here we also add other `Notified` futures in between to force the two
|
||||
/// tested futures to end up in different chunks.
|
||||
#[test]
|
||||
fn notify_waiters_poll_consistency_many() {
|
||||
fn notify_waiters_poll_consistency_many_variant(order: [usize; 2]) {
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
let mut futs = (0..WAKE_LIST_SIZE + 1)
|
||||
.map(|_| tokio_test::task::spawn(notify.notified()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_pending!(futs[order[0]].poll());
|
||||
for i in 2..futs.len() {
|
||||
assert_pending!(futs[i].poll());
|
||||
}
|
||||
assert_pending!(futs[order[1]].poll());
|
||||
|
||||
let tx = notify.clone();
|
||||
let th = thread::spawn(move || {
|
||||
tx.notify_waiters();
|
||||
});
|
||||
|
||||
let res1 = futs[0].poll();
|
||||
let res2 = futs[1].poll();
|
||||
|
||||
// If res1 is ready, then res2 must also be ready.
|
||||
assert!(res1.is_pending() || res2.is_ready());
|
||||
|
||||
th.join().unwrap();
|
||||
}
|
||||
|
||||
// We test different scenarios in which futures are polled in different order.
|
||||
loom::model(|| notify_waiters_poll_consistency_many_variant([0, 1]));
|
||||
loom::model(|| notify_waiters_poll_consistency_many_variant([1, 0]));
|
||||
}
|
||||
|
||||
/// Checks if a call to `notify_waiters` is observed as atomic when combined
|
||||
/// with a concurrent call to `notify_one`.
|
||||
#[test]
|
||||
fn notify_waiters_is_atomic() {
|
||||
fn notify_waiters_is_atomic_variant(tested_fut_index: usize) {
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
let mut futs = (0..WAKE_LIST_SIZE + 1)
|
||||
.map(|_| tokio_test::task::spawn(notify.notified()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for fut in &mut futs {
|
||||
assert_pending!(fut.poll());
|
||||
}
|
||||
|
||||
let tx = notify.clone();
|
||||
let th = thread::spawn(move || {
|
||||
tx.notify_waiters();
|
||||
});
|
||||
|
||||
block_on(async {
|
||||
// If awaiting one of the futures completes, then we should be
|
||||
// able to assume that all pending futures are notified. Therefore
|
||||
// a notification from a subsequent `notify_one` call should not
|
||||
// be consumed by an old future.
|
||||
futs.remove(tested_fut_index).await;
|
||||
|
||||
let mut new_fut = tokio_test::task::spawn(notify.notified());
|
||||
assert_pending!(new_fut.poll());
|
||||
|
||||
notify.notify_one();
|
||||
|
||||
// `new_fut` must consume the notification from `notify_one`.
|
||||
assert_ready!(new_fut.poll());
|
||||
});
|
||||
|
||||
th.join().unwrap();
|
||||
}
|
||||
|
||||
// We test different scenarios in which the tested future is at the beginning
|
||||
// or at the end of the waiters queue used by `Notify`.
|
||||
loom::model(|| notify_waiters_is_atomic_variant(0));
|
||||
loom::model(|| notify_waiters_is_atomic_variant(32));
|
||||
}
|
||||
|
||||
/// Checks if a single call to `notify_waiters` does not get through two `Notified`
|
||||
/// futures created and awaited sequentially like this:
|
||||
/// ```ignore
|
||||
/// notify.notified().await;
|
||||
/// notify.notified().await;
|
||||
/// ```
|
||||
#[test]
|
||||
fn notify_waiters_sequential_notified_await() {
|
||||
use crate::sync::oneshot;
|
||||
|
||||
loom::model(|| {
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
let (tx_fst, rx_fst) = oneshot::channel();
|
||||
let (tx_snd, rx_snd) = oneshot::channel();
|
||||
|
||||
let receiver = thread::spawn({
|
||||
let notify = notify.clone();
|
||||
move || {
|
||||
block_on(async {
|
||||
// Poll the first `Notified` to put it as the first waiter
|
||||
// in the queue.
|
||||
let mut first_notified = tokio_test::task::spawn(notify.notified());
|
||||
assert_pending!(first_notified.poll());
|
||||
|
||||
// Create additional waiters to force `notify_waiters` to
|
||||
// release the lock at least once.
|
||||
let _task_pile = (0..WAKE_LIST_SIZE + 1)
|
||||
.map(|_| {
|
||||
let mut fut = tokio_test::task::spawn(notify.notified());
|
||||
assert_pending!(fut.poll());
|
||||
fut
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// We are ready for the notify_waiters call.
|
||||
tx_fst.send(()).unwrap();
|
||||
|
||||
first_notified.await;
|
||||
|
||||
// Poll the second `Notified` future to try to insert
|
||||
// it to the waiters queue.
|
||||
let mut second_notified = tokio_test::task::spawn(notify.notified());
|
||||
assert_pending!(second_notified.poll());
|
||||
|
||||
// Wait for the `notify_waiters` to end and check if we
|
||||
// are woken up.
|
||||
rx_snd.await.unwrap();
|
||||
assert_pending!(second_notified.poll());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the signal and call `notify_waiters`.
|
||||
block_on(rx_fst).unwrap();
|
||||
notify.notify_waiters();
|
||||
tx_snd.send(()).unwrap();
|
||||
|
||||
receiver.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +46,45 @@ fn notify_clones_waker_before_lock() {
|
||||
let _ = future.poll(&mut cx);
|
||||
}
|
||||
|
||||
#[cfg(panic = "unwind")]
|
||||
#[test]
|
||||
fn notify_waiters_handles_panicking_waker() {
|
||||
use futures::task::ArcWake;
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
struct PanickingWaker(Arc<Notify>);
|
||||
|
||||
impl ArcWake for PanickingWaker {
|
||||
fn wake_by_ref(_arc_self: &Arc<Self>) {
|
||||
panic!("waker panicked");
|
||||
}
|
||||
}
|
||||
|
||||
let bad_fut = notify.notified();
|
||||
pin!(bad_fut);
|
||||
|
||||
let waker = futures::task::waker(Arc::new(PanickingWaker(notify.clone())));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let _ = bad_fut.poll(&mut cx);
|
||||
|
||||
let mut futs = Vec::new();
|
||||
for _ in 0..32 {
|
||||
let mut fut = tokio_test::task::spawn(notify.notified());
|
||||
assert!(fut.poll().is_pending());
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
assert!(std::panic::catch_unwind(|| {
|
||||
notify.notify_waiters();
|
||||
})
|
||||
.is_err());
|
||||
|
||||
for mut fut in futs {
|
||||
assert!(fut.poll().is_ready());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_simple() {
|
||||
let notify = Notify::new();
|
||||
|
||||
@@ -252,3 +252,32 @@ fn cancel_acquire_releases_permits() {
|
||||
assert_eq!(6, s.available_permits());
|
||||
assert_ok!(s.try_acquire(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_permits_at_drop() {
|
||||
use crate::sync::semaphore::*;
|
||||
use futures::task::ArcWake;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
let sem = Arc::new(Semaphore::new(1));
|
||||
|
||||
struct ReleaseOnDrop(Option<OwnedSemaphorePermit>);
|
||||
|
||||
impl ArcWake for ReleaseOnDrop {
|
||||
fn wake_by_ref(_arc_self: &Arc<Self>) {}
|
||||
}
|
||||
|
||||
let mut fut = Box::pin(async {
|
||||
let _permit = sem.acquire().await.unwrap();
|
||||
});
|
||||
|
||||
// Second iteration shouldn't deadlock.
|
||||
for _ in 0..=1 {
|
||||
let waker = futures::task::waker(Arc::new(ReleaseOnDrop(
|
||||
sem.clone().try_acquire_owned().ok(),
|
||||
)));
|
||||
let mut cx = std::task::Context::from_waker(&waker);
|
||||
assert!(fut.as_mut().poll(&mut cx).is_pending());
|
||||
}
|
||||
}
|
||||
|
||||
+71
-3
@@ -58,6 +58,7 @@ use crate::sync::notify::Notify;
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::atomic::Ordering::Relaxed;
|
||||
use crate::loom::sync::{Arc, RwLock, RwLockReadGuard};
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::panic;
|
||||
@@ -166,7 +167,6 @@ impl<'a, T> Ref<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Shared<T> {
|
||||
/// The most recent value.
|
||||
value: RwLock<T>,
|
||||
@@ -181,12 +181,24 @@ struct Shared<T> {
|
||||
ref_count_rx: AtomicUsize,
|
||||
|
||||
/// Notifies waiting receivers that the value changed.
|
||||
notify_rx: Notify,
|
||||
notify_rx: big_notify::BigNotify,
|
||||
|
||||
/// Notifies any task listening for `Receiver` dropped events.
|
||||
notify_tx: Notify,
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Shared<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let state = self.state.load();
|
||||
f.debug_struct("Shared")
|
||||
.field("value", &self.value)
|
||||
.field("version", &state.version())
|
||||
.field("is_closed", &state.is_closed())
|
||||
.field("ref_count_rx", &self.ref_count_rx)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod error {
|
||||
//! Watch error types.
|
||||
|
||||
@@ -221,6 +233,62 @@ pub mod error {
|
||||
impl std::error::Error for RecvError {}
|
||||
}
|
||||
|
||||
mod big_notify {
|
||||
use super::*;
|
||||
use crate::sync::notify::Notified;
|
||||
|
||||
// To avoid contention on the lock inside the `Notify`, we store multiple
|
||||
// copies of it. Then, we use either circular access or randomness to spread
|
||||
// out threads over different `Notify` objects.
|
||||
//
|
||||
// Some simple benchmarks show that randomness performs slightly better than
|
||||
// circular access (probably due to contention on `next`), so we prefer to
|
||||
// use randomness when Tokio is compiled with a random number generator.
|
||||
//
|
||||
// When the random number generator is not available, we fall back to
|
||||
// circular access.
|
||||
|
||||
pub(super) struct BigNotify {
|
||||
#[cfg(not(all(not(loom), feature = "sync", any(feature = "rt", feature = "macros"))))]
|
||||
next: AtomicUsize,
|
||||
inner: [Notify; 8],
|
||||
}
|
||||
|
||||
impl BigNotify {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(not(all(
|
||||
not(loom),
|
||||
feature = "sync",
|
||||
any(feature = "rt", feature = "macros")
|
||||
)))]
|
||||
next: AtomicUsize::new(0),
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn notify_waiters(&self) {
|
||||
for notify in &self.inner {
|
||||
notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
/// This function implements the case where randomness is not available.
|
||||
#[cfg(not(all(not(loom), feature = "sync", any(feature = "rt", feature = "macros"))))]
|
||||
pub(super) fn notified(&self) -> Notified<'_> {
|
||||
let i = self.next.fetch_add(1, Relaxed) % 8;
|
||||
self.inner[i].notified()
|
||||
}
|
||||
|
||||
/// This function implements the case where randomness is available.
|
||||
#[cfg(all(not(loom), feature = "sync", any(feature = "rt", feature = "macros")))]
|
||||
pub(super) fn notified(&self) -> Notified<'_> {
|
||||
let i = crate::runtime::context::thread_rng_n(8) as usize;
|
||||
self.inner[i].notified()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use self::state::{AtomicState, Version};
|
||||
mod state {
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
@@ -320,7 +388,7 @@ pub fn channel<T>(init: T) -> (Sender<T>, Receiver<T>) {
|
||||
value: RwLock::new(init),
|
||||
state: AtomicState::new(),
|
||||
ref_count_rx: AtomicUsize::new(1),
|
||||
notify_rx: Notify::new(),
|
||||
notify_rx: big_notify::BigNotify::new(),
|
||||
notify_tx: Notify::new(),
|
||||
});
|
||||
|
||||
|
||||
+14
-14
@@ -34,14 +34,14 @@ cfg_rt! {
|
||||
/// async fn main() {
|
||||
/// // `Rc` does not implement `Send`, and thus may not be sent between
|
||||
/// // threads safely.
|
||||
/// let unsend_data = Rc::new("my unsend data...");
|
||||
/// let nonsend_data = Rc::new("my nonsend data...");
|
||||
///
|
||||
/// let unsend_data = unsend_data.clone();
|
||||
/// // Because the `async` block here moves `unsend_data`, the future is `!Send`.
|
||||
/// let nonsend_data = nonsend_data.clone();
|
||||
/// // Because the `async` block here moves `nonsend_data`, the future is `!Send`.
|
||||
/// // Since `tokio::spawn` requires the spawned future to implement `Send`, this
|
||||
/// // will not compile.
|
||||
/// tokio::spawn(async move {
|
||||
/// println!("{}", unsend_data);
|
||||
/// println!("{}", nonsend_data);
|
||||
/// // ...
|
||||
/// }).await.unwrap();
|
||||
/// }
|
||||
@@ -60,18 +60,18 @@ cfg_rt! {
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let unsend_data = Rc::new("my unsend data...");
|
||||
/// let nonsend_data = Rc::new("my nonsend data...");
|
||||
///
|
||||
/// // Construct a local task set that can run `!Send` futures.
|
||||
/// let local = task::LocalSet::new();
|
||||
///
|
||||
/// // Run the local task set.
|
||||
/// local.run_until(async move {
|
||||
/// let unsend_data = unsend_data.clone();
|
||||
/// let nonsend_data = nonsend_data.clone();
|
||||
/// // `spawn_local` ensures that the future is spawned on the local
|
||||
/// // task set.
|
||||
/// task::spawn_local(async move {
|
||||
/// println!("{}", unsend_data);
|
||||
/// println!("{}", nonsend_data);
|
||||
/// // ...
|
||||
/// }).await.unwrap();
|
||||
/// }).await;
|
||||
@@ -94,18 +94,18 @@ cfg_rt! {
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let unsend_data = Rc::new("world");
|
||||
/// let nonsend_data = Rc::new("world");
|
||||
/// let local = task::LocalSet::new();
|
||||
///
|
||||
/// let unsend_data2 = unsend_data.clone();
|
||||
/// let nonsend_data2 = nonsend_data.clone();
|
||||
/// local.spawn_local(async move {
|
||||
/// // ...
|
||||
/// println!("hello {}", unsend_data2)
|
||||
/// println!("hello {}", nonsend_data2)
|
||||
/// });
|
||||
///
|
||||
/// local.spawn_local(async move {
|
||||
/// time::sleep(time::Duration::from_millis(100)).await;
|
||||
/// println!("goodbye {}", unsend_data)
|
||||
/// println!("goodbye {}", nonsend_data)
|
||||
/// });
|
||||
///
|
||||
/// // ...
|
||||
@@ -309,15 +309,15 @@ cfg_rt! {
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let unsend_data = Rc::new("my unsend data...");
|
||||
/// let nonsend_data = Rc::new("my nonsend data...");
|
||||
///
|
||||
/// let local = task::LocalSet::new();
|
||||
///
|
||||
/// // Run the local task set.
|
||||
/// local.run_until(async move {
|
||||
/// let unsend_data = unsend_data.clone();
|
||||
/// let nonsend_data = nonsend_data.clone();
|
||||
/// task::spawn_local(async move {
|
||||
/// println!("{}", unsend_data);
|
||||
/// println!("{}", nonsend_data);
|
||||
/// // ...
|
||||
/// }).await.unwrap();
|
||||
/// }).await;
|
||||
|
||||
+69
-34
@@ -29,30 +29,40 @@ cfg_not_test_util! {
|
||||
|
||||
cfg_test_util! {
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::loom::sync::{Arc, Mutex};
|
||||
use crate::loom::sync::Mutex;
|
||||
|
||||
cfg_rt! {
|
||||
fn clock() -> Option<Clock> {
|
||||
#[track_caller]
|
||||
fn with_clock<R>(f: impl FnOnce(Option<&Clock>) -> Result<R, &'static str>) -> R {
|
||||
use crate::runtime::Handle;
|
||||
|
||||
match Handle::try_current() {
|
||||
Ok(handle) => Some(handle.inner.driver().clock().clone()),
|
||||
Err(ref e) if e.is_missing_context() => None,
|
||||
let res = match Handle::try_current() {
|
||||
Ok(handle) => f(Some(handle.inner.driver().clock())),
|
||||
Err(ref e) if e.is_missing_context() => f(None),
|
||||
Err(_) => panic!("{}", crate::util::error::THREAD_LOCAL_DESTROYED_ERROR),
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(ret) => ret,
|
||||
Err(msg) => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_rt! {
|
||||
fn clock() -> Option<Clock> {
|
||||
None
|
||||
#[track_caller]
|
||||
fn with_clock<R>(f: impl FnOnce(Option<&Clock>) -> Result<R, &'static str>) -> R {
|
||||
match f(None) {
|
||||
Ok(ret) => ret,
|
||||
Err(msg) => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a source of time.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Clock {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -107,8 +117,12 @@ cfg_test_util! {
|
||||
/// [`advance`]: crate::time::advance
|
||||
#[track_caller]
|
||||
pub fn pause() {
|
||||
let clock = clock().expect("time cannot be frozen from outside the Tokio runtime");
|
||||
clock.pause();
|
||||
with_clock(|maybe_clock| {
|
||||
match maybe_clock {
|
||||
Some(clock) => clock.pause(),
|
||||
None => Err("time cannot be frozen from outside the Tokio runtime"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Resumes time.
|
||||
@@ -122,14 +136,21 @@ cfg_test_util! {
|
||||
/// runtime.
|
||||
#[track_caller]
|
||||
pub fn resume() {
|
||||
let clock = clock().expect("time cannot be frozen from outside the Tokio runtime");
|
||||
let mut inner = clock.inner.lock();
|
||||
with_clock(|maybe_clock| {
|
||||
let clock = match maybe_clock {
|
||||
Some(clock) => clock,
|
||||
None => return Err("time cannot be frozen from outside the Tokio runtime"),
|
||||
};
|
||||
|
||||
if inner.unfrozen.is_some() {
|
||||
panic!("time is not frozen");
|
||||
}
|
||||
let mut inner = clock.inner.lock();
|
||||
|
||||
inner.unfrozen = Some(std::time::Instant::now());
|
||||
if inner.unfrozen.is_some() {
|
||||
return Err("time is not frozen");
|
||||
}
|
||||
|
||||
inner.unfrozen = Some(std::time::Instant::now());
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Advances time.
|
||||
@@ -164,19 +185,27 @@ cfg_test_util! {
|
||||
///
|
||||
/// [`sleep`]: fn@crate::time::sleep
|
||||
pub async fn advance(duration: Duration) {
|
||||
let clock = clock().expect("time cannot be frozen from outside the Tokio runtime");
|
||||
clock.advance(duration);
|
||||
with_clock(|maybe_clock| {
|
||||
let clock = match maybe_clock {
|
||||
Some(clock) => clock,
|
||||
None => return Err("time cannot be frozen from outside the Tokio runtime"),
|
||||
};
|
||||
|
||||
clock.advance(duration)
|
||||
});
|
||||
|
||||
crate::task::yield_now().await;
|
||||
}
|
||||
|
||||
/// Returns the current instant, factoring in frozen time.
|
||||
pub(crate) fn now() -> Instant {
|
||||
if let Some(clock) = clock() {
|
||||
clock.now()
|
||||
} else {
|
||||
Instant::from_std(std::time::Instant::now())
|
||||
}
|
||||
with_clock(|maybe_clock| {
|
||||
Ok(if let Some(clock) = maybe_clock {
|
||||
clock.now()
|
||||
} else {
|
||||
Instant::from_std(std::time::Instant::now())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
@@ -186,34 +215,40 @@ cfg_test_util! {
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let clock = Clock {
|
||||
inner: Arc::new(Mutex::new(Inner {
|
||||
inner: Mutex::new(Inner {
|
||||
enable_pausing,
|
||||
base: now,
|
||||
unfrozen: Some(now),
|
||||
auto_advance_inhibit_count: 0,
|
||||
})),
|
||||
}),
|
||||
};
|
||||
|
||||
if start_paused {
|
||||
clock.pause();
|
||||
if let Err(msg) = clock.pause() {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
clock
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn pause(&self) {
|
||||
pub(crate) fn pause(&self) -> Result<(), &'static str> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
if !inner.enable_pausing {
|
||||
drop(inner); // avoid poisoning the lock
|
||||
panic!("`time::pause()` requires the `current_thread` Tokio runtime. \
|
||||
return Err("`time::pause()` requires the `current_thread` Tokio runtime. \
|
||||
This is the default Runtime used by `#[tokio::test].");
|
||||
}
|
||||
|
||||
let elapsed = inner.unfrozen.as_ref().expect("time is already frozen").elapsed();
|
||||
let elapsed = match inner.unfrozen.as_ref() {
|
||||
Some(v) => v.elapsed(),
|
||||
None => return Err("time is already frozen")
|
||||
};
|
||||
inner.base += elapsed;
|
||||
inner.unfrozen = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Temporarily stop auto-advancing the clock (see `tokio::time::pause`).
|
||||
@@ -232,15 +267,15 @@ cfg_test_util! {
|
||||
inner.unfrozen.is_none() && inner.auto_advance_inhibit_count == 0
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn advance(&self, duration: Duration) {
|
||||
pub(crate) fn advance(&self, duration: Duration) -> Result<(), &'static str> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
if inner.unfrozen.is_some() {
|
||||
panic!("time is not frozen");
|
||||
return Err("time is not frozen");
|
||||
}
|
||||
|
||||
inner.base += duration;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn now(&self) -> Instant {
|
||||
|
||||
@@ -235,7 +235,6 @@ pin_project! {
|
||||
cfg_trace! {
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
deadline: Instant,
|
||||
ctx: trace::AsyncOpTracingCtx,
|
||||
}
|
||||
}
|
||||
@@ -243,7 +242,6 @@ cfg_trace! {
|
||||
cfg_not_trace! {
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
deadline: Instant,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,10 +259,11 @@ impl Sleep {
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let inner = {
|
||||
let clock = handle.driver().clock();
|
||||
let handle = &handle.driver().time();
|
||||
let time_source = handle.time_source();
|
||||
let deadline_tick = time_source.deadline_to_tick(deadline);
|
||||
let duration = deadline_tick.saturating_sub(time_source.now());
|
||||
let duration = deadline_tick.saturating_sub(time_source.now(clock));
|
||||
|
||||
let location = location.expect("should have location if tracing");
|
||||
let resource_span = tracing::trace_span!(
|
||||
@@ -296,11 +295,11 @@ impl Sleep {
|
||||
resource_span,
|
||||
};
|
||||
|
||||
Inner { deadline, ctx }
|
||||
Inner { ctx }
|
||||
};
|
||||
|
||||
#[cfg(not(all(tokio_unstable, feature = "tracing")))]
|
||||
let inner = Inner { deadline };
|
||||
let inner = Inner {};
|
||||
|
||||
Sleep { inner, entry }
|
||||
}
|
||||
@@ -311,7 +310,7 @@ impl Sleep {
|
||||
|
||||
/// Returns the instant at which the future will complete.
|
||||
pub fn deadline(&self) -> Instant {
|
||||
self.inner.deadline
|
||||
self.entry.deadline()
|
||||
}
|
||||
|
||||
/// Returns `true` if `Sleep` has elapsed.
|
||||
@@ -357,7 +356,6 @@ impl Sleep {
|
||||
fn reset_inner(self: Pin<&mut Self>, deadline: Instant) {
|
||||
let mut me = self.project();
|
||||
me.entry.as_mut().reset(deadline);
|
||||
(me.inner).deadline = deadline;
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
{
|
||||
@@ -370,8 +368,9 @@ impl Sleep {
|
||||
tracing::trace_span!("runtime.resource.async_op.poll");
|
||||
|
||||
let duration = {
|
||||
let clock = me.entry.clock();
|
||||
let time_source = me.entry.driver().time_source();
|
||||
let now = time_source.now();
|
||||
let now = time_source.now(clock);
|
||||
let deadline_tick = time_source.deadline_to_tick(deadline);
|
||||
deadline_tick.saturating_sub(now)
|
||||
};
|
||||
|
||||
+103
-19
@@ -178,8 +178,12 @@ impl<L: Link> LinkedList<L, L::Target> {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller **must** ensure that `node` is currently contained by
|
||||
/// `self` or not contained by any other list.
|
||||
/// The caller **must** ensure that exactly one of the following is true:
|
||||
/// - `node` is currently contained by `self`,
|
||||
/// - `node` is not contained by any list,
|
||||
/// - `node` is currently contained by some other `GuardedLinkedList` **and**
|
||||
/// the caller has an exclusive access to that list. This condition is
|
||||
/// used by the linked list in `sync::Notify`.
|
||||
pub(crate) unsafe fn remove(&mut self, node: NonNull<L::Target>) -> Option<L::Handle> {
|
||||
if let Some(prev) = L::pointers(node).as_ref().get_prev() {
|
||||
debug_assert_eq!(L::pointers(prev).as_ref().get_next(), Some(node));
|
||||
@@ -290,6 +294,96 @@ cfg_io_readiness! {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl GuardedLinkedList =====
|
||||
|
||||
feature! {
|
||||
#![any(
|
||||
feature = "process",
|
||||
feature = "sync",
|
||||
feature = "rt",
|
||||
feature = "signal",
|
||||
)]
|
||||
|
||||
/// An intrusive linked list, but instead of keeping pointers to the head
|
||||
/// and tail nodes, it uses a special guard node linked with those nodes.
|
||||
/// It means that the list is circular and every pointer of a node from
|
||||
/// the list is not `None`, including pointers from the guard node.
|
||||
///
|
||||
/// If a list is empty, then both pointers of the guard node are pointing
|
||||
/// at the guard node itself.
|
||||
pub(crate) struct GuardedLinkedList<L, T> {
|
||||
/// Pointer to the guard node.
|
||||
guard: NonNull<T>,
|
||||
|
||||
/// Node type marker.
|
||||
_marker: PhantomData<*const L>,
|
||||
}
|
||||
|
||||
impl<U, L: Link<Handle = NonNull<U>>> LinkedList<L, L::Target> {
|
||||
/// Turns a linked list into the guarded version by linking the guard node
|
||||
/// with the head and tail nodes. Like with other nodes, you should guarantee
|
||||
/// that the guard node is pinned in memory.
|
||||
pub(crate) fn into_guarded(self, guard_handle: L::Handle) -> GuardedLinkedList<L, L::Target> {
|
||||
// `guard_handle` is a NonNull pointer, we don't have to care about dropping it.
|
||||
let guard = L::as_raw(&guard_handle);
|
||||
|
||||
unsafe {
|
||||
if let Some(head) = self.head {
|
||||
debug_assert!(L::pointers(head).as_ref().get_prev().is_none());
|
||||
L::pointers(head).as_mut().set_prev(Some(guard));
|
||||
L::pointers(guard).as_mut().set_next(Some(head));
|
||||
|
||||
// The list is not empty, so the tail cannot be `None`.
|
||||
let tail = self.tail.unwrap();
|
||||
debug_assert!(L::pointers(tail).as_ref().get_next().is_none());
|
||||
L::pointers(tail).as_mut().set_next(Some(guard));
|
||||
L::pointers(guard).as_mut().set_prev(Some(tail));
|
||||
} else {
|
||||
// The list is empty.
|
||||
L::pointers(guard).as_mut().set_prev(Some(guard));
|
||||
L::pointers(guard).as_mut().set_next(Some(guard));
|
||||
}
|
||||
}
|
||||
|
||||
GuardedLinkedList { guard, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Link> GuardedLinkedList<L, L::Target> {
|
||||
fn tail(&self) -> Option<NonNull<L::Target>> {
|
||||
let tail_ptr = unsafe {
|
||||
L::pointers(self.guard).as_ref().get_prev().unwrap()
|
||||
};
|
||||
|
||||
// Compare the tail pointer with the address of the guard node itself.
|
||||
// If the guard points at itself, then there are no other nodes and
|
||||
// the list is considered empty.
|
||||
if tail_ptr != self.guard {
|
||||
Some(tail_ptr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the last element from a list and returns it, or None if it is
|
||||
/// empty.
|
||||
pub(crate) fn pop_back(&mut self) -> Option<L::Handle> {
|
||||
unsafe {
|
||||
let last = self.tail()?;
|
||||
let before_last = L::pointers(last).as_ref().get_prev().unwrap();
|
||||
|
||||
L::pointers(self.guard).as_mut().set_prev(Some(before_last));
|
||||
L::pointers(before_last).as_mut().set_next(Some(self.guard));
|
||||
|
||||
L::pointers(last).as_mut().set_prev(None);
|
||||
L::pointers(last).as_mut().set_next(None);
|
||||
|
||||
Some(L::from_raw(last))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Pointers =====
|
||||
|
||||
impl<T> Pointers<T> {
|
||||
@@ -352,9 +446,9 @@ impl<T> fmt::Debug for Pointers<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, fuzzing))]
|
||||
#[cfg(not(loom))]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::pin::Pin;
|
||||
@@ -623,31 +717,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_wasm))]
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn fuzz_linked_list(ops: Vec<usize>) {
|
||||
run_fuzz(ops);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_wasm))]
|
||||
fn run_fuzz(ops: Vec<usize>) {
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg(fuzzing)]
|
||||
pub fn fuzz_linked_list(ops: &[u8]) {
|
||||
enum Op {
|
||||
Push,
|
||||
Pop,
|
||||
Remove(usize),
|
||||
}
|
||||
use std::collections::VecDeque;
|
||||
|
||||
let ops = ops
|
||||
.iter()
|
||||
.map(|i| match i % 3 {
|
||||
.map(|i| match i % 3u8 {
|
||||
0 => Op::Push,
|
||||
1 => Op::Pop,
|
||||
2 => Op::Remove(i / 3),
|
||||
2 => Op::Remove((i / 3u8) as usize),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
use core::fmt;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
|
||||
// Starting from Intel's Sandy Bridge, spatial prefetcher is now pulling pairs of 64-byte cache
|
||||
// lines at a time, so we have to align to 128 bytes rather than 64.
|
||||
//
|
||||
// Sources:
|
||||
// - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
|
||||
// - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107
|
||||
#[cfg_attr(target_arch = "x86_64", repr(align(128)))]
|
||||
#[cfg_attr(not(target_arch = "x86_64"), repr(align(64)))]
|
||||
pub(crate) struct CachePadded<T> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for CachePadded<T> {}
|
||||
unsafe impl<T: Sync> Sync for CachePadded<T> {}
|
||||
|
||||
impl<T> CachePadded<T> {
|
||||
pub(crate) fn new(t: T) -> CachePadded<T> {
|
||||
CachePadded::<T> { value: t }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for CachePadded<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for CachePadded<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for CachePadded<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CachePadded")
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for CachePadded<T> {
|
||||
fn from(t: T) -> Self {
|
||||
CachePadded::new(t)
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,11 @@ impl FastRand {
|
||||
old_seed
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "macros", feature = "rt-multi-thread"))]
|
||||
#[cfg(any(
|
||||
feature = "macros",
|
||||
feature = "rt-multi-thread",
|
||||
all(feature = "sync", feature = "rt")
|
||||
))]
|
||||
pub(crate) fn fastrand_n(&self, n: u32) -> u32 {
|
||||
// This is similar to fastrand() % n, but faster.
|
||||
// See https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
|
||||
@@ -334,6 +334,15 @@ assert_value!(tokio::sync::OnceCell<YY>: Send & Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMutexGuard<NN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMutexGuard<YN>: Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMutexGuard<YY>: Send & Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<NN,NN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<NN,YN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<NN,YY>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YN,NN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YN,YN>: Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YN,YY>: Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YY,NN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YY,YN>: Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedMappedMutexGuard<YY,YY>: Send & Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedRwLockMappedWriteGuard<NN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedRwLockMappedWriteGuard<YN>: !Send & !Sync & Unpin);
|
||||
assert_value!(tokio::sync::OwnedRwLockMappedWriteGuard<YY>: Send & Sync & Unpin);
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::io;
|
||||
use std::mem;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::time::Duration;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, PipeMode, ServerOptions};
|
||||
use tokio::time;
|
||||
use windows_sys::Win32::Foundation::{ERROR_NO_DATA, ERROR_PIPE_BUSY, NO_ERROR, UNICODE_STRING};
|
||||
@@ -327,17 +327,51 @@ async fn test_named_pipe_multi_client_ready() -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This tests what happens when a client tries to disconnect.
|
||||
// This tests that message mode works as expected.
|
||||
#[tokio::test]
|
||||
async fn test_named_pipe_mode_message() -> io::Result<()> {
|
||||
const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-mode-message";
|
||||
// it's easy to accidentally get a seemingly working test here because byte pipes
|
||||
// often return contents at write boundaries. to make sure we're doing the right thing we
|
||||
// explicitly test that it doesn't work in byte mode.
|
||||
_named_pipe_mode_message(PipeMode::Message).await?;
|
||||
_named_pipe_mode_message(PipeMode::Byte).await
|
||||
}
|
||||
|
||||
let server = ServerOptions::new()
|
||||
.pipe_mode(PipeMode::Message)
|
||||
.create(PIPE_NAME)?;
|
||||
async fn _named_pipe_mode_message(mode: PipeMode) -> io::Result<()> {
|
||||
let pipe_name = format!(
|
||||
r"\\.\pipe\test-named-pipe-mode-message-{}",
|
||||
matches!(mode, PipeMode::Message)
|
||||
);
|
||||
let mut buf = [0u8; 32];
|
||||
|
||||
let mut server = ServerOptions::new()
|
||||
.first_pipe_instance(true)
|
||||
.pipe_mode(mode)
|
||||
.create(&pipe_name)?;
|
||||
|
||||
let mut client = ClientOptions::new().pipe_mode(mode).open(&pipe_name)?;
|
||||
|
||||
let _ = ClientOptions::new().open(PIPE_NAME)?;
|
||||
server.connect().await?;
|
||||
|
||||
// this needs a few iterations, presumably Windows waits for a few calls before merging buffers
|
||||
for _ in 0..10 {
|
||||
client.write_all(b"hello").await?;
|
||||
server.write_all(b"world").await?;
|
||||
}
|
||||
for _ in 0..10 {
|
||||
let n = server.read(&mut buf).await?;
|
||||
if buf[..n] != b"hello"[..] {
|
||||
assert!(matches!(mode, PipeMode::Byte));
|
||||
return Ok(());
|
||||
}
|
||||
let n = client.read(&mut buf).await?;
|
||||
if buf[..n] != b"world"[..] {
|
||||
assert!(matches!(mode, PipeMode::Byte));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// byte mode should have errored before.
|
||||
assert!(matches!(mode, PipeMode::Message));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user