Describe the bugcrossterm::event::poll(Duration::ZERO) can, in some situation, return false despite currently having one in its buffer.
This can lead some setups to be out-of-sync due to the subsequent crossterm::event::read returning an older event than the one meant by crossterm::event::poll.
To Reproduce
Here is a minimum example I put together, using a similar structure to the event-poll-read example.
The context of this setup is that I was trying to use crossterm to read events from stdin (using its read and poll functions), but being able to handle signal interruptions myself during the polling through EINTR.
Due to both read and poll functions catching and handling EINTR errors themselves, I used mio myself to poll standard input.
[dependencies]
anyhow = "1.0.75"
crossterm = { version = "0.27.0", features = ["use-dev-tty"] }
mio = "0.8.9"
use std::{io, time::Duration};
use crossterm::{
cursor::position,
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode},
};
use mio::unix::SourceFd;
use mio::{Events, Interest, Poll, Token};
const HELP: &str = r#"Blocking poll() & non-blocking read()
- Keyboard, mouse and terminal resize events enabled
- Hit "c" to print current cursor position
- Use Esc to quit
"#;
fn print_events() -> anyhow::Result<()> {
let mut events = Events::with_capacity(1);
let mut mio_poll = Poll::new()?;
// Register a READABLE interest on standard input
mio_poll.registry()
.register(&mut SourceFd(&0), Token(0), Interest::READABLE)?;
loop {
// Check if we have a buffered event ready?
if !crossterm::event::poll(Duration::ZERO)? {
// We don't have an event ready, so start polling stdin ourselves
events.clear();
match mio_poll.poll(&mut events, None) {
Ok(_) => {
// an event might be ready on stdin
}
Err(err) if err.kind() == io::ErrorKind::Interrupted => {
// we got signaled, let's handle it here
println!("Signal received !\r");
// then we go back to polling
continue;
}
Err(err) => {
// we got an `mio` error
return Err(err.into());
}
}
}
// It's guaranteed that read() won't block if `poll` returns `Ok(true)`
let event = crossterm::event::read()?;
println!("Event::{:?}\r", event);
if event == Event::Key(KeyCode::Char('c').into()) {
println!("Cursor position: {:?}\r", position());
}
if event == Event::Key(KeyCode::Esc.into()) {
break;
}
}
Ok(())
}
fn main() -> anyhow::Result<()> {
println!("{}", HELP);
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnableMouseCapture)?;
if let Err(e) = print_events() {
println!("Error: {:?}\r", e);
}
execute!(stdout, DisableMouseCapture)?;
disable_raw_mode()?;
Ok(())
}
Running this program, it should behave just like event-poll-read initially, but if multiple events are submitted at once (by pasting some text, for example, since bracketed paste is not enabled), it starts breaking and older keypresses are processed when newer keypresses are done.
This is due to poll(Duration::ZERO) returning false when some events are available, causing read to not be called until an even newer event comes up, and that read call will returns this older event instead of the new one (which will be waiting in the buffer).
When the use-dev-tty feature is disabled, this setup works as expected, without having this issue.
After having read some of crossterm's code, since stdin is not redirected in this example,use-dev-tty seems to be using file descriptor 0 directly instead of /dev/tty since they are equivalent (isatty(0) is true in this case), so my mio polling and crossterm's polling should have perfectly identical results, and should behave identically than without use-dev-tty.
Expected behavior
I expected this setup to work just like the event-poll-read, except with the added benefit of handling EINTR myself.
I expected no behaviour change compared to without use-dev-tty enabled.
OS & Terminal/Console
I've reproduced this bug on two different machines.
Describe the bug
crossterm::event::poll(Duration::ZERO)
can, in some situation, returnfalse
despite currently having one in its buffer.This can lead some setups to be out-of-sync due to the subsequent
crossterm::event::read
returning an older event than the one meant bycrossterm::event::poll
.To Reproduce Here is a minimum example I put together, using a similar structure to the
event-poll-read
example.The context of this setup is that I was trying to use
crossterm
to read events from stdin (using itsread
andpoll
functions), but being able to handle signal interruptions myself during the polling through EINTR.Due to both
read
andpoll
functions catching and handlingEINTR
errors themselves, I usedmio
myself to poll standard input.Running this program, it should behave just like
event-poll-read
initially, but if multiple events are submitted at once (by pasting some text, for example, since bracketed paste is not enabled), it starts breaking and older keypresses are processed when newer keypresses are done.This is due to
poll(Duration::ZERO)
returningfalse
when some events are available, causingread
to not be called until an even newer event comes up, and thatread
call will returns this older event instead of the new one (which will be waiting in the buffer).When the
use-dev-tty
feature is disabled, this setup works as expected, without having this issue.After having read some of crossterm's code, since stdin is not redirected in this example,
use-dev-tty
seems to be using file descriptor 0 directly instead of/dev/tty
since they are equivalent (isatty(0)
is true in this case), so mymio
polling and crossterm's polling should have perfectly identical results, and should behave identically than withoutuse-dev-tty
.Expected behavior I expected this setup to work just like the
event-poll-read
, except with the added benefit of handlingEINTR
myself.I expected no behaviour change compared to without
use-dev-tty
enabled.OS & Terminal/Console I've reproduced this bug on two different machines.
First machine:
Second machine: