Repository navigation
terminal: bridge serial ports in the server, without picocom - #78
Conversation
The serial terminal ran picocom in a PTY, but no build shipped picocom (the docs said a custom static build was bundled; nothing built or packaged it), so every serial terminal ended with "Unable to start terminal session". A terminal on a serial port needs a byte bridge and line settings, not a PTY child with its own key sequences, lock files and a package. runSerialSession opens the device itself: O_NONBLOCK and O_NOCTTY, one session per port within the server, flock and TIOCEXCL against other programs, raw termios with the speed, parity, data bits, stop bits and flow control the query already validated (ttyGS is nominal 9600 8N1 as before), then bytes both ways between the WebSocket and the fd. Resize JSON is dropped; the browser's OSC 777 size reply is input as before. An unplugged device, a port held by another session or program, or a missing device ends the session with a message. The console probe reports busy while a session holds the port. The picocom build script, its packaging requirements in the legacy release scripts and the docs paragraph go; the docs describe the bridge. Tests: a pty stands in for the device: bytes both ways with ANSI and all 256 byte values, resize dropped and the OSC reply passed, the line settings on the device, a 15 KiB paste in order, a second session and the probe while one is open, the device going away, a missing device; the query validation and the termios mapping. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Review of
Reproduction output: This PR is stacked on #76, not main. Rebase it on the corrected parent before final integration. No physical-device access or deployment was used for this review. |
Review: the busy check was an observation only, and deviceInUse skips this process, so two console tests at once both opened the port, read each other's answer and restored each other's line settings. The probe now takes a per-device lease before anything else and gives it back after the device is closed and the settings are restored; a second probe meanwhile reports busy. The lease registry (serial_lease.go) is the one the terminal session in #78 will share, so either holder keeps the other out in both start orders. Test: a 600 ms probe on a silent host, a 200 ms probe started while the first waits for its answer is busy, the lease is gone when the first ends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # server/service/vm/serial_console.go
… lease Review of the serial session, three findings. One writer. The device side and the input side both wrote bytes and notices to the WebSocket, and gorilla allows one data writer at a time: closing the device while a large browser write was blocked panicked with "concurrent write to websocket connection", in a goroutine the HTTP recovery does not cover. Every data write and its deadline now go through one mutex (wsWriter); the pings are control frames and stay outside. Cancellable writes. writeAllSerial waited for room in the tty for ever, and it ran in the goroutine that reads the WebSocket, so a browser that closed while the line did not take data was never seen and the device stayed claimed. The browser's messages now go through a bounded queue to a writer goroutine that waits in 200 ms slices and stops with the session; the WebSocket reader never waits on the tty. The ping goroutine is the watch on the connection's life: a ping to a browser that has gone fails, and that ends the session. No write timeout: a slow line still gets its bytes. The descriptor is closed after every goroutine has returned, and the lease released after that. The lease. The probe's busy check saw a terminal, but a terminal started while the probe ran opened the device and later got the probe's restored canonical settings over its raw line. #76 now holds a per-device lease for the probe's whole run (serial_lease.go); the session claims the same one, so either keeps the other out in both start orders. Tests: a disconnect during traffic in both directions (20 runs under -race), the browser closing during a blocked write releases the device within the ping interval, a terminal refused while the probe holds the device and admitted afterwards; the existing exclusivity test with the generic notice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
All three in
|
|
Re-review complete on |
The serial terminal (
GET /api/vm/terminal?port=/dev/tty…) ran picocom in a PTY, and no build shipped picocom:docs/usb-composition.mdsaid a custom static build was bundled, but neitherplatform/build.shnorpackages.listbuilt or packaged it, so every serial terminal ended with "Unable to start terminal session." (found while testing #76). As decided, the server now bridges the port itself instead of packaging picocom.Stacked on #76 (its branch is the base, merged in at 9b0c91f): the per-device lease in
serial_lease.gothere is shared by the console probe and the terminal session, so either keeps the other out in both start orders.The bridge (
serial_session.go)serialSettingsFromQueryvalidates whatterminal_command.govalidated before: the port pattern, baud (the fullBnnnlist), parity (none/even/odd, alson/e/o), flow control (none/hard/soft, alson/h/s/x), data bits 5–8, stop bits 1–2.ttyGSports stay nominal 9600 8N1 without flow control, as before.runSerialSessionclaims the device's lease, opens it (O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC),flock(LOCK_EX|LOCK_NB)andTIOCEXCLagainst other programs, applies raw termios (cfmakerawequivalent plus the speed viaCBAUD/Ispeed/Ospeed, parity, size, stop bits,CRTSCTSorIXON|IXOFF,VMIN/VTIME0), flushes, and copies bytes both ways:POLLOUTwaits in 200 ms slices that end with the session. No write timeout, so a slow line still gets its bytes. The ping goroutine is the watch on the connection's life: a ping to a browser that has gone fails and ends the session, so a line that does not take data (flow control asserted, nothing at the other end) cannot keep the device claimed after the browser closed;EIO/ENXIO/ENODEV, or EOF on the tty)./bin/sh -lin a PTY) is unchanged.Removed
scripts/build-picocom.sh, its requirements in the legacyscripts/package.shandscripts/build-in-container.sh, the row inscripts/README.md, and the docs paragraph about the bundled build. The docs now describe the bridge.Checked
go vet,go test -tags teststub ./..., and the package under-race. Tests with a pty as the device: bytes both ways including ANSI and all 256 byte values; resize JSON dropped and the OSC 777 reply delivered; the line settings reach the device (stop bits, speed, raw; the pty driver forces CS8 without parity, so those two are covered by the termios unit test); a 15 KiB paste arrives in order; a second session is refused and the first keeps working; the console probe reportsbusywhile a session is open and not after; a terminal is refused while the probe holds the device and admitted afterwards; a disconnect during traffic in both directions with a blocked 64 KiB browser write (20 runs under-race, this is what panicked before); the browser closing during a blocked write releases the device within the ping interval; the device going away ends the session with a notice; a missing device; the query validation and the termios mapping./dev/ttyGS0opens and stays (before this change: "Unable to start terminal session"),POST /api/vm/serial-console/testreportsbusywhile it is open andsilentafter it closes, a second terminal on the same port gets the "in use" notice, and an invalid query is a 400.Not covered
ttyUSB,ttyS) at its speed: the stand has none attached; the speed and framing settings are checked on a pty and by the termios test.🤖 Generated with Claude Code