Smithay / calloop

A callback-based Event Loop

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is there a way for a source to remove itself within its own process_events()?

detly opened this issue · comments

I have recently found the need to have event sources remove themselves from the loop after they complete a task. Mostly this is with timers and subprocesses (via futures), but it can be demonstrated quite simply with an example. Here's an event source that starts a timer when it's added to the loop, and when the timer expires, prints a message and never runs again.

use calloop::{EventLoop, EventSource, Poll, Readiness, Token};

struct ThingOne {
    timer: calloop::timer::Timer<()>,
    done: bool,
}

impl ThingOne {
    fn new() -> Self {
        Self {
            timer: calloop::timer::Timer::new().unwrap(),
            done: false,
        }
    }
}

impl EventSource for ThingOne {
    type Event = ();
    type Metadata = ();
    type Ret = ();

    fn process_events<F>(&mut self, readiness: Readiness, token: Token, _: F) -> std::io::Result<()>
    where
        F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
    {
        if !self.done {
            let done = &mut self.done;
            self.timer.process_events(readiness, token, |_, _| {
                println!("Done!");
                *done = true;
            })?;
        }

        Ok(())
    }

    fn register(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> {
        let hdl = self.timer.handle();
        hdl.add_timeout(std::time::Duration::from_secs(1), ());
        self.timer.register(poll, token)
    }

    fn reregister(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> {
        let hdl = self.timer.handle();
        hdl.add_timeout(std::time::Duration::from_secs(1), ());
        self.timer.reregister(poll, token)
    }

    fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> {
        self.timer.unregister(poll)
    }
}

fn main() {
    let mut event_loop = EventLoop::try_new().unwrap();

    let handle = event_loop.handle();

    let _loop_token = handle.insert_source(ThingOne::new(), |_, _, _| {}).unwrap();

    event_loop
        .run(None, &mut (), |_| {})
        .expect("Error during event loop!");
}

It would be good if I could actually have ThingOne remove itself from the loop when it's done. In this case, it's not a big deal. But when you're dealing with open file descriptors and sockets, it's good to have cleanup. But I can't figure out how.

  • I can't remove it from inside the process_events() method, because I don't have the registration token or the poll argument for self.unregister().
  • I can't remove it via main(), because by the time I have the registration token, the loop has taken ownership of the source.

Is there a way to do this that I'm missing?

Generally the intended approach was that sources are to be removed externally, I did not really consider self-destructing sources, though that might be added relatively easily if there is a motivated need for it.

The canonical way to disable a source and have its Drop impl run is LoopHandle::kill. It can even be invoked via the source callback by passing the LoopHandle and the RegistrationToken through the dispatch_data.

I guess the return type of process_events could be changed to allow an event source to request to be reregistered/unregistered/dropped, that'd probably be the simplest.

t can even be invoked via the source callback by passing the LoopHandle and the RegistrationToken through the dispatch_data.

Can you elaborate on this (specifically, what is dispatch_data?) I considered using register_dispatcher() instead of insert_source(), but in my real project I ran into lifetime/ownership issues extremely quickly.

I really like your "return value from process_events()" approach, it's very clever and ergonomic!