mdlayher/consrv

consrv does not gracefully handle disappearing / re-appearing serial device

stapelberg opened this issue · 1 comments

In my setup, for whichever reason, the USB-to-serial adapter occasionally drops off the USB bus and comes back as ttyUSB1 instead of ttyUSB0:

[776891.671139] cp210x ttyUSB1: usb_serial_generic_read_bulk_callback - urb stopped: -32
[776891.680238] cp210x ttyUSB1: usb_serial_generic_read_bulk_callback - urb stopped: -32
[776891.764819] usb 1-1-port3: disabled by hub (EMI?), re-enabling...
[776891.772885] usb 1-1.3: USB disconnect, device number 6
[776891.779671] cp210x ttyUSB1: failed set request 0x12 status: -19
[776891.786843] cp210x ttyUSB1: failed set request 0x0 status: -19
[776891.794229] cp210x ttyUSB1: cp210x converter now disconnected from ttyUSB1
[776891.802492] cp210x 1-1.3:1.0: device disconnected
[776892.031977] usb 1-1.3: new full-speed USB device number 7 using dwc2
[776892.143732] cp210x 1-1.3:1.0: cp210x converter detected
[776892.151568] usb 1-1.3: cp210x converter now attached to ttyUSB0

I don’t know yet if this correlates to any specific trigger (perhaps rebooting my router?), but it happens frequently enough to be noticeable.

This behavior can be reproduced by physically un-plugging the USB-to-serial adapter and plugging it back in.

On the Go side, the symptom is that Read() returns an io.EOF error.

Currently, consrv connections just hang indefinitely until you send a byte, which triggers a write to the ttyUSB0 device, which triggers a write /dev/ttyUSB1: input/output error that then closes the SSH session.

There are a number of things subtly wrong that result in the silent swallowing of the error. I can send a PR that addresses enough of them to make the SSH session close immediately when un-plugging the adapter.

What’s still left to be done is closing the mux device and underlying serial port, and then re-opening it on the next connection.

What’s still left to be done is closing the mux device and underlying serial port, and then re-opening it on the next connection.

A pragmatic workaround for this is to enable logtostdout (see PR #5) and make consrv log.Fatalf when reading EOF:

diff --git i/cmd/consrv/main.go w/cmd/consrv/main.go
index da918a0..18cef58 100644
--- i/cmd/consrv/main.go
+++ w/cmd/consrv/main.go
@@ -119,6 +119,10 @@ func main() {
 				if err := scanner.Err(); err != nil {
 					ll.Printf("copying serial to stdout: %v", err)
 				}
+				// io.EOF (not considered an error by scanner.Err()) is
+				// unexpected here, as we expect an infinite serial
+				// stream. Encountering io.EOF means the device has disappeared.
+				log.Fatalf("device disappeared: %s", d.Name)
 			}()
 		}
 	}

When unplugging the serial adapter, consrv starts crashlooping until the adapter is plugged back in.

Obviously this is only doable when you have precisely 1 serial adapter you care about, otherwise the failure of one results in all other adapters being unavailable, too.