From d1faccdd1ce9fffb53f877215729a521f6748a1c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jun 2025 12:51:59 +0530 Subject: [PATCH] Loop API print proper stack traces for panics in go routines --- kittens/choose_files/scan.go | 1 + kittens/diff/ui.go | 5 +++++ tools/tui/loop/api.go | 18 ++++++++++++++---- tools/tui/loop/run.go | 3 +++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/kittens/choose_files/scan.go b/kittens/choose_files/scan.go index aeedafd4e..fb4a22c47 100644 --- a/kittens/choose_files/scan.go +++ b/kittens/choose_files/scan.go @@ -229,6 +229,7 @@ func (h *Handler) get_results() (ans []*ResultItem, in_progress bool) { sc.search_text = st sp := h.state.ScorePatterns() go func() { + defer h.lp.RecoverFromPanicInGoRoutine() results := sc.scan(cd, st, sp) sc.mutex.Lock() defer sc.mutex.Unlock() diff --git a/kittens/diff/ui.go b/kittens/diff/ui.go index a5737958b..4c0738c4e 100644 --- a/kittens/diff/ui.go +++ b/kittens/diff/ui.go @@ -171,6 +171,7 @@ func (self *Handler) initialize() { self.original_context_count = self.current_context_count self.async_results = make(chan AsyncResult, 32) go func() { + self.lp.RecoverFromPanicInGoRoutine() r := AsyncResult{} r.collection, r.err = create_collection(self.left, self.right) self.async_results <- r @@ -191,6 +192,7 @@ func (self *Handler) generate_diff() { return nil }) go func() { + self.lp.RecoverFromPanicInGoRoutine() r := AsyncResult{rtype: DIFF} r.diff_map, r.err = diff(jobs, self.current_context_count) self.async_results <- r @@ -230,6 +232,7 @@ func (self *Handler) highlight_all() { } text_files := utils.Filter(self.collection.paths_to_highlight.AsSlice(), is_path_text) go func() { + self.lp.RecoverFromPanicInGoRoutine() r := AsyncResult{rtype: HIGHLIGHT} highlight_all(text_files, use_light_colors) self.async_results <- r @@ -252,6 +255,7 @@ func (self *Handler) load_all_images() { if self.image_count > 0 { image_collection.Initialize(self.lp) go func() { + self.lp.RecoverFromPanicInGoRoutine() r := AsyncResult{rtype: IMAGE_LOAD} image_collection.LoadAll() self.async_results <- r @@ -273,6 +277,7 @@ func (self *Handler) resize_all_images_if_needed() { } if sz != self.images_resized_to && self.image_count > 0 { go func() { + self.lp.RecoverFromPanicInGoRoutine() image_collection.ResizeForPageSize(sz.Width, sz.Height) r := AsyncResult{rtype: IMAGE_RESIZE, page_size: sz} self.async_results <- r diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index 7c83dbc4b..7c4136d9c 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -49,6 +49,7 @@ type Loop struct { timers, timers_temp []*timer timer_id_counter, write_msg_id_counter IdType wakeup_channel chan byte + panic_channel chan any pending_writes []write_msg tty_write_channel chan write_msg pending_mouse_events *utils.RingBuffer[MouseEvent] @@ -328,11 +329,14 @@ func (self *Loop) Run() (err error) { lines = append(lines, fmt.Sprintf("%s\r\n\t%s:%d\r\n", frame.Function, frame.File, frame.Line)) } text := strings.Join(lines, "") - os.Stderr.WriteString(text) tty.DebugPrintln(strings.TrimSpace(text)) - if self.terminal_options.Alternate_screen { - term, err := tty.OpenControllingTerm(tty.SetRaw) - if err == nil { + is_terminal := tty.IsTerminal(os.Stderr.Fd()) + if is_terminal { + os.Stderr.WriteString("\x1b]\x1b\\\x1bc\x1b[H\x1b[2J") // reset terminal + } + os.Stderr.WriteString(text) + if is_terminal { + if term, err := tty.OpenControllingTerm(tty.SetRaw); err == nil { defer term.RestoreAndClose() fmt.Println("Press any key to exit.\r") buf := make([]byte, 16) @@ -588,6 +592,12 @@ type SizedText struct { Width int } +func (self *Loop) RecoverFromPanicInGoRoutine() { + if r := recover(); r != nil { + self.panic_channel <- r + } +} + func (self *Loop) DrawSizedText(text string, spec SizedText) { b := strings.Builder{} b.Grow(len(text) + 24) diff --git a/tools/tui/loop/run.go b/tools/tui/loop/run.go index 76aaca346..8f8b2d3db 100644 --- a/tools/tui/loop/run.go +++ b/tools/tui/loop/run.go @@ -394,6 +394,7 @@ func (self *Loop) run() (err error) { self.write_msg_id_counter = 0 write_done_channel := make(chan IdType) self.wakeup_channel = make(chan byte, 256) + self.panic_channel = make(chan any) self.pending_writes = make([]write_msg, 0, 256) err_channel := make(chan error, 8) self.death_signal = SIGNULL @@ -563,6 +564,8 @@ func (self *Loop) run() (err error) { } select { case <-timeout_chan: + case p := <-self.panic_channel: + panic(p) case <-self.wakeup_channel: for len(self.wakeup_channel) > 0 { <-self.wakeup_channel