mirror of
https://github.com/tw93/Mole.git
synced 2026-02-15 13:25:04 +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 (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -448,3 +449,40 @@ func TestScanPathPermissionError(t *testing.T) {
|
|||||||
t.Logf("unexpected error type: %v", err)
|
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 {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
subDir := filepath.Join(dirPath, entry.Name())
|
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)
|
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 {
|
} else {
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user