Skip to content
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

Allow disabling time auto-advance behavior in tests #6113

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

l4l
Copy link
Contributor

@l4l l4l commented Oct 26, 2023

Basically a rebased version of @Dentosal patch from #5200 with slight naming changes.

Accepts a flag to explicitly disable auto-advancing behavior on paused time.

Co-authored-by: Hannes Karppila <hannes.karppila@gmail.com>
@Darksonn Darksonn added A-tokio Area: The main tokio crate M-time Module: tokio/time labels Oct 27, 2023
@carllerche
Copy link
Member

cc @mcches

@carllerche
Copy link
Member

Thanks for the PR. I read the discussion around the original issue, and I'm not sure I fully understand the motivation for this option.

Could you explain the use case a bit more? What is the problem caused by auto-advancing mocked time, and why not just disable time mocking entirely?

Copy link
Member

@carllerche carllerche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider including mock_time in the runtime-level configuration option, but I don't have a super strong opinion there.

///
/// [`sleep`]: fn@crate::time::sleep
/// [`advance`]: crate::time::advance
pub fn set_time_auto_advance(enable: bool) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the fn is in the time module, including time in the fn name is redundant: time::set_time_auto_advance(true).

Also, does this function need to exist (I couldn't say because I don't fully understand the use case yet).

@l4l
Copy link
Contributor Author

l4l commented Oct 27, 2023

Could you explain the use case a bit more? What is the problem caused by auto-advancing mocked time, and why not just disable time mocking entirely?

Although the behavior is quite reasonable this doesn't seem to be obvious to me. Otherwise I guess there won't be any notice in the pause fn. I thought as #4522 is still being open then the feature is still needed, though it seems #5115 actually solves the author's issue.

So apparently there's lack of sufficient motivation. For now I could think of two cases:

  1. The behavior is not obvious, one may want to disable it. It sill could be done with something like this though, but it's awkward and exploit internal (or just undocumented?) implementation of Don't auto-advance time when a spawn_blocking task is running. #5115:
use tokio::{task::spawn_blocking, time::sleep};

let _h = spawn_blocking(|| loop {}); // increments inhibit counter
sleep(Duration::from_secs(1)).await; // now it won't return
  1. Idle inhibit doesn't interoperate with other threads & timers, consider the following use-case:
use std::time::Duration;

use futures_intrusive::timer::{StdClock, Timer, TimerService};
use once_cell::sync::Lazy;

#[tokio::test]
async fn test_a() {
    tokio::time::pause();

    static CLOCK: Lazy<StdClock> = Lazy::new(|| StdClock::new());
    static TIME_SERVICE: Lazy<TimerService> = Lazy::new(|| TimerService::new(&*CLOCK));

    // Spawn thread that checks the timer for wake constantly.
    // Note that we do have synchronous control when to perform wakes here.
    std::thread::spawn(|| loop {
        TIME_SERVICE.check_expirations();
        std::thread::sleep(Duration::from_millis(10));
    });

    // Create first timer with shorter duration. This expected to resolve first.
    let timer1 = TIME_SERVICE.delay(Duration::from_millis(500));
    // And the second one from tokio.
    let timer2 = tokio::time::sleep(Duration::from_secs(1));

    // With disabled auto-advance, here we may adjust time as we want.

    tokio::select! {
        _ = timer1 => {},
        _ = timer2 => panic!("second timer goes earlier"),
    }
}

For me this tests always fails contrary to the code. So it's not about mocking itself but rather about expected amount of control.

I would consider including mock_time in the runtime-level configuration option, but I don't have a super strong opinion there.

Neither do I, but I guess explicit usage time::pause/auto_advance/start_paused implies mocking? Then we have something redundant like this:

time::pause() & co mock_time
called set mock_time explicit
called unset time::pause should panic?
uncalled set time::pause calls implicitly?
uncalled unset not mocked as expected

@vthib
Copy link

vthib commented Oct 17, 2024

I also have tests that cannot work properly due to the auto-advance feature. I've fixed it with the spawn_blocking hack, but I would very much love to have a proper fix to be able to disable this feature. What is the status on this MR?

/// .build()
/// .unwrap();
/// ```
pub fn time_auto_advance(&mut self, time_auto_advance: bool) -> &mut Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this panic if using the multi-thread runtime flavor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think so, will add then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate M-time Module: tokio/time
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants