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 forself.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!