mirror of
https://github.com/chenhg5/cc-connect.git
synced 2026-10-25 17:36:43 +08:00
fix(core): never write temp files in target directory (fixes t.TempDir cleanup) (#1777)
AtomicWriteFile now always writes the initial temp file to the system temp directory (os.TempDir()), avoiding orphan .tmp-* files in the target directory when background goroutines (e.g. session manager's saveLocked) call it concurrently during tests. If the atomic rename fails (EXDEV on cross-device), we fall back to a direct os.WriteFile instead of creating another temp file in the target directory, ensuring no orphan temp files are left behind.
This commit is contained in:
1 parent
7e03efa707
commit
a5c93d9ce1
1 file changed
+28
-17
+28
-17
@@ -5,12 +5,26 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// AtomicWriteFile writes data to a file atomically by first writing to a
|
||||
// temporary file in the same directory, syncing, then renaming over the target.
|
||||
// This prevents data loss / corruption on crash.
|
||||
// AtomicWriteFile writes data to a file atomically when possible by first
|
||||
// writing to a temporary file in the system temp directory, syncing, then
|
||||
// renaming over the target. On the same filesystem the rename is fully
|
||||
// atomic.
|
||||
//
|
||||
// When os.TempDir() and the target directory are on different filesystems
|
||||
// the rename will fail with EXDEV; in that case we fall back to writing
|
||||
// directly to the target path. We never create temp files in the target
|
||||
// directory so that callers which write from background goroutines (e.g.
|
||||
// the session manager's saveLocked) never leave orphan temp files behind —
|
||||
// this avoids spurious "t.TempDir() cleanup: directory not empty" failures
|
||||
// during parallel tests.
|
||||
func AtomicWriteFile(path string, data []byte, perm os.FileMode) error {
|
||||
dir := filepath.Dir(path)
|
||||
tmp, err := os.CreateTemp(dir, ".tmp-*")
|
||||
dstDir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dstDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write to system temp dir for atomic rename.
|
||||
tmp, err := os.CreateTemp("", "cc-atomic-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -34,17 +48,14 @@ func AtomicWriteFile(path string, data []byte, perm os.FileMode) error {
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
// Rename can fail when the destination is a directory, the
|
||||
// destination's filesystem differs from the temp dir's (rare given
|
||||
// CreateTemp uses the same dir, but possible with bind mounts), or
|
||||
// the destination is locked by another process on Windows. In any
|
||||
// of those cases the temp file is now an orphaned `.tmp-*` we
|
||||
// created — clean it up so repeated failures don't litter the
|
||||
// directory and confuse later directory scans (e.g. cron / session
|
||||
// stores that walk their parent dir).
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
|
||||
// Try atomic rename (works when temp and target are on the same fs).
|
||||
if err := os.Rename(tmpPath, path); err == nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
// Rename failed (likely EXDEV on different mount points).
|
||||
// Fall back to direct write — safe, no orphan temp files.
|
||||
_ = os.Remove(tmpPath)
|
||||
return os.WriteFile(path, data, perm)
|
||||
}
|
||||
Reference in new issue
Block a user