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

Dropped timer causes infinite wakeups? #113

Closed
redbaron opened this issue May 15, 2020 · 6 comments
Closed

Dropped timer causes infinite wakeups? #113

redbaron opened this issue May 15, 2020 · 6 comments

Comments

@redbaron
Copy link

I'd expect this incorrect program to never wake up, never return and underneath stream::poll_fn to be polled once.

When running with smol 0.1.5 it triggers tight infinite polling loop.

use smol::{self, Timer};
use std::task::Poll;

use std::time::Duration;

use futures::future::FutureExt;
use futures::stream::{self, StreamExt};

pub fn main() {
    smol::run(async {
        let mut sd = stream::poll_fn(|cx| {
            println!("I am being polled");
            Timer::after(Duration::from_secs(1)).poll_unpin(cx);
            Poll::<Option<()>>::Pending
        });
        sd.next().await;
    })
}

For comparison, here is Playground with tokio: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=489752594073ad372fee0a65f0aeb7ad

@ComputerDruid
Copy link

Slight minimization, it's not unique to Stream:

use smol::{self, Timer};
use std::task::Poll;

use std::time::Duration;

use futures::future::{FutureExt, self};

pub fn main() {
    smol::run(
        future::poll_fn(|cx| {
            println!("I am being polled");
            Timer::after(Duration::from_secs(1)).poll_unpin(cx);
            Poll::<Option<()>>::Pending
        })
    );
}

But I think it's inaccurate to say this triggers an infinite polling loop, just a single spurious poll. This example code creates and drops the timer every time it's polled, which is causing the tight loop.

ComputerDruid added a commit to ComputerDruid/smol that referenced this issue May 17, 2020
When a timer is inserted into the reactor, a message is be sent to the
timer_event object so that someone waiting in the reactor gets woken up
early so that they can consider if the new timer is will trigger before
they would have originally woken up.

However, if no one is waiting in the reactor, this step is unnecessary.
In a case with only one executor thread, this is always true, since
timers aren't added while the reactor lock is held. Furthermore, if you
notify the reactor while no one is holding the lock, the next reactor
wait will return immediately, which will make you go through the event
loop again unnecessarily.

Fixes smol-rs#113
ComputerDruid added a commit to ComputerDruid/smol that referenced this issue May 17, 2020
When a timer is inserted into the reactor, a message is sent to the
timer_event object so that someone waiting in the reactor gets woken up
early so that they can consider if the new timer is will trigger before
they would have originally woken up.

However, if no one is waiting in the reactor, this step is unnecessary.
In a case with only one executor thread, this is always true, since
timers aren't added while the reactor lock is held. Furthermore, if you
notify the reactor while no one is holding the lock, the next reactor
wait will return immediately, which will make you go through the event
loop again unnecessarily.

Fixes smol-rs#113
@ComputerDruid
Copy link

I think there's 2 "bugs" this reveals in smol:

  • If a new timer is added to the reactor while no one is waiting on the reactor, the next reactor wait will terminate immediately because of the timer_event notification, even though the timer op will have already been handled. This forces the executor to go through the loop again unnecessarily. I wrote Skip notifying the reactor for added timers unnecessarily #116 which I believe addresses that.

  • Whenever the event loop runs, smol polls the main future, even if the waker associated with the main future has not been woken. That means that events meant for spawned tasks (or in this bug's case, meant for the management of the timer queue) will also cause the main future to be re-polled, which seems undesirable.

I believe fixing either of these things will make this "bug" disappear. That said, futures getting polled spuriously is fairly normal when multiple futures are combined together with select or join, so I'm not sure that this bug itself represents a real issue. It was fun to dig in to smol to try to figure out why this was happening, regardless.

@ComputerDruid
Copy link

Oh, and I found using a variant of the repro case that exits after 20 polls more pleasant to work with, in case that's useful:

use smol::{self, Timer};
use std::task::Poll;

use std::time::Duration;

use futures::future::{self, FutureExt};

pub fn main() {
    let mut poll_count: i32 = 0;
    smol::run(future::poll_fn(|cx| {
        poll_count += 1;
        if poll_count == 20 {
            return Poll::Ready(());
        }
        eprintln!("polled {} times", poll_count);
        Timer::after(Duration::from_secs(1)).poll_unpin(cx);
        Poll::Pending
    }));
}

@ghost
Copy link

ghost commented May 17, 2020

Thank you for the deep analysis! This will be fixed after I extract the Async+Timer part of smol into a separate crate. We just need to do a bit more diligent around epoll/kqueue/wepoll and check for spurious wakeups...

@ComputerDruid
Copy link

I think this is actually fixed by 55f360b

At the very least, I can no longer reproduce it. I think the added timer_event.clear() means that the next reactor wait doesn't immediately wake up because of the new timer.

The other bug I mentioned (main future gets polled regardless of being woken) still exists, but it might be worthwhile to split that out into a separate bug, so I opened #166

@redbaron
Copy link
Author

Great, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants