-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tokio::time::pause should not automatically advance time when there are other things to do #3108
Comments
I do not believe this is correct. Without advancing time implicitly, there are many cases that just aren't possible. You made the claim "not very useful" without providing any support. Empirically, this statement is not true. An argument could be made that it should not be called Without this behavior, there is no real way to process all queued tasks before advancing the time manually. |
My issue is that I want to spawn a task that sleeps before doing something. For example this is a problem that pretty much prevents me from testing https://gitlab.com/KonradBorowski/psdevbot-rust/-/blob/232e13bbf0fb17abb1a2af35e27723a112395060/src/unbounded.rs. Specifically, the issue is as follows, I'm trying to write a test that makes sure throttled channel doesn't delay on the first message. Simple enough. First, let's make a minimal struct that doesn't depend on specific structures from the code I'm writing, something that could be put into use futures::channel::mpsc;
use futures::channel::mpsc::SendError;
use futures::{Sink, SinkExt, StreamExt};
use tokio::time;
pub struct UnboundedSender {
sender: mpsc::UnboundedSender<i32>,
}
impl UnboundedSender {
pub fn new(mut showdown_sender: impl Sink<i32> + Send + Unpin + 'static) -> Self {
let (tx, mut rx) = mpsc::unbounded();
tokio::spawn(async move {
while let Some(message) = rx.next().await {
if showdown_sender.send(message).await.is_err() {
return;
}
time::delay_for(time::Duration::from_millis(700)).await;
}
});
Self { sender: tx }
}
pub async fn send(&self, message: i32) -> Result<(), SendError> {
(&self.sender).send(message).await
}
} Then we can write a test. #[tokio::test]
async fn sender_does_not_delay_on_first_message() -> Result<(), Box<dyn std::error::Error>> {
use tokio::time::{self, Instant};
time::pause();
let (tx, mut rx) = mpsc::unbounded();
let sender = UnboundedSender::new(tx);
let now = Instant::now();
sender.send(42).await?;
assert_eq!(rx.next().await, Some(42));
assert_eq!(now, Instant::now());
Ok(())
} This causes an error:
If we go back to tokio 0.2.0 with (by the way, yes, the change of behaviour in tokio 0.2.y was a breaking change, but at this point I don't think it should reverted) Okay, but what if I want to check whether an event did not occur. Well, that could be done too. #[tokio::test]
async fn sender_does_not_delay_on_first_message() -> Result<(), Box<dyn std::error::Error>> {
use tokio::task;
use tokio::time::{self, Duration, Instant};
time::pause();
let (tx, mut rx) = mpsc::unbounded();
let sender = UnboundedSender::new(tx);
let now = Instant::now();
// First element gets send instantly
sender.send(42).await?;
task::yield_now().await;
assert_eq!(rx.try_next()?, Some(42));
assert_eq!(now, Instant::now());
// Second element will require waiting
sender.send(24).await?;
task::yield_now().await;
assert!(rx.try_next().is_err());
// Actually waiting
time::advance(Duration::from_millis(701)).await;
task::yield_now().await;
assert_eq!(rx.try_next()?, Some(24));
Ok(())
} How do I do this test with current Arguably the current implementation may have its uses (I don't know what they may be, didn't find anything on GitHub), but it's not pausing the timer, and should be called something else if it should be still available. As for deadlocking, I would say that deadlocking in tests is somewhat more acceptable ( |
As far as I can tell, both cases are bugs. Time should not advance unless the system is idle. It is caused because the root future is not spawned onto the runtime. It can be fixed by not advancing time if the waker was notified. Feel free to submit a PR to fix. |
Reclassifying this as a bug and removing the 1.0 label as it is not a blocker. |
Thanks, for me spawning a task in a test is a valid workaround. |
This seems to no longer be a bug: |
It's still here, it's just that spawning a task work-arounds the bug. It's a valid workaround for me as I said before. |
@xfix If I run your original test as a test on the current fork, the test passes as well. Also I looked at the timer implementation and I don't see how spawning a task should somehow change the behaviour of |
Try running it as a test. #[tokio::test]
async fn test() {
sender_does_not_delay_on_first_message().await
} |
I'll take a look at this one. |
Provided tests cases pass with 1.x. |
Thanks for trying the test cases again @gwik |
Use basic_scheduler as all test should succeed without any thread. Add entry/exit functions that can contain the same code for all tests and log. Note: Ideally we would be able to use `tokio::time::pause();` in test_step_start(). However it does not behave as expected and time runs away with it. Somewhat related to tokio-rs/tokio#3108 but work around does not seem to affect the test, so it is possible the test would need to be written differently. Test: cargo test cargo clippy
Is your feature request related to a problem? Please describe.
Currently
tokio::time::pause
is not very useful due to automatically advancing the time even when there are other things to do. This makes it mostly useless for unit testing, something it was designed for, unless a test doesn't spawn new tasks that wait. For example, the following program:Will show a time difference of 10 seconds due to spawned tasks advancing a timer by 10 seconds even when it's not necessary.
Curiously, this behaviour wasn't always here, before #2059 was merged the behaviour was much saner in that the timer did not advance until manually advanced.
Describe the solution you'd like
Change the behaviour of
tokio::time::pause
to advance timer only when necessary.The text was updated successfully, but these errors were encountered: