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
97 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|