From 9e3d6987e2f00c3d9e289be3e265c04a42a9776e Mon Sep 17 00:00:00 2001 From: valoq Date: Sun, 19 Jul 2026 12:51:05 +0200 Subject: [PATCH] fix: do not block ui on dead fuse mount --- app.go | 2 ++ eval.go | 3 ++- nav.go | 35 +++++++++++++++++++++++++++++++---- ui.go | 1 + 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index 725962b09..15f622070 100644 --- a/app.go +++ b/app.go @@ -438,6 +438,8 @@ func (app *app) loop() { } app.ui.draw(app.nav) + case fn := <-app.nav.checkChan: + fn() case r := <-app.nav.regChan: if r.height != app.nav.height { delete(app.nav.regCache, r.path) diff --git a/eval.go b/eval.go index 07b157098..c80277166 100644 --- a/eval.go +++ b/eval.go @@ -1597,7 +1597,8 @@ func (e *callExpr) eval(app *app, _ []string) { dir := app.nav.currDir() app.nav.checkDir(dir) - if dir.loading { + // the directory is checked asynchronously, so show the file in the meantime + if (dir.loading || dir.checking) && !slices.ContainsFunc(dir.files, func(f *file) bool { return f.path == path }) { dir.files = append(dir.files, &file{FileInfo: lstat}) } dir.sel(filepath.Base(path), app.nav.height) diff --git a/nav.go b/nav.go index 776a49736..b396c918d 100644 --- a/nav.go +++ b/nav.go @@ -163,6 +163,7 @@ func readdir(path string) ([]*file, error) { type dir struct { loading bool // whether directory is loading from disk + checking bool // whether directory is being checked for changes loadTime time.Time // last load time ind int // 0-based index of current entry in dir.files pos int // 0-based cursor row in directory window @@ -453,6 +454,7 @@ type nav struct { preloadChan chan string previewChan chan string dirChan chan *dir + checkChan chan func() regChan chan *reg fileChan chan *file delChan chan string @@ -510,11 +512,21 @@ func (nav *nav) getDir(path string) *dir { } func (nav *nav) checkDir(dir *dir) { - if dir.loading { + if dir.loading || dir.checking { return } - s, err := os.Stat(dir.path) + // os.Stat can block forever on an unresponsive filesystem, so keep it off the ui goroutine + dir.checking = true + go func() { + s, err := os.Stat(dir.path) + nav.checkChan <- func() { nav.checkedDir(dir, s, err) } + }() +} + +func (nav *nav) checkedDir(dir *dir, s os.FileInfo, err error) { + dir.checking = false + if err != nil { log.Printf("getting directory info: %s", err) return @@ -586,6 +598,7 @@ func newNav(ui *ui) *nav { preloadChan: make(chan string, 1024), previewChan: make(chan string, 1024), dirChan: make(chan *dir), + checkChan: make(chan func()), regChan: make(chan *reg), fileChan: make(chan *file), delChan: make(chan string), @@ -989,7 +1002,21 @@ func (nav *nav) loadReg(path string, volatile bool) *reg { } func (nav *nav) checkReg(reg *reg) { - s, err := os.Stat(reg.path) + if reg.checking { + return + } + + // os.Stat can block forever on an unresponsive filesystem, so keep it off the ui goroutine + reg.checking = true + go func() { + s, err := os.Stat(reg.path) + nav.checkChan <- func() { nav.checkedReg(reg, s, err) } + }() +} + +func (nav *nav) checkedReg(reg *reg, s os.FileInfo, err error) { + reg.checking = false + if err != nil { return } @@ -1584,7 +1611,7 @@ func (nav *nav) rename() error { dir := nav.getDir(filepath.Dir(newPath)) nav.checkDir(dir) - if dir.loading { + if dir.loading || dir.checking { for i := range dir.allFiles { if dir.allFiles[i].path == oldPath { dir.allFiles[i] = &file{FileInfo: lstat} diff --git a/ui.go b/ui.go index 6e4c2d0e5..e33c0922b 100644 --- a/ui.go +++ b/ui.go @@ -606,6 +606,7 @@ func (ui *ui) echoerrf(format string, a ...any) { // file preview, but `previewer` now also supports non-regular files. type reg struct { loading bool + checking bool volatile bool loadTime time.Time path string