This commit is contained in:
parent
ed7236d491
commit
6cd9860681
|
|
@ -9,9 +9,15 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
osWriteFile = os.WriteFile
|
||||||
|
osRename = os.Rename
|
||||||
|
)
|
||||||
|
|
||||||
type VideoRequest struct {
|
type VideoRequest struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
OutDir string `json:"out_dir"`
|
OutDir string `json:"out_dir"`
|
||||||
|
|
@ -169,12 +175,7 @@ func (q *Queue) persistLocked() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := q.persist + ".tmp"
|
return writeFileAtomic(q.persist, data)
|
||||||
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Rename(tmp, q.persist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var idCounter uint64
|
var idCounter uint64
|
||||||
|
|
@ -183,3 +184,24 @@ func generateID() string {
|
||||||
count := atomic.AddUint64(&idCounter, 1)
|
count := atomic.AddUint64(&idCounter, 1)
|
||||||
return fmt.Sprintf("%d-%d", time.Now().UTC().UnixNano(), count)
|
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"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -90,3 +91,48 @@ func TestQueuePersistence(t *testing.T) {
|
||||||
t.Fatalf("expected queue file removed after draining, got %v", err)
|
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