Search code examples
rustmio

Why is Mio's poll triggered twice for user-generated events?


The following code generates a "user event" to be returned by poll:

extern crate mio;

use mio::event::Evented;
use mio::{Events, Poll, PollOpt, Ready, Registration, Token};
use std::thread::{sleep, spawn, JoinHandle};
use std::time::{Duration, Instant};

#[derive(Debug)]
struct Output(u32, Duration);

pub struct MioThread {
    registration: Registration,
    handle: JoinHandle<Output>,
}

impl MioThread {
    pub fn new(i: u32) -> MioThread {
        let now = Instant::now();

        let (registration, set_readiness) = Registration::new2();

        let handle = spawn(move || {
            sleep(Duration::from_millis((1000 - (100 * i)) as u64));

            set_readiness.set_readiness(Ready::readable()).unwrap();

            Output(i, now.elapsed())
        });

        MioThread {
            registration: registration,
            handle: handle,
        }
    }

    // manage the thread result
    fn eval_result(self) {
        let out = self.handle.join();
        println!("do whathever you want with: {:?}", out.unwrap());
    }
}

fn main() {
    let poll = Poll::new().unwrap();

    let mut events = Events::with_capacity(16);

    let mut tasks = Vec::new();
    for i in 0..5 {
        let mio_thread = MioThread::new(i);

        mio_thread
            .registration
            .register(&poll, Token(i as usize), Ready::readable(), PollOpt::edge())
            .unwrap();

        tasks.push(Some(mio_thread));
    }

    loop {
        let num_events = poll.poll(&mut events, None).unwrap();
        println!("poll fired: {} events", num_events);
        for event in &events {
            if event.readiness().is_readable() {
                let Token(thread_id) = event.token();

                if let Some(t) = tasks.remove(thread_id) {
                    t.eval_result();
                }

            }
        }
    }
}

The output is:

poll fired: 1 events
do whathever you want with: Output(4, Duration { secs: 0, nanos: 600967623 })
poll fired: 0 events
poll fired: 1 events
do whathever you want with: Output(3, Duration { secs: 0, nanos: 701035026 })
poll fired: 0 events
poll fired: 1 events
do whathever you want with: Output(2, Duration { secs: 0, nanos: 801089370 })
poll fired: 0 events
poll fired: 1 events
do whathever you want with: Output(1, Duration { secs: 0, nanos: 900890190 })
poll fired: 0 events
poll fired: 1 events
do whathever you want with: Output(0, Duration { secs: 1, nanos: 600076 })
poll fired: 0 events

I've opened an issue on the Mio repository.


Solution

  • As stated in the comments and confirmed here:

    dropping a Registration might wake up the loop (which it indeed does, the same as a registration) without actually triggering an event

    This is obviously not an issue in most cases, just a behavior that I did not expect after reading the docs:

    fn poll(&self, events: &mut Events, timeout: Option) -> Result[−] Wait for readiness events

    Blocks the current thread and waits for readiness events for any of the Evented handles that have been registered with this Poll instance. The function will block until either at least one readiness event has been received or timeout has elapsed. A timeout of None means that poll will block until a readiness event has been received.

    The current thread is indeed awaken also when dropping a Registration.