You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
os/v3/puppy: the console-TUI promise 'a paint failure redraws' was never implemented - one EINTR ended the console, and the cycle-26 heal doubled the exposure #1101
os/v3/puppy/console-tui/src/main.rs opens with three promises, and the first one covers paint failures:
the console never goes dead. q does not exit; Ctrl-C does not exit; a paint failure redraws; if this process does
die, 1bit-console falls back to the dialog menu and /etc/inittab respawns it.
The loop does not implement the bolded half: the first paint error ends the process.
if term.draw(|f| ui::draw(f,&app,&mut links)).is_err(){break1;}
Two of the three error classes there are handled correctly by exiting - a terminal that cannot be written should fall back to
the dialog menu - but the third one is not an error at all:
ErrorKind::Interrupted: a signal arrived before the write reached the terminal. Nothing was drawn, the screen is exactly
as it was, and the right answer is to paint again on the next pass. Ending the console over it turns a harmless resize into a
screen change, which is the one thing this binary exists to prevent.
and it is reachable: crossterm's event source installs a SIGWINCH handler (that is how resize events arrive), so an ssh
user dragging a window edge is the realistic trigger. On tty1 there is no resizing, so the appliance itself is unlikely to see
it - ssh -t root@<box> /usr/local/bin/1bit-console-tui is the documented way in, and that is where windows get resized.
#1093 made it twice as likely per second. The heal added term.clear() before each full repaint, guarded the same way, so the
loop now has two "one error ends the console" points and the clear runs once a second instead of never.
Fix
paint_failed(&io::Error) -> bool = "is this error fatal for the console?", true for everything except Interrupted; clear()
and draw() both consult it - an interrupted clear is ignored (the heal retries next pass), an interrupted draw skips the
hyperlink pass and repaints on the next loop iteration, and any other error still exits so 1bit-console shows the dialog menu.
Measured
Unit test an_interrupted_paint_is_repainted_but_a_dead_terminal_ends_the_console: Interrupted -> retry; BrokenPipe, PermissionDenied, NotFound, Other -> fatal. 53 tests (52 + this).
12 window resizes in 2 s while the TUI runs (tmux resize-window, i.e. a SIGWINCH storm): the session survives, nine actions
drawn, q still stays on the console.
The heal itself is unchanged on the happy path: a stray write is gone at +1.0 s (80x25, pty), both committed snapshots
byte-identical, the eight gate strings still on the 80x25 frame, artifact 906705b04783a87d (1,581,568 B, static).
Not measured: the EINTR itself. Inducing a signal inside crossterm's write is not something a pty can be made to do on
demand, so the classification is unit-tested and the code path is reasoned - what is demonstrated is the resize storm (survives)
and that a fatal error still exits (the fallback path was measured in cycle 18: TUI exits 0 / exits 7 / absent all end in the
dialog menu).
Activity