From 74a5930495003eb67201a8e94b9baf1394da0ad2 Mon Sep 17 00:00:00 2001 From: PLN <78331+PLN@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:15:09 +0200 Subject: [PATCH 1/3] test: combine native process pressure with storage and reader faults --- internal/journal/diskfull_windows_test.go | 11 + internal/journal/export_test.go | 56 +++++ .../journal/manager_pressure_windows_test.go | 98 +++++++- .../operational_pressure_windows_test.go | 237 ++++++++++++++++++ 4 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 internal/journal/operational_pressure_windows_test.go diff --git a/internal/journal/diskfull_windows_test.go b/internal/journal/diskfull_windows_test.go index e2d29fc..c513615 100644 --- a/internal/journal/diskfull_windows_test.go +++ b/internal/journal/diskfull_windows_test.go @@ -16,6 +16,13 @@ import ( // This opt-in test fills only a separately prepared, tiny disposable volume. // Ordinary test runs skip it; a skip is not disk-pressure qualification. func TestDisposableVolumeDiskFullRecovery(t *testing.T) { + root, total := DisposableTestVolume(t) + runDisposableVolumeRecovery(t, root, total) +} + +// DisposableTestVolume is exported only to the external journal test package. +func DisposableTestVolume(t *testing.T) (string, uint64) { + t.Helper() root := os.Getenv("WINUNITD_TEST_JOURNAL_VOLUME") if root == "" { t.Skip("requires an explicitly prepared disposable volume") @@ -39,6 +46,10 @@ func TestDisposableVolumeDiskFullRecovery(t *testing.T) { if windows.UTF16ToString(label[:]) != "winunitd-test" || total < 16<<20 || total > 128<<20 { t.Fatal("fixture requires a 16-128 MiB volume labeled winunitd-test") } + return root, total +} + +func runDisposableVolumeRecovery(t *testing.T, root string, total uint64) { dir, err := os.MkdirTemp(root, "winunitd-diskfull-") if err != nil { t.Fatal(err) diff --git a/internal/journal/export_test.go b/internal/journal/export_test.go index 5ec9cf3..177e6e7 100644 --- a/internal/journal/export_test.go +++ b/internal/journal/export_test.go @@ -1,5 +1,61 @@ package journal +import ( + "io" + "sync" +) + +// OpenOperationalPressureTestStore prepares independently stalled readers. +// ArmOperationalPressureFault adds the write fault after manager admission. +func OpenOperationalPressureTestStore(dir string, onScan func()) (*Store, error) { + s, err := Open(dir) + if err != nil { + return nil, err + } + if err = s.append(Entry{Unit: "reader.service", Message: "before fault"}); err == nil { + err = s.syncUnit("reader.service") + } + if err != nil { + _ = s.Close() + return nil, err + } + s.onScan = onScan + return s, nil +} + +func (s *Store) ArmOperationalPressureFault(onWrite func(), nativeDisk bool) (func(), error) { + const name = "pressure-0.service" + if err := s.append(Entry{Unit: name, Message: "before fault"}); err != nil { + return nil, err + } + if err := s.syncUnit(name); err != nil { + return nil, err + } + u := s.fileExisting(name) + u.mu.Lock() + if nativeDisk { + u.w = &recordBuffer{writer: &pressureWriteGate{writer: u.f, enter: onWrite}} + u.mu.Unlock() + return func() {}, nil + } + w := &recoveringWriter{file: u.f, remaining: 37} + w.fail.Store(true) + u.w = &recordBuffer{writer: &pressureWriteGate{writer: w, enter: onWrite}} + u.mu.Unlock() + return func() { w.fail.Store(false) }, nil +} + +type pressureWriteGate struct { + writer io.Writer + enter func() + once sync.Once +} + +func (w *pressureWriteGate) Write(p []byte) (int, error) { + w.once.Do(w.enter) + return w.writer.Write(p) +} + func OpenRetentionTestStore(dir string, maxFiles int) (*Store, error) { s, err := Open(dir) if err == nil { diff --git a/internal/journal/manager_pressure_windows_test.go b/internal/journal/manager_pressure_windows_test.go index 132cdcb..7bbebb7 100644 --- a/internal/journal/manager_pressure_windows_test.go +++ b/internal/journal/manager_pressure_windows_test.go @@ -28,6 +28,24 @@ func TestManagerPressureChild(t *testing.T) { if mode == "" { t.Skip("isolated child helper") } + if mode == "crash" { + if err := os.WriteFile(filepath.Join(root, fmt.Sprintf("crash-%d", os.Getpid())), []byte("exit 7"), 0600); err != nil { + os.Exit(6) + } + os.Exit(7) + } + if os.Getenv("WINUNITD_PRESSURE_GATE") == "1" { + deadline := time.Now().Add(20 * time.Second) + for { + if _, err := os.Stat(filepath.Join(root, "output-go")); err == nil { + break + } + if time.Now().After(deadline) { + os.Exit(8) + } + time.Sleep(10 * time.Millisecond) + } + } if mode == "quiet" { if _, err := io.WriteString(os.Stdout, "quiet manager pressure\n"); err != nil { os.Exit(2) @@ -147,6 +165,10 @@ func TestManagerAggregateCapturePressure(t *testing.T) { } func runManagerPressureCycle(t *testing.T) pressureMemory { + return runManagerPressureScenario(t, false, false) +} + +func runManagerPressureScenario(t *testing.T, combined, nativeDisk bool) pressureMemory { base := t.TempDir() if err := os.MkdirAll(filepath.Join(base, "units"), 0700); err != nil { t.Fatal(err) @@ -165,6 +187,9 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { name, mode = "pressure-quiet.service", "quiet" } body := fmt.Sprintf("[Service]\nExecStart=%s\nExecStartArg=-test.run=^TestManagerPressureChild$\nEnvironment=WINUNITD_PRESSURE_MODE=%s\nEnvironment=\"WINUNITD_PRESSURE_ROOT=%s\"\nEnvironment=WINUNITD_PRESSURE_NAME=%s\nTimeoutStartSec=20s\nTimeoutStopSec=1s\n", exe, mode, base, name) + if combined { + body += "Environment=WINUNITD_PRESSURE_GATE=1\n" + } if err := os.WriteFile(filepath.Join(base, "units", name), []byte(body), 0600); err != nil { t.Fatal(err) } @@ -173,10 +198,25 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { blocked, release := make(chan struct{}), make(chan struct{}) var entered, released sync.Once unblock := func() { released.Do(func() { close(release) }) } + var operational *operationalPressure + if combined { + operational = prepareOperationalPressure(t, base, exe) + if nativeDisk { + operational.prepareVolume(t) + } + } var store *journal.Store + stall := func() { entered.Do(func() { close(blocked); <-release }) } m, err := manager.New(manager.Config{BaseDir: base, JournalOpen: func(dir string) (*journal.Store, error) { var openErr error - store, openErr = journal.OpenPressureTestStore(dir, func() { entered.Do(func() { close(blocked); <-release }) }) + if operational != nil { + if operational.journalDir != "" { + dir = operational.journalDir + } + store, openErr = journal.OpenOperationalPressureTestStore(dir, operational.scan) + } else { + store, openErr = journal.OpenPressureTestStore(dir, stall) + } return store, openErr }}) if err != nil { @@ -184,6 +224,9 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { } t.Cleanup(func() { unblock() + if operational != nil { + operational.release() + } ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() if err := m.Shutdown(ctx); err != nil { @@ -201,6 +244,9 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { if err != nil { t.Fatal(err) } + if operational != nil { + operational.startReaders(t, store) + } peak := baseline var maxStatus time.Duration observe := func() { @@ -236,6 +282,18 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { t.Fatal(err) } } + if operational != nil { + operational.repair, err = store.ArmOperationalPressureFault(stall, nativeDisk) + if err != nil { + t.Fatal(err) + } + if nativeDisk { + operational.fillVolume(t) + } + if err := os.WriteFile(filepath.Join(base, "output-go"), []byte("go"), 0600); err != nil { + t.Fatal(err) + } + } wait(func() bool { for _, name := range names[:5] { if _, err := os.Stat(filepath.Join(base, name+".ready")); err != nil { @@ -244,11 +302,14 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { } return true }) - select { - case <-blocked: - default: - t.Fatal("storage did not stall") - } + wait(func() bool { + select { + case <-blocked: + return true + default: + return false + } + }) quiet := names[5] if _, err := m.Start(context.Background(), quiet); err != nil { t.Fatal(err) @@ -270,6 +331,9 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { if dropped == 0 || store.CaptureStats(quiet).DroppedRecords != 0 { t.Fatal("loss accounting or quiet fairness failed") } + if operational != nil { + operational.burstAndFail(t, m, wait) + } stops := make(chan error, len(names)) stopStart := time.Now() for _, name := range names { @@ -293,6 +357,10 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { t.Fatalf("manager resource budget: baseline=%+v peak=%+v", baseline, peak) } unblock() + if operational != nil { + wait(func() bool { return store.CaptureStats(names[0]).StorageErrors > 0 }) + operational.release() + } // Releasing storage does not synchronously persist the queued backlog. // Each stop retains its one-second budget; retry retained cleanup within a // separate bounded recovery phase instead of assuming one retry can drain it. @@ -312,7 +380,25 @@ func runManagerPressureCycle(t *testing.T) pressureMemory { } } recoveryDuration := time.Since(recoveryStart) + if operational != nil { + operational.verifyRecovery(t, store, m, wait) + } logs, err := m.Logs(protocol.LogsParams{Unit: quiet}) + if nativeDisk && err == nil && len(logs.Entries) == 0 { + if store.CaptureStats(quiet).DroppedRecords == 0 || store.CaptureStats(quiet).StorageErrors == 0 { + t.Fatal("native disk-full loss was not reported for quiet output") + } + if _, err := m.Start(context.Background(), quiet); err != nil { + t.Fatal(err) + } + wait(func() bool { + logs, err = m.Logs(protocol.LogsParams{Unit: quiet}) + return err == nil && len(logs.Entries) == 1 + }) + if _, err := m.Stop(quiet); err != nil { + t.Fatal(err) + } + } if err != nil || len(logs.Entries) != 1 || logs.Entries[0].Message != "quiet manager pressure" { t.Fatalf("quiet output after recovery: %+v, %v", logs, err) } diff --git a/internal/journal/operational_pressure_windows_test.go b/internal/journal/operational_pressure_windows_test.go new file mode 100644 index 0000000..814dd55 --- /dev/null +++ b/internal/journal/operational_pressure_windows_test.go @@ -0,0 +1,237 @@ +//go:build windows + +package journal_test + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/PLN/winunitd/internal/core" + "github.com/PLN/winunitd/internal/journal" + "github.com/PLN/winunitd/internal/manager" + "golang.org/x/sys/windows" +) + +func TestManagerCombinedOperationalPressure(t *testing.T) { + runOperationalPressure(t, false) +} + +// This lane must run on a separately prepared tiny lab volume. Ordinary CI +// still runs the combined injected short-write/disk-full lane above. +func TestManagerCombinedDisposableDiskPressure(t *testing.T) { + journal.DisposableTestVolume(t) + runOperationalPressure(t, true) +} + +func runOperationalPressure(t *testing.T, nativeDisk bool) { + if os.Getenv("WINUNITD_OPERATIONAL_MANAGER") != "1" { + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 150*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, exe, "-test.run=^"+t.Name()+"$", "-test.v") + cmd.Env = append(os.Environ(), "WINUNITD_OPERATIONAL_MANAGER=1", "GOMAXPROCS=2") + output, err := cmd.CombinedOutput() + t.Logf("isolated combined pressure: %s", output) + if err != nil { + t.Fatalf("combined pressure process: %v", err) + } + return + } + var after [3]pressureMemory + for i := range after { + if !t.Run(fmt.Sprintf("cycle-%d", i+1), func(t *testing.T) { + after[i] = runManagerPressureScenario(t, true, nativeDisk) + }) { + return + } + } + if after[2].Handles > after[0].Handles+32 || after[2].Threads > after[0].Threads+4 { + t.Fatalf("combined workload resource growth: first=%+v final=%+v", after[0], after[2]) + } +} + +type operationalPressure struct { + base, watch string + journalDir string + entered chan struct{} + readers chan struct{} + once sync.Once + repair func() +} + +func (p *operationalPressure) prepareVolume(t *testing.T) { + root, _ := journal.DisposableTestVolume(t) + dir, err := os.MkdirTemp(root, "combined-pressure-") + if err != nil { + t.Fatal(err) + } + p.journalDir = dir + t.Cleanup(func() { + if err := os.RemoveAll(dir); err != nil { + t.Error(err) + } + }) +} + +func (p *operationalPressure) fillVolume(t *testing.T) { + root, total := journal.DisposableTestVolume(t) + filler, err := os.CreateTemp(root, "combined-filler-") + if err != nil { + t.Fatal(err) + } + var once sync.Once + p.repair = func() { + once.Do(func() { + if err := filler.Close(); err != nil { + t.Error(err) + } + if err := os.Remove(filler.Name()); err != nil { + t.Error(err) + } + }) + } + t.Cleanup(p.repair) + chunk := bytes.Repeat([]byte{0xa5}, 4<<10) + var written uint64 + for { + n, err := filler.Write(chunk) + written += uint64(n) + if errors.Is(err, windows.ERROR_DISK_FULL) || errors.Is(err, windows.ERROR_HANDLE_DISK_FULL) { + break + } + if err != nil || n != len(chunk) || written > total { + t.Fatalf("native filler: bytes=%d error=%v", written, err) + } + } + t.Logf("combined native disk-full: volume bytes=%d filler bytes=%d", total, written) +} + +func prepareOperationalPressure(t *testing.T, base, exe string) *operationalPressure { + p := &operationalPressure{base: base, watch: filepath.Join(base, "watch"), entered: make(chan struct{}, 4), readers: make(chan struct{})} + if err := os.Mkdir(p.watch, 0700); err != nil { + t.Fatal(err) + } + units := map[string]string{ + "crash.service": fmt.Sprintf("[Unit]\nFormatVersion=2\nStartLimitIntervalSec=60s\nStartLimitBurst=4\n[Service]\nExecStart=%s\nExecStartArg=-test.run=^TestManagerPressureChild$\nEnvironment=WINUNITD_PRESSURE_MODE=crash\nEnvironment=\"WINUNITD_PRESSURE_ROOT=%s\"\nRestart=on-failure\nRestartSec=100ms\nTimeoutStartSec=10s\nTimeoutStopSec=1s\n", exe, base), + "crash.path": fmt.Sprintf("[Path]\nPathChanged=%s\n", p.watch), + } + for name, body := range units { + if err := os.WriteFile(filepath.Join(base, "units", name), []byte(body), 0600); err != nil { + t.Fatal(err) + } + } + return p +} + +func (p *operationalPressure) scan() { + select { + case <-p.readers: + return + default: + } + p.entered <- struct{}{} + <-p.readers +} + +func (p *operationalPressure) release() { + if p.repair != nil { + p.repair() + } + p.once.Do(func() { close(p.readers) }) +} + +func (p *operationalPressure) startReaders(t *testing.T, store *journal.Store) { + for i := 0; i < 4; i++ { + ctx, cancel := context.WithCancel(context.Background()) + finished := make(chan error, 1) + go func() { + _, _, _, err := store.QueryPageContext(ctx, "reader.service", time.Time{}, "original", 1024) + finished <- err + }() + select { + case <-p.entered: + case <-time.After(5 * time.Second): + cancel() + t.Fatal("slow reader was not admitted") + } + cancel() + select { + case err := <-finished: + if !errors.Is(err, context.Canceled) { + t.Fatalf("reader cancellation: %v", err) + } + case <-time.After(time.Second): + t.Fatal("slow reader blocked caller cancellation") + } + } + for i := 0; i < 128; i++ { + if _, cursor, _, err := store.QueryPageContext(context.Background(), "reader.service", time.Time{}, "original", 1024); !errors.Is(err, journal.ErrQueryBusy) || cursor != "original" { + t.Fatalf("slow reader overload lost admission or cursor: %q %v", cursor, err) + } + } +} + +func (p *operationalPressure) burstAndFail(t *testing.T, m *manager.Manager, wait func(func() bool)) { + if _, err := m.Start(context.Background(), "crash.path"); err != nil { + t.Fatal(err) + } + for i := 0; i < 128; i++ { + if err := os.WriteFile(filepath.Join(p.watch, fmt.Sprintf("burst-%03d", i)), []byte("trigger"), 0600); err != nil { + t.Fatal(err) + } + } + wait(func() bool { + status, err := m.Status("crash.service") + return err == nil && status.Unit.Reason == core.ReasonStartLimit && status.Unit.MainPID == 0 && len(status.Unit.PendingCleanup) == 0 + }) + if _, err := m.Stop("crash.path"); err != nil { + t.Fatal(err) + } + files, err := filepath.Glob(filepath.Join(p.base, "crash-*")) + if err != nil || len(files) != 4 { + t.Fatalf("native failure limit: executions=%d error=%v", len(files), err) + } + if _, err := m.Stop("crash.service"); err != nil { + t.Fatal(err) + } + t.Log("combined faults: 128 native path writes, four exit-7 processes, four retained slow scans, 128 rejected excess readers") +} + +func (p *operationalPressure) verifyRecovery(t *testing.T, store *journal.Store, m *manager.Manager, wait func(func() bool)) { + wait(func() bool { + entries, _, _, err := store.QueryPageContext(context.Background(), "reader.service", time.Time{}, "", 1024) + if errors.Is(err, journal.ErrQueryBusy) { + return false + } + if err != nil || len(entries) != 1 || entries[0].Message != "before fault" { + t.Fatalf("reader recovery: %d entries, %v", len(entries), err) + } + return true + }) + entries, err := store.Read("pressure-0.service") + if err != nil || len(entries) < 2 || entries[0].Message != "before fault" { + t.Fatalf("partial disk-full write recovery: %d entries, %v", len(entries), err) + } + // The first interrupted fragment must resume at its unwritten suffix. + // Duplicate prefixes, dropped suffixes and joined records alter this payload. + if entries[1].Message != strings.Repeat("x", journal.MaxCaptureFragment) { + t.Fatal("interrupted record was not recovered intact") + } + status, err := m.Status("crash.service") + if err != nil || status.Unit.MainPID != 0 || status.Unit.TerminationUncertain { + t.Fatalf("stopped trigger companion resurrected: %+v %v", status, err) + } + t.Logf("combined recovery: disk-full errors=%d, recovered first fragment bytes=%d", store.CaptureStats("pressure-0.service").StorageErrors, len(entries[1].Message)) +} From 5c14598d7250862ba1117ca30588633ef343a9f1 Mon Sep 17 00:00:00 2001 From: PLN <78331+PLN@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:25:28 +0200 Subject: [PATCH 2/3] test: verify native capacity restoration before timing recovery --- .../operational_pressure_windows_test.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/internal/journal/operational_pressure_windows_test.go b/internal/journal/operational_pressure_windows_test.go index 814dd55..a386244 100644 --- a/internal/journal/operational_pressure_windows_test.go +++ b/internal/journal/operational_pressure_windows_test.go @@ -100,6 +100,31 @@ func (p *operationalPressure) fillVolume(t *testing.T) { if err := os.Remove(filler.Name()); err != nil { t.Error(err) } + // Deletion can remain pending while Windows still owns an open + // handle (for example an on-access scanner). Establish restored + // capacity before measuring the manager's recovery deadline. + native, err := windows.UTF16PtrFromString(root) + if err != nil { + t.Error(err) + return + } + started := time.Now() + for { + var available, size, free uint64 + if err := windows.GetDiskFreeSpaceEx(native, &available, &size, &free); err != nil { + t.Error(err) + return + } + if available >= total/2 { + t.Logf("native capacity restored: available=%d elapsed=%v", available, time.Since(started)) + return + } + if time.Since(started) > 30*time.Second { + t.Errorf("fixture capacity did not return after filler deletion: available=%d", available) + return + } + time.Sleep(10 * time.Millisecond) + } }) } t.Cleanup(p.repair) From 3605c2c7978c4b10076558a76e5453fd051e60bc Mon Sep 17 00:00:00 2001 From: PLN <78331+PLN@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:32:50 +0200 Subject: [PATCH 3/3] test: reclaim filler allocation before disk recovery checks --- internal/journal/diskfull_windows_test.go | 8 ++++++++ internal/journal/manager_pressure_windows_test.go | 6 +++--- .../journal/operational_pressure_windows_test.go | 12 +++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/journal/diskfull_windows_test.go b/internal/journal/diskfull_windows_test.go index c513615..e46da1c 100644 --- a/internal/journal/diskfull_windows_test.go +++ b/internal/journal/diskfull_windows_test.go @@ -103,6 +103,14 @@ func runDisposableVolumeRecovery(t *testing.T, root string, total uint64) { if stats := s.CaptureStats(name); stats.DroppedRecords != 1 || stats.StorageErrors == 0 { t.Fatalf("missing pressure accounting: %+v", stats) } + // Deletion alone may defer space reclamation while Windows retains another + // handle. Truncate and flush our own file before measuring recovery. + if err := filler.Truncate(0); err != nil { + t.Fatal(err) + } + if err := filler.Sync(); err != nil { + t.Fatal(err) + } if err := filler.Close(); err != nil { t.Fatal(err) } diff --git a/internal/journal/manager_pressure_windows_test.go b/internal/journal/manager_pressure_windows_test.go index 7bbebb7..69593cb 100644 --- a/internal/journal/manager_pressure_windows_test.go +++ b/internal/journal/manager_pressure_windows_test.go @@ -353,9 +353,6 @@ func runManagerPressureScenario(t *testing.T, combined, nativeDisk bool) pressur if maxStatus > time.Second || stopDuration > 5*time.Second { t.Fatalf("control stalled: status=%v stop=%v", maxStatus, stopDuration) } - if peak.HeapBytes > baseline.HeapBytes+(128<<20) || peak.PrivateBytes > baseline.PrivateBytes+pressurePrivateBudget || peak.Goroutines > baseline.Goroutines+256 || peak.Handles > baseline.Handles+256 || peak.Threads > baseline.Threads+64 { - t.Fatalf("manager resource budget: baseline=%+v peak=%+v", baseline, peak) - } unblock() if operational != nil { wait(func() bool { return store.CaptureStats(names[0]).StorageErrors > 0 }) @@ -424,6 +421,9 @@ func runManagerPressureScenario(t *testing.T, combined, nativeDisk bool) pressur }{baseline, peak, after, queue.Bytes, queue.Records, dropped, float64(maxStatus) / float64(time.Millisecond), float64(stopDuration) / float64(time.Millisecond), float64(recoveryDuration) / float64(time.Millisecond)} encoded, _ := json.Marshal(result) t.Logf("manager pressure measurements: %s", encoded) + if peak.HeapBytes > baseline.HeapBytes+(128<<20) || peak.PrivateBytes > baseline.PrivateBytes+pressurePrivateBudget || peak.Goroutines > baseline.Goroutines+256 || peak.Handles > baseline.Handles+256 || peak.Threads > baseline.Threads+64 { + t.Fatalf("manager resource budget: baseline=%+v peak=%+v", baseline, peak) + } if after.Goroutines > baseline.Goroutines+16 { t.Fatalf("cleanup did not release workers: baseline=%+v after=%+v", baseline, after) } diff --git a/internal/journal/operational_pressure_windows_test.go b/internal/journal/operational_pressure_windows_test.go index a386244..9e8f20b 100644 --- a/internal/journal/operational_pressure_windows_test.go +++ b/internal/journal/operational_pressure_windows_test.go @@ -94,15 +94,21 @@ func (p *operationalPressure) fillVolume(t *testing.T) { var once sync.Once p.repair = func() { once.Do(func() { + // Release allocation through the owned handle. Delete alone may + // defer reclamation until another Windows handle closes. + if err := filler.Truncate(0); err != nil { + t.Error(err) + } + if err := filler.Sync(); err != nil { + t.Error(err) + } if err := filler.Close(); err != nil { t.Error(err) } if err := os.Remove(filler.Name()); err != nil { t.Error(err) } - // Deletion can remain pending while Windows still owns an open - // handle (for example an on-access scanner). Establish restored - // capacity before measuring the manager's recovery deadline. + // Establish restored capacity before measuring manager recovery. native, err := windows.UTF16PtrFromString(root) if err != nil { t.Error(err)