mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-08 00:00:13 +02:00
tokio: re-enable timer in runtimes (#1237)
This also brings back the timer tests in the tokio crate.
This commit is contained in:
+4
-4
@@ -33,7 +33,7 @@ default = [
|
|||||||
"rt-full",
|
"rt-full",
|
||||||
"sync",
|
"sync",
|
||||||
"tcp",
|
"tcp",
|
||||||
# "timer",
|
"timer",
|
||||||
"udp",
|
"udp",
|
||||||
# "uds",
|
# "uds",
|
||||||
]
|
]
|
||||||
@@ -45,7 +45,7 @@ reactor = ["io", "tokio-reactor"]
|
|||||||
rt-full = [
|
rt-full = [
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"reactor",
|
"reactor",
|
||||||
# "timer",
|
"timer",
|
||||||
"tokio-current-thread",
|
"tokio-current-thread",
|
||||||
"tokio-executor",
|
"tokio-executor",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
@@ -54,7 +54,7 @@ rt-full = [
|
|||||||
]
|
]
|
||||||
sync = ["tokio-sync"]
|
sync = ["tokio-sync"]
|
||||||
tcp = ["tokio-tcp"]
|
tcp = ["tokio-tcp"]
|
||||||
#timer = ["tokio-timer"]
|
timer = ["tokio-timer"]
|
||||||
udp = ["tokio-udp"]
|
udp = ["tokio-udp"]
|
||||||
#uds = ["tokio-uds"]
|
#uds = ["tokio-uds"]
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ tokio-sync = { version = "0.2.0", optional = true, path = "../tokio-sync" }
|
|||||||
#tokio-threadpool = { version = "0.2.0", optional = true, path = "../tokio-threadpool" }
|
#tokio-threadpool = { version = "0.2.0", optional = true, path = "../tokio-threadpool" }
|
||||||
tokio-tcp = { version = "0.2.0", optional = true, path = "../tokio-tcp" }
|
tokio-tcp = { version = "0.2.0", optional = true, path = "../tokio-tcp" }
|
||||||
tokio-udp = { version = "0.2.0", optional = true, path = "../tokio-udp" }
|
tokio-udp = { version = "0.2.0", optional = true, path = "../tokio-udp" }
|
||||||
#tokio-timer = { version = "0.3.0", optional = true, path = "../tokio-timer" }
|
tokio-timer = { version = "0.3.0", optional = true, path = "../tokio-timer" }
|
||||||
tracing-core = { version = "0.1", optional = true }
|
tracing-core = { version = "0.1", optional = true }
|
||||||
|
|
||||||
# Needed for async/await preview support
|
# Needed for async/await preview support
|
||||||
|
|||||||
+1
-1
@@ -100,7 +100,7 @@ pub mod reactor;
|
|||||||
pub mod sync;
|
pub mod sync;
|
||||||
#[cfg(feature = "timer")]
|
#[cfg(feature = "timer")]
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
//pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
if_runtime! {
|
if_runtime! {
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
//!
|
//!
|
||||||
//! The prelude may grow over time as additional items see ubiquitous use.
|
//! The prelude may grow over time as additional items see ubiquitous use.
|
||||||
|
|
||||||
|
pub use crate::util::FutureExt;
|
||||||
pub use std::future::Future;
|
pub use std::future::Future;
|
||||||
pub use std::task::{self, Poll};
|
pub use std::task::{self, Poll};
|
||||||
//pub use crate::util::{FutureExt, StreamExt};
|
|
||||||
#[cfg(feature = "io")]
|
#[cfg(feature = "io")]
|
||||||
pub use tokio_io::{AsyncRead, AsyncWrite};
|
pub use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::runtime::current_thread::Runtime;
|
use crate::runtime::current_thread::Runtime;
|
||||||
use tokio_current_thread::CurrentThread;
|
use tokio_current_thread::CurrentThread;
|
||||||
use tokio_reactor::Reactor;
|
use tokio_reactor::Reactor;
|
||||||
//use tokio_timer::clock::Clock;
|
use tokio_timer::clock::Clock;
|
||||||
//use tokio_timer::timer::Timer;
|
use tokio_timer::timer::Timer;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Builds a Single-threaded runtime with custom configuration values.
|
/// Builds a Single-threaded runtime with custom configuration values.
|
||||||
@@ -35,8 +35,8 @@ use std::io;
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
// /// The clock to use
|
/// The clock to use
|
||||||
//clock: Clock,
|
clock: Clock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@@ -46,17 +46,15 @@ impl Builder {
|
|||||||
/// Configuration methods can be chained on the return value.
|
/// Configuration methods can be chained on the return value.
|
||||||
pub fn new() -> Builder {
|
pub fn new() -> Builder {
|
||||||
Builder {
|
Builder {
|
||||||
//clock: Clock::new(),
|
clock: Clock::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Set the `Clock` instance that will be used by the runtime.
|
/// Set the `Clock` instance that will be used by the runtime.
|
||||||
pub fn clock(&mut self, clock: Clock) -> &mut Self {
|
pub fn clock(&mut self, clock: Clock) -> &mut Self {
|
||||||
self.clock = clock;
|
self.clock = clock;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/// Create the configured `Runtime`.
|
/// Create the configured `Runtime`.
|
||||||
pub fn build(&mut self) -> io::Result<Runtime> {
|
pub fn build(&mut self) -> io::Result<Runtime> {
|
||||||
@@ -66,18 +64,18 @@ impl Builder {
|
|||||||
|
|
||||||
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the
|
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the
|
||||||
// reactor pick up some new external events.
|
// reactor pick up some new external events.
|
||||||
//let timer = Timer::new_with_now(reactor, self.clock.clone());
|
let timer = Timer::new_with_now(reactor, self.clock.clone());
|
||||||
//let timer_handle = timer.handle();
|
let timer_handle = timer.handle();
|
||||||
|
|
||||||
// And now put a single-threaded executor on top of the timer. When there are no futures ready
|
// And now put a single-threaded executor on top of the timer. When there are no futures ready
|
||||||
// to do something, it'll let the timer or the reactor to generate some new stimuli for the
|
// to do something, it'll let the timer or the reactor to generate some new stimuli for the
|
||||||
// futures to continue in their life.
|
// futures to continue in their life.
|
||||||
let executor = CurrentThread::new_with_park(reactor /*timer*/);
|
let executor = CurrentThread::new_with_park(timer);
|
||||||
|
|
||||||
let runtime = Runtime::new2(
|
let runtime = Runtime::new2(
|
||||||
reactor_handle,
|
reactor_handle,
|
||||||
//timer_handle,
|
timer_handle,
|
||||||
//self.clock.clone(),
|
self.clock.clone(),
|
||||||
executor,
|
executor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use tokio_current_thread::Handle as ExecutorHandle;
|
|||||||
use tokio_current_thread::{self as current_thread, CurrentThread};
|
use tokio_current_thread::{self as current_thread, CurrentThread};
|
||||||
use tokio_executor;
|
use tokio_executor;
|
||||||
use tokio_reactor::{self, Reactor};
|
use tokio_reactor::{self, Reactor};
|
||||||
//use tokio_timer::clock::{self, Clock};
|
use tokio_timer::clock::{self, Clock};
|
||||||
//use tokio_timer::timer::{self, Timer};
|
use tokio_timer::timer::{self, Timer};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@@ -19,13 +19,12 @@ use std::io;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
reactor_handle: tokio_reactor::Handle,
|
reactor_handle: tokio_reactor::Handle,
|
||||||
//timer_handle: timer::Handle,
|
timer_handle: timer::Handle,
|
||||||
//clock: Clock,
|
clock: Clock,
|
||||||
executor: CurrentThread<Parker>,
|
executor: CurrentThread<Parker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//pub(super) type Parker = Timer<Reactor>;
|
pub(super) type Parker = Timer<Reactor>;
|
||||||
pub(super) type Parker = Reactor;
|
|
||||||
|
|
||||||
/// Handle to spawn a future on the corresponding `CurrentThread` runtime instance
|
/// Handle to spawn a future on the corresponding `CurrentThread` runtime instance
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -101,14 +100,14 @@ impl Runtime {
|
|||||||
|
|
||||||
pub(super) fn new2(
|
pub(super) fn new2(
|
||||||
reactor_handle: tokio_reactor::Handle,
|
reactor_handle: tokio_reactor::Handle,
|
||||||
//timer_handle: timer::Handle,
|
timer_handle: timer::Handle,
|
||||||
//clock: Clock,
|
clock: Clock,
|
||||||
executor: CurrentThread<Parker>,
|
executor: CurrentThread<Parker>,
|
||||||
) -> Runtime {
|
) -> Runtime {
|
||||||
Runtime {
|
Runtime {
|
||||||
reactor_handle,
|
reactor_handle,
|
||||||
//timer_handle,
|
timer_handle,
|
||||||
//clock,
|
clock,
|
||||||
executor,
|
executor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,8 +196,8 @@ impl Runtime {
|
|||||||
{
|
{
|
||||||
let Runtime {
|
let Runtime {
|
||||||
ref reactor_handle,
|
ref reactor_handle,
|
||||||
//ref timer_handle,
|
ref timer_handle,
|
||||||
//ref clock,
|
ref clock,
|
||||||
ref mut executor,
|
ref mut executor,
|
||||||
..
|
..
|
||||||
} = *self;
|
} = *self;
|
||||||
@@ -209,20 +208,20 @@ impl Runtime {
|
|||||||
// This will set the default handle and timer to use inside the closure
|
// This will set the default handle and timer to use inside the closure
|
||||||
// and run the future.
|
// and run the future.
|
||||||
tokio_reactor::with_default(&reactor_handle, &mut enter, |enter| {
|
tokio_reactor::with_default(&reactor_handle, &mut enter, |enter| {
|
||||||
//clock::with_default(clock, enter, |enter| {
|
clock::with_default(clock, || {
|
||||||
// timer::with_default(&timer_handle, enter, |enter| {
|
timer::with_default(&timer_handle, || {
|
||||||
// The TaskExecutor is a fake executor that looks into the
|
// The TaskExecutor is a fake executor that looks into the
|
||||||
// current single-threaded executor when used. This is a trick,
|
// current single-threaded executor when used. This is a trick,
|
||||||
// because we need two mutable references to the executor (one
|
// because we need two mutable references to the executor (one
|
||||||
// to run the provided future, another to install as the default
|
// to run the provided future, another to install as the default
|
||||||
// one). We use the fake one here as the default one.
|
// one). We use the fake one here as the default one.
|
||||||
let mut default_executor = current_thread::TaskExecutor::current();
|
let mut default_executor = current_thread::TaskExecutor::current();
|
||||||
tokio_executor::with_default(&mut default_executor, enter, |enter| {
|
tokio_executor::with_default(&mut default_executor, enter, |enter| {
|
||||||
let mut executor = executor.enter(enter);
|
let mut executor = executor.enter(enter);
|
||||||
f(&mut executor)
|
f(&mut executor)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
// })
|
|
||||||
//})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,12 +81,3 @@
|
|||||||
//! [`DelayQueue`]: struct.DelayQueue.html
|
//! [`DelayQueue`]: struct.DelayQueue.html
|
||||||
|
|
||||||
pub use tokio_timer::{delay_queue, timeout, Delay, DelayQueue, Error, Interval, Timeout};
|
pub use tokio_timer::{delay_queue, timeout, Delay, DelayQueue, Error, Interval, Timeout};
|
||||||
|
|
||||||
#[deprecated(since = "0.1.8", note = "use Timeout instead")]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type Deadline<T> = ::tokio_timer::Deadline<T>;
|
|
||||||
#[deprecated(since = "0.1.8", note = "use Timeout instead")]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type DeadlineError<T> = ::tokio_timer::DeadlineError<T>;
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
use futures::Future;
|
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
use tokio_timer::Deadline;
|
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
#[cfg(feature = "timer")]
|
||||||
use tokio_timer::Timeout;
|
use tokio_timer::Timeout;
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
#[cfg(feature = "timer")]
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
/// An extension trait for `Future` that provides a variety of convenient
|
/// An extension trait for `Future` that provides a variety of convenient
|
||||||
/// combinator functions.
|
/// combinator functions.
|
||||||
@@ -62,31 +58,6 @@ pub trait FutureExt: Future {
|
|||||||
{
|
{
|
||||||
Timeout::new(self, timeout)
|
Timeout::new(self, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
|
||||||
#[deprecated(since = "0.1.8", note = "use `timeout` instead")]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn deadline(self, deadline: Instant) -> Deadline<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Deadline::new(self, deadline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized> FutureExt for T where T: Future {}
|
impl<T: ?Sized> FutureExt for T where T: Future {}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::prelude::future;
|
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
|
||||||
#[test]
|
|
||||||
fn timeout_polls_at_least_once() {
|
|
||||||
let base_future = future::result::<(), ()>(Ok(()));
|
|
||||||
let timeouted_future = base_future.timeout(Duration::new(0, 0));
|
|
||||||
assert!(timeouted_future.wait().is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
//! [`FutureExt`]: trait.FutureExt.html
|
//! [`FutureExt`]: trait.FutureExt.html
|
||||||
//! [`StreamExt`]: trait.StreamExt.html
|
//! [`StreamExt`]: trait.StreamExt.html
|
||||||
|
|
||||||
mod enumerate;
|
// mod enumerate;
|
||||||
mod future;
|
mod future;
|
||||||
mod stream;
|
// mod stream;
|
||||||
|
|
||||||
pub use self::future::FutureExt;
|
pub use self::future::FutureExt;
|
||||||
pub use self::stream::StreamExt;
|
// pub use self::stream::StreamExt;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
pub use crate::util::enumerate::Enumerate;
|
pub use crate::util::enumerate::Enumerate;
|
||||||
|
|
||||||
use futures::Stream;
|
|
||||||
#[cfg(feature = "timer")]
|
#[cfg(feature = "timer")]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[cfg(feature = "timer")]
|
#[cfg(feature = "timer")]
|
||||||
use tokio_timer::{throttle::Throttle, Timeout};
|
use tokio_timer::{throttle::Throttle, Timeout};
|
||||||
|
|
||||||
|
|||||||
+57
-74
@@ -1,112 +1,95 @@
|
|||||||
#![cfg(feature = "broken")]
|
|
||||||
#![deny(warnings, rust_2018_idioms)]
|
#![deny(warnings, rust_2018_idioms)]
|
||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
use env_logger;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use tokio;
|
use tokio;
|
||||||
use tokio::prelude::*;
|
use tokio::prelude::*;
|
||||||
|
// use tokio::sync::mpsc;
|
||||||
use tokio::timer::*;
|
use tokio::timer::*;
|
||||||
|
|
||||||
#[test]
|
use std::sync::mpsc;
|
||||||
fn timer_with_runtime() {
|
use std::time::{Duration, Instant};
|
||||||
let _ = env_logger::try_init();
|
|
||||||
|
|
||||||
let when = Instant::now() + Duration::from_millis(100);
|
#[test]
|
||||||
|
fn timer_with_threaded_runtime() {
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().unwrap();
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
tokio::run({
|
rt.spawn(async move {
|
||||||
Delay::new(when)
|
let when = Instant::now() + Duration::from_millis(100);
|
||||||
.map_err(|e| panic!("unexpected error; err={:?}", e))
|
|
||||||
.and_then(move |_| {
|
Delay::new(when).await;
|
||||||
assert!(Instant::now() >= when);
|
assert!(Instant::now() >= when);
|
||||||
tx.send(()).unwrap();
|
|
||||||
Ok(())
|
tx.send(()).unwrap();
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rt.run().unwrap();
|
||||||
rx.recv().unwrap();
|
rx.recv().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn starving() {
|
fn timer_with_current_thread_runtime() {
|
||||||
use futures::{task, Async, Poll};
|
use tokio::runtime::current_thread::Runtime;
|
||||||
|
|
||||||
let _ = env_logger::try_init();
|
let mut rt = Runtime::new().unwrap();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
rt.spawn(async move {
|
||||||
|
let when = Instant::now() + Duration::from_millis(100);
|
||||||
|
|
||||||
|
Delay::new(when).await;
|
||||||
|
assert!(Instant::now() >= when);
|
||||||
|
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.run().unwrap();
|
||||||
|
rx.recv().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn starving() {
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
struct Starve(Delay, u64);
|
struct Starve(Delay, u64);
|
||||||
|
|
||||||
impl Future for Starve {
|
impl Future for Starve {
|
||||||
type Item = u64;
|
type Output = u64;
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, ()> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u64> {
|
||||||
if self.0.poll().unwrap().is_ready() {
|
if Pin::new(&mut self.0).poll(cx).is_ready() {
|
||||||
return Ok(self.1.into());
|
return Poll::Ready(self.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.1 += 1;
|
self.1 += 1;
|
||||||
|
|
||||||
task::current().notify();
|
cx.waker().wake_by_ref();
|
||||||
|
|
||||||
Ok(Async::NotReady)
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let when = Instant::now() + Duration::from_millis(20);
|
let when = Instant::now() + Duration::from_millis(20);
|
||||||
let starve = Starve(Delay::new(when), 0);
|
let starve = Starve(Delay::new(when), 0);
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
starve.await;
|
||||||
|
assert!(Instant::now() >= when);
|
||||||
tokio::run({
|
|
||||||
starve.and_then(move |_ticks| {
|
|
||||||
assert!(Instant::now() >= when);
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
rx.recv().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn deadline() {
|
async fn timeout() {
|
||||||
use futures::future;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
let _ = env_logger::try_init();
|
let (_tx, rx) = oneshot::channel::<()>();
|
||||||
|
|
||||||
let when = Instant::now() + Duration::from_millis(20);
|
let now = Instant::now();
|
||||||
let (tx, rx) = mpsc::channel();
|
let dur = Duration::from_millis(20);
|
||||||
|
|
||||||
#[allow(deprecated)]
|
let res = rx.timeout(dur).await;
|
||||||
tokio::run({
|
assert!(res.is_err());
|
||||||
future::empty::<(), ()>().deadline(when).then(move |res| {
|
assert!(Instant::now() >= now + dur);
|
||||||
assert!(res.is_err());
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
rx.recv().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn timeout() {
|
|
||||||
use futures::future;
|
|
||||||
|
|
||||||
let _ = env_logger::try_init();
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
tokio::run({
|
|
||||||
future::empty::<(), ()>()
|
|
||||||
.timeout(Duration::from_millis(20))
|
|
||||||
.then(move |res| {
|
|
||||||
assert!(res.is_err());
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
rx.recv().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user