fix(clients): фиксировать базу на диск через fsync

This commit is contained in:
samosvalishe
2026-09-21 13:43:13 +03:00
parent b621182ce4
commit ac43e63ef8
2 changed files with 34 additions and 4 deletions
+31 -1
View File
@@ -8,6 +8,7 @@ import (
"maps"
"net"
"os"
"path/filepath"
"sync"
"time"
)
@@ -142,10 +143,13 @@ func (db *DB) save(next Data) error {
}
tmpFile := db.path + ".tmp"
err = os.WriteFile(tmpFile, b, 0o600) // 0o600: файл содержит Client ID токены авторизации
err = writeSync(tmpFile, b)
if err == nil {
err = os.Rename(tmpFile, db.path)
}
if err == nil {
syncDir(filepath.Dir(db.path))
}
if err == nil {
// Авторизация меняется только после успешной замены файла.
db.data = next
@@ -157,6 +161,32 @@ func (db *DB) save(next Data) error {
return err
}
func writeSync(path string, b []byte) error {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) // 0o600: файл содержит Client ID токены авторизации
if err != nil {
return err
}
if _, err = f.Write(b); err == nil {
err = f.Sync()
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
_ = os.Remove(path)
}
return err
}
func syncDir(dir string) {
d, err := os.Open(dir)
if err != nil {
return
}
_ = d.Sync()
_ = d.Close()
}
// Тег режима едет хвостом той же записи: клиент до этого поля его не писал, а читатель
// брал ровно 1+len байт - лишний байт старый сервер молча пропускает.
const (
+3 -3
View File
@@ -14,11 +14,11 @@ func TestFailedSavePreservesAuthorization(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := db.Add("existing", "original"); err != nil {
if err = db.Add("existing", "original"); err != nil {
t.Fatal(err)
}
// Каталог вместо временного файла воспроизводит отказ записи без прав root.
if err := os.Mkdir(path+".tmp", 0o700); err != nil {
if err = os.Mkdir(path+".tmp", 0o700); err != nil {
t.Fatal(err)
}
operations := []struct {
@@ -31,7 +31,7 @@ func TestFailedSavePreservesAuthorization(t *testing.T) {
}
for _, op := range operations {
t.Run(op.name, func(t *testing.T) {
if err := op.run(); err == nil {
if runErr := op.run(); runErr == nil {
t.Fatal("save unexpectedly succeeded")
}
if !db.IsAuthorized("existing") || db.IsAuthorized("new") {