mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Merge pull request #2046 from rcourtman/maintainer/20260911T061346Z
Reduce unnecessary alert-state disk writes
This commit is contained in:
@@ -2923,3 +2923,16 @@ notification enqueue atomic with alert resolution.
|
||||
before the former 180-minute deadline, plus current payload/routing selection,
|
||||
copy isolation, snooze and invalid-level rejection. These deterministic component tests
|
||||
establish neither installed delivery timing nor the cause of a delayed email.
|
||||
|
||||
### Unchanged intent-pending checkpoints
|
||||
|
||||
The serialized active-alert checkpoint also compares the complete, sorted
|
||||
pending-policy JSON against a bounded regular file with mode 0600 before
|
||||
replacement. Empty and nonempty unchanged pending state leave the file intact;
|
||||
changed or cleared state, missing/stale content, unsafe destinations and failed
|
||||
writes retain replacement/retry semantics. This does not change intent timing,
|
||||
retention, alert freshness or the existing persistence durability contract.
|
||||
`TestIntentCheckpoint*` exercises the complete JSON-only checkpoint without
|
||||
starting database workers, including restart loading and Unix hardening. The
|
||||
active mirror's unchanged fast path alone does not cover this companion file.
|
||||
This is a narrow write contributor, not aggregate installed-write acceptance.
|
||||
|
||||
@@ -21322,3 +21322,118 @@ func TestActiveMirrorUnchangedRepairsDirectoryPermissions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Use the complete JSON-only checkpoint, not just the active-alert mirror:
|
||||
// unchanged pending-policy state must not undo the mirror's write reduction.
|
||||
func TestIntentCheckpointUnchanged(t *testing.T) {
|
||||
m := &Manager{alertsDir: t.TempDir()}
|
||||
save := func() {
|
||||
t.Helper()
|
||||
if err := m.SaveActiveAlerts(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
save()
|
||||
path := filepath.Join(m.alertsDir, intentPendingFileName)
|
||||
first, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
save()
|
||||
after, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !os.SameFile(first, after) {
|
||||
t.Fatal("unchanged empty pending state was replaced")
|
||||
}
|
||||
}
|
||||
after, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !os.SameFile(first, after) {
|
||||
t.Fatal("unchanged empty pending state was replaced")
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
m.intentPending = map[string]IntentPendingState{"a": {TrackingKey: "a", ResourceID: "r", Signal: "offline", FirstMatchedAt: now, LastObservedAt: now}}
|
||||
save()
|
||||
restored := &Manager{alertsDir: m.alertsDir}
|
||||
if err := restored.loadIntentPendingNoLock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(restored.intentPending) != 1 {
|
||||
t.Fatal("changed pending state was not persisted")
|
||||
}
|
||||
first, err = os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
save()
|
||||
after, err = os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !os.SameFile(first, after) {
|
||||
t.Fatal("unchanged nonempty pending state was replaced")
|
||||
}
|
||||
// Compare actual bytes, not cached metadata: same-size stale state is repaired.
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stale := append([]byte(nil), data...)
|
||||
stale[0] = ' '
|
||||
if err := os.WriteFile(path, stale, alertsFilePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
save()
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != string(data) {
|
||||
t.Fatal("stale state survived checkpoint")
|
||||
}
|
||||
if err := os.Rename(path, path+".saved"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
save()
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.intentPending = nil
|
||||
save()
|
||||
got, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "[]" {
|
||||
t.Fatal("cleared pending state was not persisted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentCheckpointRetriesFailedWrite(t *testing.T) {
|
||||
m := &Manager{alertsDir: t.TempDir()}
|
||||
path := filepath.Join(m.alertsDir, intentPendingFileName)
|
||||
if err := os.Mkdir(path, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.SaveActiveAlerts(); err == nil {
|
||||
t.Fatal("expected obstructed checkpoint failure")
|
||||
}
|
||||
if err := os.Rename(path, path+".obstruction"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.SaveActiveAlerts(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "[]" {
|
||||
t.Fatalf("retry wrote %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//go:build !windows
|
||||
|
||||
package alerts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntentCheckpointRepairsUnsafeDestination(t *testing.T) {
|
||||
for _, kind := range []string{"permissions", "symlink"} {
|
||||
t.Run(kind, func(t *testing.T) {
|
||||
m := &Manager{alertsDir: t.TempDir()}
|
||||
save := func() {
|
||||
t.Helper()
|
||||
if err := m.SaveActiveAlerts(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
save()
|
||||
path := filepath.Join(m.alertsDir, intentPendingFileName)
|
||||
if kind == "permissions" {
|
||||
if err := os.Chmod(path, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err := os.Rename(path, path+".target"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(path+".target", path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
save()
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !info.Mode().IsRegular() || info.Mode().Perm() != alertsFilePerm {
|
||||
t.Fatalf("unsafe state accepted: %v", info.Mode())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package alerts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -30,6 +32,19 @@ func (m *Manager) saveIntentPendingSnapshot() error {
|
||||
if err := os.MkdirAll(alertsDir, alertsDirPerm); err != nil {
|
||||
return fmt.Errorf("create alert intent state directory: %w", err)
|
||||
}
|
||||
finalPath := filepath.Join(alertsDir, intentPendingFileName)
|
||||
// SaveActiveAlerts serializes snapshots and writes. Check the actual bounded
|
||||
// file bytes so missing, stale and failed checkpoints remain retryable.
|
||||
if info, err := os.Lstat(finalPath); err == nil && info.Mode().IsRegular() &&
|
||||
info.Mode().Perm() == alertsFilePerm && info.Size() == int64(len(data)) {
|
||||
if file, err := os.Open(finalPath); err == nil {
|
||||
existing, readErr := io.ReadAll(io.LimitReader(file, int64(len(data))+1))
|
||||
closeErr := file.Close()
|
||||
if readErr == nil && closeErr == nil && bytes.Equal(existing, data) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
tmp, err := os.CreateTemp(alertsDir, "intent-pending-*.json.tmp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create alert intent state temp file: %w", err)
|
||||
@@ -51,7 +66,6 @@ func (m *Manager) saveIntentPendingSnapshot() error {
|
||||
if err := tmp.Close(); err != nil {
|
||||
return fmt.Errorf("close alert intent state temp file: %w", err)
|
||||
}
|
||||
finalPath := filepath.Join(alertsDir, intentPendingFileName)
|
||||
if err := os.Rename(tmpName, finalPath); err != nil {
|
||||
return fmt.Errorf("persist alert intent pending state: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user