1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-08 12:04:21 +00:00

fix(analyze): fix scan deadlock with non-blocking fallback and add regression test (#419)

This commit is contained in:
tw93
2026-02-07 11:01:00 +08:00
parent 5cdfcf2479
commit 95b3818da8
2 changed files with 51 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/gob"
"fmt"
"os"
"path/filepath"
"strings"
@@ -448,3 +449,40 @@ func TestScanPathPermissionError(t *testing.T) {
t.Logf("unexpected error type: %v", err)
}
}
func TestCalculateDirSizeFastHighFanoutCompletes(t *testing.T) {
root := t.TempDir()
// Reproduce high fan-out nested directory pattern that previously risked semaphore deadlock.
const fanout = 256
for i := 0; i < fanout; i++ {
nested := filepath.Join(root, fmt.Sprintf("dir-%03d", i), "nested")
if err := os.MkdirAll(nested, 0o755); err != nil {
t.Fatalf("create nested dir: %v", err)
}
if err := os.WriteFile(filepath.Join(nested, "data.bin"), []byte("x"), 0o644); err != nil {
t.Fatalf("write nested file: %v", err)
}
}
var files, dirs, bytes int64
current := &atomic.Value{}
current.Store("")
done := make(chan int64, 1)
go func() {
done <- calculateDirSizeFast(root, &files, &dirs, &bytes, current)
}()
select {
case total := <-done:
if total <= 0 {
t.Fatalf("expected positive total size, got %d", total)
}
if got := atomic.LoadInt64(&files); got < fanout {
t.Fatalf("expected at least %d files scanned, got %d", fanout, got)
}
case <-time.After(5 * time.Second):
t.Fatalf("calculateDirSizeFast did not complete under high fan-out")
}
}

View File

@@ -351,14 +351,20 @@ func calculateDirSizeFast(root string, filesScanned, dirsScanned, bytesScanned *
for _, entry := range entries {
if entry.IsDir() {
subDir := filepath.Join(dirPath, entry.Name())
sem <- struct{}{}
wg.Add(1)
go func(p string) {
defer wg.Done()
defer func() { <-sem }()
walk(p)
}(subDir)
atomic.AddInt64(dirsScanned, 1)
select {
case sem <- struct{}{}:
wg.Add(1)
go func(p string) {
defer wg.Done()
defer func() { <-sem }()
walk(p)
}(subDir)
default:
// Fallback to synchronous traversal to avoid semaphore deadlock under high fan-out.
walk(subDir)
}
} else {
info, err := entry.Info()
if err == nil {