Repository navigation
fix(cli): show the stack trace of a panic in the TUI - #44
Conversation
Since p10node#41, os.Stderr is a pipe while the TUI runs, so that a credential plugin cannot write over the screen. When the TUI panics, Bubble Tea prints the panic's value to stdout and then its stack trace with debug.PrintStack, which writes to os.Stderr: the trace went into the pipe, which kept only its last line, so a crash report said what happened but not where. The pipe's reader now also keeps the last 64 KiB it read, as written. When Run returns tea.ErrProgramPanic, main closes the pipe, gives the reader up to a second to finish (a credential plugin that is still running holds the pipe open too), and writes those bytes to the terminal ahead of its own error line. This covers both panics Bubble Tea recovers: one on its event loop, and one in a command's goroutine. Signed-off-by: Adao Oliveira Jr <junior@users.noreply.github.com>
Signed-off-by: Adao Oliveira Jr <junior@users.noreply.github.com>
|
@pierreneter, could you take a look at this one when you have a moment? It's the stack-trace fix from the #41 review, and the last change for v0.1.10: #39 is in and you've approved the notes in #43, so once this lands I'll merge the notes and tag the release. |
Requesting changes:
|
When a command's goroutine panics, Bubble Tea 1.3.10 sends ErrProgramPanic and cancels the program first, and only then prints the stack trace, from that goroutine. Run does not wait for command goroutines, so it could return, and the pipe could be closed, before the trace was written: the write then failed with os.ErrClosed and the trace was lost. TestBubbleTeaPanicStackTracesReachTheTail caught it now and then: 25 times in 100 for pierreneter on darwin/arm64, 4 in 100 on linux/amd64. drain now waits, up to its wait, until nothing new has been read for 50 ms, and only then closes the write end, as pierreneter suggested in his review. A panic exit takes 50 to 100 ms longer. The comments no longer say the tail is byte for byte: the scanner drops a \r before a newline, and a last line written without one gets one. Signed-off-by: Adao Oliveira Jr <junior@users.noreply.github.com>
|
Thanks for digging into this, and for the fix! You're right: I took your
And agreed on the panic value: Bubble Tea prints it on the alt screen, so it's gone with it, and that's outside this PR, as you say. |
There was a problem hiding this comment.
Thanks @junior, the quiet-wait fix holds up: 0 failures in 200 runs of the loop on darwin/arm64 and the trace shows for both panic kinds.
Point 1 of the review of #41. Since #41,
os.Stderris a pipe while the TUI runs. When the TUI panics, Bubble Tea 1.3.10 prints the panic's value to stdout but the stack trace withdebug.PrintStack(), which writes toos.Stderr: the trace went into the pipe, which kept only its last line.The tap now also keeps the last 64 KiB it read, line by line. When
Runreturnstea.ErrProgramPanic,mainwaits up to a second for the pipe to go quiet, closes it, gives the reader up to a second to finish (a credential plugin that is still running holds the pipe open too), and writes those bytes to the terminal ahead of itsk10s: ...line. The wait is for a panic in a command's goroutine: Bubble Tea writes that trace from the goroutine after ending the program, andRundoes not wait for it. Both panics Bubble Tea recovers are covered: on its event loop, and in a command's goroutine. A normal exit is unchanged.Tests:
TestBubbleTeaPanicStackTracesReachTheTailruns a Bubble Tea program that panics each way with the tap in place and checks that both stack traces are in the tail;TestDrainStderrGivesBackWhatWasWrittenandTestStderrTailKeepsTheNewestBytescover the tail itself, andTestTUIFailedPutsTheStackTraceFirstcoversmain's side, with a stand-in for the pipe, which a process captures only once. The tail tests fail with a tap that keeps nothing, and themaintest fails without the write. gofmt, vet andgo test -short ./...pass on Go 1.26.11 and 1.27.4, and so does-raceon.,internal/k8sandinternal/ui.TestBubbleTeaPanicStackTracesReachTheTailfailed now and then without the wait, as pierreneter found; with it, it passed 300 runs of 300 on darwin/arm64 and 500 of 500 on linux/amd64.