From 1a67fb8ffc573930a633a3e5a17556dcfa17da0b Mon Sep 17 00:00:00 2001 From: Yanhu007 Date: Tue, 14 Apr 2026 23:42:39 +0800 Subject: [PATCH] fix: prevent mutex double-unlock panic in retrieveFileList retrieveFileList holds w.mu via defer Unlock, but also manually calls Unlock/Lock around Remove operations. If the type assertion err.(*os.PathError) panics (e.g., when the error wraps a different type), the deferred Unlock fires on an already-unlocked mutex, causing "sync: unlock of unlocked mutex". Fix by using a safe type assertion (comma-ok pattern) and only unlocking the mutex when the assertion succeeds, ensuring the manual Unlock/Lock and deferred Unlock never conflict. Fixes #121 --- watcher.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/watcher.go b/watcher.go index 4da4dfe..5e6fc4e 100644 --- a/watcher.go +++ b/watcher.go @@ -496,12 +496,12 @@ func (w *Watcher) retrieveFileList() map[string]os.FileInfo { list, err = w.listRecursive(name) if err != nil { if os.IsNotExist(err) { - w.mu.Unlock() - if name == err.(*os.PathError).Path { + if pathErr, ok := err.(*os.PathError); ok && name == pathErr.Path { + w.mu.Unlock() w.Error <- ErrWatchedFileDeleted w.RemoveRecursive(name) + w.mu.Lock() } - w.mu.Lock() } else { w.Error <- err } @@ -510,12 +510,12 @@ func (w *Watcher) retrieveFileList() map[string]os.FileInfo { list, err = w.list(name) if err != nil { if os.IsNotExist(err) { - w.mu.Unlock() - if name == err.(*os.PathError).Path { + if pathErr, ok := err.(*os.PathError); ok && name == pathErr.Path { + w.mu.Unlock() w.Error <- ErrWatchedFileDeleted w.Remove(name) + w.mu.Lock() } - w.mu.Lock() } else { w.Error <- err }