[GH-ISSUE #1017] App becomes unresponsive sometimes if something inside panics #737

Closed
opened 2026-03-04 01:07:23 +03:00 by kerem · 8 comments
Owner

Originally created by @freak12techno on GitHub (Aug 11, 2024).
Original GitHub issue: https://github.com/rivo/tview/issues/1017

So I am building a tool called tmtop, and sometimes it may panic (that's another topic why). Problem is, once it panics, the terminal becomes unresponsive, like what you can see on a screen, and the terminal tab becomes that much messed up so it's easier to close it and open a new one.
image

On the other hand, if it panics before app.Run() is called, its output is not messed up. I assume the app somehow fails to close properly or fails to render it back to usable state.

Here's how I start it:

func (w *Wrapper) Start() {
	// some text rendering and key events bindings

	w.App.SetBeforeDrawFunc(func(screen tcell.Screen) bool {
		screen.Clear()
		return false
	})

	if err := w.App.Run(); err != nil {
		w.Logger.Panic().Err(err).Msg("Could not draw screen") // this calls panic() internally
	}
}

How can I fix this so the terminal tab would fallback to the usable state and the panic stacktrace would be displayed properly?

Originally created by @freak12techno on GitHub (Aug 11, 2024). Original GitHub issue: https://github.com/rivo/tview/issues/1017 So I am building a tool called [tmtop](https://github.com/QuokkaStake/tmtop), and sometimes it may panic (that's another topic why). Problem is, once it panics, the terminal becomes unresponsive, like what you can see on a screen, and the terminal tab becomes that much messed up so it's easier to close it and open a new one. <img width="1440" alt="image" src="https://github.com/user-attachments/assets/82e36a48-ece7-4e90-b147-4275c8a1e7ca"> On the other hand, if it panics before `app.Run()` is called, its output is not messed up. I assume the app somehow fails to close properly or fails to render it back to usable state. Here's how I start it: ``` func (w *Wrapper) Start() { // some text rendering and key events bindings w.App.SetBeforeDrawFunc(func(screen tcell.Screen) bool { screen.Clear() return false }) if err := w.App.Run(); err != nil { w.Logger.Panic().Err(err).Msg("Could not draw screen") // this calls panic() internally } } ``` How can I fix this so the terminal tab would fallback to the usable state and the panic stacktrace would be displayed properly?
kerem closed this issue 2026-03-04 01:07:23 +03:00
Author
Owner

@kivattt commented on GitHub (Aug 12, 2024):

I work around this issue by piping the stderr output into a file, like

./program 2> tempfile.txt

that way i can read the stacktrace by looking at the file

<!-- gh-comment-id:2282985420 --> @kivattt commented on GitHub (Aug 12, 2024): I work around this issue by piping the stderr output into a file, like ```console ./program 2> tempfile.txt ``` that way i can read the stacktrace by looking at the file
Author
Owner

@freak12techno commented on GitHub (Aug 12, 2024):

@kivattt yeah, but it's ugly for me as an app developer to ask the users of my app to use this approach to avoid their terminal tab getting messed up. I'd rather do something in my app so it'd quit properly to avoid that, if it's possible.

<!-- gh-comment-id:2283501890 --> @freak12techno commented on GitHub (Aug 12, 2024): @kivattt yeah, but it's ugly for me as an app developer to ask the users of my app to use this approach to avoid their terminal tab getting messed up. I'd rather do something in my app so it'd quit properly to avoid that, if it's possible.
Author
Owner

@rivo commented on GitHub (Aug 13, 2024):

It's unfortunate that you didn't post any code that we can run ourselves to reproduce this. Generally, panicking while your application is running is not a problem. It doesn't lead to the effect you're seeing. This is because panics are recovered here:

github.com/rivo/tview@e4c497cc59/application.go (L287)

Here's an example:

func main() {
	box := tview.NewBox()

	box.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		panic("panic!")
	})

	if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {
		fmt.Println("Error:", err)
	}
}

This will lead to a properly formatted stack trace:

image

If you panic after Run() returns, as your code above suggests, that's also not a problem. See here:

func main() {
	app := tview.NewApplication()
	box := tview.NewBox()

	box.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		// Cause Run() to return with an error.
		app.QueueEvent(tcell.NewEventError(errors.New("error")))
		return nil
	})

	if err := app.SetRoot(box, true).Run(); err != nil {
		panic("panic!")
	}
}

Again, properly formatted stack trace:

image

In both cases, the screen is "finalized" (see here for details) before the stack trace is printed.

So I'm guessing that you're panicking in a separate goroutine, before Run() returns. In that case, yes, the terminal will still be set up for tcell's screen rendering and simply printing out a stack trace to stdout/stderr will lead to the issues you're seeing.

You don't want to do that. Be sure to exit your application first before causing a panic or ensure that the panic occurs in the main thread instead of a goroutine.

<!-- gh-comment-id:2286049197 --> @rivo commented on GitHub (Aug 13, 2024): It's unfortunate that you didn't post any code that we can run ourselves to reproduce this. Generally, panicking while your application is running is not a problem. It doesn't lead to the effect you're seeing. This is because panics are recovered here: https://github.com/rivo/tview/blob/e4c497cc59ed2f1b8eea43ef37456edc63e21749/application.go#L287 Here's an example: ```go func main() { box := tview.NewBox() box.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { panic("panic!") }) if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil { fmt.Println("Error:", err) } } ``` This will lead to a properly formatted stack trace: <img width="462" alt="image" src="https://github.com/user-attachments/assets/2a06d531-6bef-429a-9874-a3dc6de15c57"> If you panic after `Run()` returns, as your code above suggests, that's also not a problem. See here: ```go func main() { app := tview.NewApplication() box := tview.NewBox() box.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { // Cause Run() to return with an error. app.QueueEvent(tcell.NewEventError(errors.New("error"))) return nil }) if err := app.SetRoot(box, true).Run(); err != nil { panic("panic!") } } ``` Again, properly formatted stack trace: <img width="464" alt="image" src="https://github.com/user-attachments/assets/9dd94664-1cc6-4f71-8abf-7883cb076b91"> In both cases, the screen is "finalized" (see [here](https://pkg.go.dev/github.com/gdamore/tcell/v2#Screen.Fini) for details) before the stack trace is printed. So I'm guessing that you're panicking in a separate goroutine, before `Run()` returns. In that case, yes, the terminal will still be set up for `tcell`'s screen rendering and simply printing out a stack trace to stdout/stderr will lead to the issues you're seeing. You don't want to do that. Be sure to exit your application first before causing a panic or ensure that the panic occurs in the main thread instead of a goroutine.
Author
Owner

@freak12techno commented on GitHub (Aug 13, 2024):

@rivo okay, I managed to write a minimal example that reproduces it:

package main

import (
	"errors"
	"github.com/rivo/tview"
	"time"
)

func main() {
	grid := tview.NewGrid().
		SetRows(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
		SetColumns(0, 0, 0, 0, 0, 0).
		SetBorders(true)

	pages := tview.NewPages().AddPage("grid", grid, true, true)

	app := tview.NewApplication().SetRoot(pages, true)

	go func() {
		time.Sleep(2 * time.Second)
		panic(errors.New("Panic!"))
	}()

	if err := app.Run(); err != nil {
		panic(err)
	}
}

So I'm guessing that you're panicking in a separate goroutine, before Run() returns.

so basically yes. I need to do a bunch of queries in my app asynchronously, and sometimes it might panic, if for example the response is malformed or something similar. How can I make it panic properly so it won't mess up my terminal with this?

<!-- gh-comment-id:2286567101 --> @freak12techno commented on GitHub (Aug 13, 2024): @rivo okay, I managed to write a minimal example that reproduces it: ``` package main import ( "errors" "github.com/rivo/tview" "time" ) func main() { grid := tview.NewGrid(). SetRows(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). SetColumns(0, 0, 0, 0, 0, 0). SetBorders(true) pages := tview.NewPages().AddPage("grid", grid, true, true) app := tview.NewApplication().SetRoot(pages, true) go func() { time.Sleep(2 * time.Second) panic(errors.New("Panic!")) }() if err := app.Run(); err != nil { panic(err) } } ``` > So I'm guessing that you're panicking in a separate goroutine, before Run() returns. so basically yes. I need to do a bunch of queries in my app asynchronously, and sometimes it might panic, if for example the response is malformed or something similar. How can I make it panic properly so it won't mess up my terminal with this?
Author
Owner

@rivo commented on GitHub (Aug 14, 2024):

How can I make it panic properly so it won't mess up my terminal with this?

What I wrote before:

Be sure to exit your application first before causing a panic or ensure that the panic occurs in the main thread instead of a goroutine.

Or don't panic at all. (Which I think is generally good advice for Go anyway.)

If it's absolutely unavoidable, catch the panic, then stop the application, then panic again. Basically, again, "...exit your application first before causing a panic".

In general, you cannot write to stdout or stderr while a tview application is running. Well, you can, but it will look ugly.

<!-- gh-comment-id:2288435656 --> @rivo commented on GitHub (Aug 14, 2024): > How can I make it panic properly so it won't mess up my terminal with this? What I wrote before: > Be sure to exit your application first before causing a panic or ensure that the panic occurs in the main thread instead of a goroutine. Or don't panic at all. (Which I think is generally good advice for Go anyway.) If it's absolutely unavoidable, catch the panic, then stop the application, then panic again. Basically, again, _"...exit your application first before causing a panic"_. In general, you cannot write to stdout or stderr while a `tview` application is running. Well, you can, but it will look ugly.
Author
Owner

@freak12techno commented on GitHub (Aug 15, 2024):

@rivo gotcha. I suggest adding this info you outlined above to the docs/wiki (unless I overlooked it if it's already present), so others would know about it in advance, and I basically got my answer so I think you can close this issue as there's no action points to be done (except documenting it). Thanks!

<!-- gh-comment-id:2291989136 --> @freak12techno commented on GitHub (Aug 15, 2024): @rivo gotcha. I suggest adding this info you outlined above to the docs/wiki (unless I overlooked it if it's already present), so others would know about it in advance, and I basically got my answer so I think you can close this issue as there's no action points to be done (except documenting it). Thanks!
Author
Owner

@freak12techno commented on GitHub (Aug 26, 2024):

@rivo one more case I found out, somewhat related to the one outlined in the issue. If I run the app in an ssh session, and somehow it disconnects, it doesn't "finalize" the screen so the terminal tab becomes a mess. Do you think there's something that can be done to avoid it?

<!-- gh-comment-id:2311277983 --> @freak12techno commented on GitHub (Aug 26, 2024): @rivo one more case I found out, somewhat related to the one outlined in the issue. If I run the app in an ssh session, and somehow it disconnects, it doesn't "finalize" the screen so the terminal tab becomes a mess. Do you think there's something that can be done to avoid it?
Author
Owner

@rivo commented on GitHub (Aug 28, 2024):

I don't think so. To my understanding, the terminal is set to some special mode which allows us to draw characters anywhere on screen. If the application doesn't terminate orderly, it can't reset the terminal to normal mode again.

In any case, this is all a topic for tcell: https://github.com/gdamore/tcell If you want to know the details about how this works or what can be done, please ask there.

<!-- gh-comment-id:2315026849 --> @rivo commented on GitHub (Aug 28, 2024): I don't think so. To my understanding, the terminal is set to some special mode which allows us to draw characters anywhere on screen. If the application doesn't terminate orderly, it can't reset the terminal to normal mode again. In any case, this is all a topic for `tcell`: https://github.com/gdamore/tcell If you want to know the details about how this works or what can be done, please ask there.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#737
No description provided.