381 lines
9.0 KiB
Rust
381 lines
9.0 KiB
Rust
|
|
#![warn(rust_2018_idioms)]
|
||
|
|
|
||
|
|
use futures::future::pending;
|
||
|
|
#[cfg(tokio_unstable)]
|
||
|
|
use std::rc::Rc;
|
||
|
|
use tokio::sync::mpsc;
|
||
|
|
use tokio::task::LocalSet;
|
||
|
|
use tokio_test::{assert_pending, assert_ready, task};
|
||
|
|
use tokio_util::task::TaskTracker;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn open_close() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
assert!(!tracker.is_closed());
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert_eq!(tracker.len(), 0);
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert_eq!(tracker.len(), 0);
|
||
|
|
|
||
|
|
tracker.reopen();
|
||
|
|
assert!(!tracker.is_closed());
|
||
|
|
tracker.reopen();
|
||
|
|
assert!(!tracker.is_closed());
|
||
|
|
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert_eq!(tracker.len(), 0);
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
tracker.close();
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert_eq!(tracker.len(), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn token_len() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
let mut tokens = Vec::new();
|
||
|
|
for i in 0..10 {
|
||
|
|
assert_eq!(tracker.len(), i);
|
||
|
|
tokens.push(tracker.token());
|
||
|
|
}
|
||
|
|
|
||
|
|
assert!(!tracker.is_empty());
|
||
|
|
assert_eq!(tracker.len(), 10);
|
||
|
|
|
||
|
|
for (i, token) in tokens.into_iter().enumerate() {
|
||
|
|
drop(token);
|
||
|
|
assert_eq!(tracker.len(), 9 - i);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_immediately() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.close();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_immediately_on_reopen() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.close();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
tracker.reopen();
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_on_close() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
tracker.close();
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_on_close_reopen() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
tracker.close();
|
||
|
|
tracker.reopen();
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_on_last_task() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.close();
|
||
|
|
let token = tracker.token();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
drop(token);
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_on_last_task_respawn() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.close();
|
||
|
|
let token = tracker.token();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
drop(token);
|
||
|
|
let token2 = tracker.token();
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
drop(token2);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn no_notify_on_respawn_if_open() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
let token = tracker.token();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
drop(token);
|
||
|
|
let token2 = tracker.token();
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
drop(token2);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn close_during_exit() {
|
||
|
|
const ITERS: usize = 5;
|
||
|
|
|
||
|
|
for close_spot in 0..=ITERS {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
let tokens: Vec<_> = (0..ITERS).map(|_| tracker.token()).collect();
|
||
|
|
|
||
|
|
let mut wait = task::spawn(tracker.wait());
|
||
|
|
|
||
|
|
for (i, token) in tokens.into_iter().enumerate() {
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
if i == close_spot {
|
||
|
|
tracker.close();
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
}
|
||
|
|
drop(token);
|
||
|
|
}
|
||
|
|
|
||
|
|
if close_spot == ITERS {
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
tracker.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn notify_many() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
let mut waits: Vec<_> = (0..10).map(|_| task::spawn(tracker.wait())).collect();
|
||
|
|
|
||
|
|
for wait in &mut waits {
|
||
|
|
assert_pending!(wait.poll());
|
||
|
|
}
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
|
||
|
|
for wait in &mut waits {
|
||
|
|
assert_ready!(wait.poll());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(tokio_unstable)]
|
||
|
|
mod spawn {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
/// Spawn several tasks, and then close the [`TaskTracker`].
|
||
|
|
#[tokio::test(flavor = "local")]
|
||
|
|
async fn spawn_then_close() {
|
||
|
|
const N: usize = 8;
|
||
|
|
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
tracker.spawn(async {});
|
||
|
|
}
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
tracker.spawn_on(async {}, &tokio::runtime::Handle::current());
|
||
|
|
}
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
tracker.wait().await;
|
||
|
|
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(tokio_unstable)]
|
||
|
|
mod spawn_local {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
#[should_panic(
|
||
|
|
expected = "`spawn_local` called from outside of a `task::LocalSet` or `runtime::LocalRuntime`"
|
||
|
|
)]
|
||
|
|
fn panic_outside_any_runtime() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.spawn_local(async {});
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test(flavor = "multi_thread")]
|
||
|
|
#[should_panic(
|
||
|
|
expected = "`spawn_local` called from outside of a `task::LocalSet` or `runtime::LocalRuntime`"
|
||
|
|
)]
|
||
|
|
async fn panic_in_multi_thread_runtime() {
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
tracker.spawn_local(async {});
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Spawn several tasks, and then close the [`TaskTracker`].
|
||
|
|
#[tokio::test(flavor = "local")]
|
||
|
|
async fn spawn_then_close() {
|
||
|
|
const N: usize = 8;
|
||
|
|
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
let rc = Rc::new(());
|
||
|
|
tracker.spawn_local(async move {
|
||
|
|
drop(rc);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
tracker.wait().await;
|
||
|
|
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Close the [`TaskTracker`], and then spawn several tasks
|
||
|
|
#[tokio::test(flavor = "local")]
|
||
|
|
async fn spawn_after_close() {
|
||
|
|
const N: usize = 8;
|
||
|
|
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
let rc = Rc::new(());
|
||
|
|
tracker.spawn_local(async move {
|
||
|
|
drop(rc);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
tracker.wait().await;
|
||
|
|
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mod spawn_local_on {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[cfg(tokio_unstable)]
|
||
|
|
mod local_runtime {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
/// Spawn several tasks, and then close the [`TaskTracker`].
|
||
|
|
#[tokio::test(flavor = "local")]
|
||
|
|
async fn spawn_then_close() {
|
||
|
|
const N: usize = 8;
|
||
|
|
let local_set = LocalSet::new();
|
||
|
|
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
let rc = Rc::new(());
|
||
|
|
tracker.spawn_local_on(
|
||
|
|
async move {
|
||
|
|
drop(rc);
|
||
|
|
},
|
||
|
|
&local_set,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
local_set
|
||
|
|
.run_until(async {
|
||
|
|
tracker.close();
|
||
|
|
tracker.wait().await;
|
||
|
|
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
})
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mod local_set {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
/// Spawn several pending-forever tasks, and then drop the [`TaskTracker`]
|
||
|
|
/// while the `LocalSet` is already driven.
|
||
|
|
#[tokio::test(flavor = "current_thread")]
|
||
|
|
async fn spawn_then_drop() {
|
||
|
|
const N: usize = 8;
|
||
|
|
let local = LocalSet::new();
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
|
||
|
|
|
||
|
|
for _i in 0..N {
|
||
|
|
let tx = tx.clone();
|
||
|
|
tracker.spawn_local_on(
|
||
|
|
async move {
|
||
|
|
pending::<()>().await;
|
||
|
|
drop(tx);
|
||
|
|
},
|
||
|
|
&local,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
drop(tx);
|
||
|
|
|
||
|
|
local
|
||
|
|
.run_until(async move {
|
||
|
|
drop(tracker);
|
||
|
|
tokio::task::yield_now().await;
|
||
|
|
|
||
|
|
use tokio::sync::mpsc::error::TryRecvError;
|
||
|
|
|
||
|
|
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
|
||
|
|
})
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Close the tracker first, spawn several pending-forever tasks,
|
||
|
|
/// then wait while the`LocalSet` is already driven.
|
||
|
|
#[tokio::test(flavor = "current_thread")]
|
||
|
|
async fn close_then_spawn() {
|
||
|
|
const N: usize = 8;
|
||
|
|
let local = LocalSet::new();
|
||
|
|
let tracker = TaskTracker::new();
|
||
|
|
|
||
|
|
tracker.close();
|
||
|
|
|
||
|
|
for _ in 0..N {
|
||
|
|
let rc = std::rc::Rc::new(());
|
||
|
|
tracker.spawn_local_on(
|
||
|
|
async move {
|
||
|
|
drop(rc);
|
||
|
|
},
|
||
|
|
&local,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
local
|
||
|
|
.run_until(async move {
|
||
|
|
tracker.wait().await;
|
||
|
|
assert!(tracker.is_closed());
|
||
|
|
assert!(tracker.is_empty());
|
||
|
|
})
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|