Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

97 lines
3.0 KiB
Go

//go:build windows
package agents
import (
"os"
"path/filepath"
"sync"
"syscall"
"testing"
"time"
)
// openExclusiveNoDelete opens path with a share mode that omits
// FILE_SHARE_DELETE, reproducing the handle an editor's language server,
// antivirus, a search indexer, or Gortex's own file watcher holds while
// touching a file. MoveFileEx — and therefore os.Rename — cannot replace
// a destination held this way and fails with ERROR_SHARING_VIOLATION.
func openExclusiveNoDelete(t *testing.T, path string) syscall.Handle {
t.Helper()
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
t.Fatalf("utf16 %s: %v", path, err)
}
h, err := syscall.CreateFile(p,
syscall.GENERIC_READ,
syscall.FILE_SHARE_READ, // deliberately no FILE_SHARE_DELETE / WRITE
nil,
syscall.OPEN_EXISTING,
syscall.FILE_ATTRIBUTE_NORMAL,
0)
if err != nil {
t.Fatalf("CreateFile %s: %v", path, err)
}
return h
}
// TestAtomicWriteFileRetriesPastSharingViolation is the Windows regression
// test for the "The process cannot access the file because it is being
// used by another process" failures: when the destination is transiently
// held open without FILE_SHARE_DELETE, AtomicWriteFile must retry the
// rename and succeed once the holder releases the handle, instead of
// surfacing the spurious error.
//
// CI's Windows job is build-only (see .github/workflows/ci.yml), so this
// runs locally / on demand, not in the cross-platform test matrix.
func TestAtomicWriteFileRetriesPastSharingViolation(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "held.txt")
if err := os.WriteFile(path, []byte("old"), 0o644); err != nil {
t.Fatal(err)
}
h := openExclusiveNoDelete(t, path)
// Release the handle within the retry budget (~225ms) so a rename that
// first fails with a sharing violation later succeeds.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(40 * time.Millisecond)
_ = syscall.CloseHandle(h)
}()
if err := AtomicWriteFile(path, []byte("new"), 0o644); err != nil {
t.Fatalf("AtomicWriteFile should retry past a transient hold, got: %v", err)
}
wg.Wait()
if got, _ := os.ReadFile(path); string(got) != "new" {
t.Fatalf("content after retry: got %q want %q", got, "new")
}
}
// TestAtomicWriteFileFailsWhenHeldThroughout bounds the retry: a
// destination held open for longer than the whole retry budget must make
// AtomicWriteFile give up and return an error — not hang forever — and
// must leave the original file intact.
func TestAtomicWriteFileFailsWhenHeldThroughout(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "locked.txt")
if err := os.WriteFile(path, []byte("old"), 0o644); err != nil {
t.Fatal(err)
}
h := openExclusiveNoDelete(t, path)
defer syscall.CloseHandle(h)
if err := AtomicWriteFile(path, []byte("new"), 0o644); err == nil {
t.Fatal("expected AtomicWriteFile to fail while the file is held open throughout")
}
if got, _ := os.ReadFile(path); string(got) != "old" {
t.Fatalf("a failed write must not corrupt the destination: got %q", got)
}
}