mirror of
https://github.com/tw93/Mole.git
synced 2026-02-08 07:59:16 +00:00
fix(analyze): fix scan deadlock with non-blocking fallback and add regression test (#419)
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user