Why mio::Poll::poll() only receives the *very first* event?

In this code adapted straight from the official mio example code:

use std::error::Error;
use std::io::Read;
use std::time::Duration;

use mio::net::{TcpListener};
use mio::{Events, Interest, Poll, Token};

const SERVER: Token = Token(0);

fn main() -> Result<(), Box<dyn Error>> {
    let addr = "127.0.0.1:8080".parse()?;
    let mut server = TcpListener::bind(addr)?;

    let mut events = Events::with_capacity(128);
    let mut poll = Poll::new()?;
    poll.registry().register(&mut server, SERVER, Interest::READABLE)?;

    loop {
        match poll.poll(&mut events, Some(Duration::from_secs(10))) {
            Ok(()) => {
                if !events.is_empty() {
                    println!("event(s) have been received!");
                    for event in events.iter() {
                        match event.token() {
                            SERVER => {
                                println!("server event!");
                                let connection = server.accept();
                                println!("--> accepted!");
                                if let Ok((mut stream, _addr)) = connection {
                                    let mut buffer = [0; 8192];
                                    if let Ok(len) = stream.read(&mut buffer) {
                                        println!("len = {}", len);
                                    }
                                }
                                println!("--> dropped!");
                            }
                            _ => unreachable!(),
                        }
                    }    
                } else {
                    println!("timeout!");
                }
            },
            Err(err) => println!("Poll error: {}", err),
        }
    }
}

Why do I only receive the event for the very first incoming connection, but then never any event is processed again, even though I try to connect to the server continuously? :slightly_frowning_face:

Note: The code is definitely not blocked, as the poll regularly exists with a timeout every 10 secs...

(If I terminate the process and re-start it, then again, I can receive one connection, but no more)

Thanks for any help!


Output:

C:\Source\mio_test>cargo run
   Compiling mio_test v0.1.0 (C:\Source\mio_test)
    Finished dev [unoptimized + debuginfo] target(s) in 1.42s
     Running `target\debug\mio_test.exe`
timeout!
timeout!
event(s) have been received!
server event!
--> accepted!
--> dropped!
timeout!
timeout!
timeout!
timeout!
timeout!
...

Any subsequent attempt (except very first!) to connect to the server (via cURL) just hangs :confounded:

Mio is edge triggered. You only get a notification when readiness changes from NOT READY to READY, and it only changes from READY to NOT READY when you get a WouldBlock error. Therefore, you must keep calling accept until it fails with a WouldBlock error.

2 Likes

Thanks !!!

I was about to answer my own question, after I found this remark in the docs:

Draining readiness
When using edge-triggered mode, once a readiness event is received, the corresponding operation must be performed repeatedly until it returns WouldBlock. Unless this is done, there is no guarantee that another readiness event will be delivered, even if further data is received for the Evented handle.

A bit surprising that they left out this important "detail" in all examples :thinking:

Also, since they mention "edge-triggered mode": Is there an alternative mode that does not require the operation to be repeated until WouldBlock error is encountered?

I don't know whether mio supports an alternate mode.

Probably not. In general, that pattern of drain until empty + wait until trigger is what eliminates the race condition.