This commit is contained in:
parent
ed7236d491
commit
6cd9860681
|
|
@ -9,9 +9,15 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
osWriteFile = os.WriteFile
|
||||
osRename = os.Rename
|
||||
)
|
||||
|
||||
type VideoRequest struct {
|
||||
URL string `json:"url"`
|
||||
OutDir string `json:"out_dir"`
|
||||
|
|
@ -169,12 +175,7 @@ func (q *Queue) persistLocked() error {
|
|||
return err
|
||||
}
|
||||
|
||||
tmp := q.persist + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(tmp, q.persist)
|
||||
return writeFileAtomic(q.persist, data)
|
||||
}
|
||||
|
||||
var idCounter uint64
|
||||
|
|
@ -183,3 +184,24 @@ func generateID() string {
|
|||
count := atomic.AddUint64(&idCounter, 1)
|
||||
return fmt.Sprintf("%d-%d", time.Now().UTC().UnixNano(), count)
|
||||
}
|
||||
|
||||
func writeFileAtomic(path string, data []byte) error {
|
||||
tmp := path + ".tmp"
|
||||
if err := osWriteFile(tmp, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := osRename(tmp, path); err != nil {
|
||||
if errors.Is(err, syscall.EBUSY) {
|
||||
if writeErr := osWriteFile(path, data, 0o644); writeErr != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return writeErr
|
||||
}
|
||||
return os.Remove(tmp)
|
||||
}
|
||||
_ = os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -90,3 +91,48 @@ func TestQueuePersistence(t *testing.T) {
|
|||
t.Fatalf("expected queue file removed after draining, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFileAtomicFallback(t *testing.T) {
|
||||
// Simulate EBUSY by replacing os.Rename temporarily.
|
||||
origRename := osRename
|
||||
origWriteFile := osWriteFile
|
||||
defer func() {
|
||||
osRename = origRename
|
||||
osWriteFile = origWriteFile
|
||||
}()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "queue.json")
|
||||
odyssey := filepath.Join(tmpDir, "queue.json.tmp")
|
||||
|
||||
osWriteFile = func(name string, data []byte, perm os.FileMode) error {
|
||||
if name == path {
|
||||
if err := os.WriteFile(name, data, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return os.WriteFile(name, data, perm)
|
||||
}
|
||||
|
||||
osRename = func(oldpath, newpath string) error {
|
||||
return syscall.EBUSY
|
||||
}
|
||||
|
||||
if err := writeFileAtomic(path, []byte("{}")); err != nil {
|
||||
t.Fatalf("writeFileAtomic: %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read path: %v", err)
|
||||
}
|
||||
|
||||
if string(data) != "{}" {
|
||||
t.Fatalf("expected file contents '{}', got %q", data)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(odyssey); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected tmp removed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue