Repository: esp-rs/espmonitor (language: rust)

Issue:
espmonitor spins in a tight loop if USB device unplugged on Linux
When running espmonitor on `/dev/tty{USB,ACM}*` it will go into a tight loop consuming 100% CPU if you unplug the device.

This happens because after the device is unplugged, `dev.read` will return immediately with `Ok(0)` to reflect that EOF has been reached, so the code never blocks for the 200ms serial timeout. Polling for the keyboard input has a zero duration, so that also doesn't block, and it just goes around the loop like that forever.

Plugging the device back in also doesn't fix it since the device would have to be reopened, and in fact since espmonitor still has the character device open the kernel will just give the device a new device number so even reopening it usually won't work as the path to the device has probably changed.

It looks like on *nix the read will only return `Ok(0)` when the device has actually gone away, as if there's just no data it will just return `Err(TimedOut)` after 200ms. So.. just exiting when this happens should be okay, but I don't know if there's any other case (on Windows, or some other edge case I'm not thinking of) where `Ok(0)` might be returned during normal operation...


Discussion / hints:
I can't test Windows, unfortunately, but I think having us exit on `Ok(0)` is probably ok to do.  I do know some people use this on Windows, so they'll tell us quickly if it breaks something.
(Another option would be to not exit, but instead keep trying to repoen the device.  Not sure the added code complexity is worth the IMO very minor increase in usability, though.  And I expect some people would consider auto-exit on disconnect to be the most desired behavior anyway... and I might be one of those people.)

Fix patch:
diff --git a/espmonitor/src/lib.rs b/espmonitor/src/lib.rs
index c147f70..9e9d903 100644
--- a/espmonitor/src/lib.rs
+++ b/espmonitor/src/lib.rs
@@ -135,7 +135,10 @@ fn run_child(mut args: AppArgs) -> Result<(), Box<dyn std::error::Error>> {
     loop {
         match dev.read(&mut buf) {
             Ok(bytes) if bytes > 0 => handle_serial(&mut serial_state, &buf[0..bytes])?,
-            Ok(_) => (),
+            Ok(_) => if dev.read_dsr().is_err() {
+                rprintln!("Device disconnected; exiting");
+                break Ok(());
+            },
             Err(err) if err.kind() == ErrorKind::TimedOut => (),
             Err(err) if err.kind() == ErrorKind::WouldBlock => (),
             Err(err) => break Err(err.into()),


Test patch: