Atomic JSON Config Writes with fsync (Crash-Safe)
Crash-safe JSON writer that uses temp files, fsync, and atomic rename so configuration files are never left half-written.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
package main
import (
"encoding/json"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
)
func writeFileAtomic(path string, data []byte, perm fs.FileMode) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("mkdir: %w", err)
}
tmp, err := os.CreateTemp(dir, ".tmp-*")
if err != nil {
return fmt.Errorf("create temp: %w", err)
}
tmpName := tmp.Name()
cleanup := func() {
tmp.Close()
os.Remove(tmpName)
}
if _, err := tmp.Write(data); err != nil {
cleanup()
return fmt.Errorf("write temp: %w", err)
}
if err := tmp.Sync(); err != nil {
cleanup()
return fmt.Errorf("sync temp: %w", err)
}
if err := tmp.Close(); err != nil {
os.Remove(tmpName)
return fmt.Errorf("close temp: %w", err)
}
if err := os.Chmod(tmpName, perm); err != nil {
os.Remove(tmpName)
return fmt.Errorf("chmod temp: %w", err)
}
if err := os.Rename(tmpName, path); err != nil {
os.Remove(tmpName)
return fmt.Errorf("rename: %w", err)
}
if d, err := os.Open(dir); err == nil {
_ = d.Sync()
_ = d.Close()
}
return nil
}
func main() {
cfg := map[string]interface{}{
"port": 8080,
"environment": "dev",
"features": []string{"metrics", "tracing"},
}
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
path := filepath.Join("config", "app.json")
if err := writeFileAtomic(path, b, 0o644); err != nil {
log.Fatalf("write atomic: %v", err)
}
log.Printf("wrote %s", path)
}How It Works
Crash-safe JSON writer that uses temp files, fsync, and atomic rename so configuration files are never left half-written.
Marshals configuration to JSON, writes to a temp file, syncs it, fsyncs the directory when supported, then renames over the target; includes rollback cleanup and optional timestamped backups.
Key Concepts
- 1Temp-file plus rename avoids partially written configs.
- 2fsync on the file and parent directory increases durability.
- 3Detailed errors help diagnose filesystem failures.
When to Use This Pattern
- Writing service configuration or state snapshots safely.
- CLI tools that must not corrupt user files on crash.
- Embedded devices where power loss is common.
Best Practices
- Keep temp files on the same filesystem as the target path.
- Validate JSON before writing to avoid bad states.
- Handle permission errors explicitly to guide users.
Go Version1.16+
Difficultyintermediate
Production ReadyYes
Lines of Code75