diff --git a/utils/cache/spread_fs.go b/utils/cache/spread_fs.go index 131f8a708..11801b324 100644 --- a/utils/cache/spread_fs.go +++ b/utils/cache/spread_fs.go @@ -128,12 +128,7 @@ func (sfs *spreadFS) Create(name string) (stream.File, error) { if err != nil { return nil, err } - // Unlink instead of truncating: an older stream may still be serving readers from - // this path, and shrinking the file under them spins them at a premature EOF. - if err := os.Remove(name); err != nil && !os.IsNotExist(err) { - return nil, err - } - return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + return createDataFile(name) } func (sfs *spreadFS) Open(name string) (stream.File, error) { diff --git a/utils/cache/spread_fs_test.go b/utils/cache/spread_fs_test.go index ae2d86f05..44b14db2b 100644 --- a/utils/cache/spread_fs_test.go +++ b/utils/cache/spread_fs_test.go @@ -4,6 +4,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "github.com/djherbis/stream" @@ -45,6 +46,9 @@ var _ = Describe("Spread FS", func() { It("leaves an already-open reader's bytes intact", func() { // A re-created entry must not shrink the file an older stream is still // serving: its reader would spin forever at the premature EOF. + if runtime.GOOS == "windows" { + Skip("Windows cannot unlink a file with open handles, so Create reuses the inode") + } name := filepath.Join(rootDir, "aa", "bb", "data") s, err := stream.NewStream(name, fs) Expect(err).To(BeNil()) diff --git a/utils/cache/spread_fs_unix.go b/utils/cache/spread_fs_unix.go new file mode 100644 index 000000000..c7cb19cf8 --- /dev/null +++ b/utils/cache/spread_fs_unix.go @@ -0,0 +1,18 @@ +//go:build !windows + +package cache + +import ( + "os" + + "github.com/djherbis/stream" +) + +// createDataFile unlinks instead of truncating, so a re-created entry gets a fresh +// inode and readers still holding the old one see its full contents. +func createDataFile(name string) (stream.File, error) { + if err := os.Remove(name); err != nil && !os.IsNotExist(err) { + return nil, err + } + return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) +} diff --git a/utils/cache/spread_fs_windows.go b/utils/cache/spread_fs_windows.go new file mode 100644 index 000000000..ca4186d65 --- /dev/null +++ b/utils/cache/spread_fs_windows.go @@ -0,0 +1,15 @@ +//go:build windows + +package cache + +import ( + "os" + + "github.com/djherbis/stream" +) + +// createDataFile truncates in place: Windows refuses to unlink a file another handle +// has open, and failing the create would break every miss on a path with a live reader. +func createDataFile(name string) (stream.File, error) { + return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) +}