feat: reclaim terminal commands atomically

This commit is contained in:
2026-08-31 09:56:11 +00:00
parent 611100f67c
commit af9041e1ca
4 changed files with 582 additions and 2 deletions
@@ -3,6 +3,7 @@
package store_test
import (
"bytes"
"context"
"crypto/sha256"
"database/sql"
@@ -27,7 +28,7 @@ func TestRealSQLiteInitializationAndRestart_HP_STORE_01(t *testing.T) {
dataDir := filepath.Join(t.TempDir(), "state")
opened := openStore(t, dataDir)
for _, directory := range []string{dataDir, filepath.Join(dataDir, "segments"), filepath.Join(dataDir, "audit")} {
for _, directory := range []string{dataDir, filepath.Join(dataDir, "segments"), filepath.Join(dataDir, "audit"), filepath.Join(dataDir, "deleting")} {
info, err := os.Stat(directory)
if err != nil {
t.Fatal(err)
@@ -637,6 +638,147 @@ func TestFilesystemFloorAndCounterMismatch_BH_STORE_08(t *testing.T) {
}
}
func TestTerminalAgeRetentionEvictsWholeCommand_HP_STORE_08(t *testing.T) {
t.Parallel()
dataDir := filepath.Join(t.TempDir(), "state")
opened := openStore(t, dataDir)
now := time.Unix(2_000_000, 0).UTC()
old := uuidBytes(120)
active := uuidBytes(140)
seedCommand(t, opened.DB(), old)
seedCommand(t, opened.DB(), active)
if _, err := opened.AppendCommandEvent(context.Background(), appendEvent(old, 1, "retained history")); err != nil {
t.Fatal(err)
}
markTerminal(t, opened.DB(), old, now.Add(-40*24*time.Hour), now.Add(-31*24*time.Hour))
if _, err := opened.DB().Exec(`UPDATE commands SET issue_time = ? WHERE issue_uuid = ?`, now.Add(-100*24*time.Hour).UnixNano(), active[:]); err != nil {
t.Fatal(err)
}
report, err := opened.RunRetention(context.Background(), now, store.RetentionPolicy{
TerminalAge: 30 * 24 * time.Hour, TombstoneMaxEntries: 1_000_000,
})
if err != nil {
t.Fatal(err)
}
if report.CommandsEvicted != 1 || report.FilesDeleted != 1 || report.BytesReleased == 0 {
t.Fatalf("retention report = %+v", report)
}
var commands, tombstones int
if err := opened.DB().QueryRow(`SELECT count(*) FROM commands WHERE issue_uuid = ?`, old[:]).Scan(&commands); err != nil || commands != 0 {
t.Fatalf("old command count = %d, err = %v", commands, err)
}
if err := opened.DB().QueryRow(`SELECT count(*) FROM commands WHERE issue_uuid = ?`, active[:]).Scan(&commands); err != nil || commands != 1 {
t.Fatalf("active command count = %d, err = %v", commands, err)
}
if err := opened.DB().QueryRow(`SELECT count(*) FROM command_tombstones WHERE issue_uuid = ?`, old[:]).Scan(&tombstones); err != nil || tombstones != 1 {
t.Fatalf("tombstone count = %d, err = %v", tombstones, err)
}
for _, directory := range []string{"segments", "deleting"} {
entries, err := os.ReadDir(filepath.Join(dataDir, directory))
if err != nil || len(entries) != 0 {
t.Fatalf("%s entries = %v, err = %v", directory, entries, err)
}
}
if _, err := opened.RecoverCommandSegments(context.Background()); err != nil {
t.Fatal(err)
}
if err := opened.Close(); err != nil {
t.Fatal(err)
}
}
func TestTombstoneFIFOIsCappedInEvictionTransaction_HP_STORE_09(t *testing.T) {
t.Parallel()
opened := openStore(t, filepath.Join(t.TempDir(), "state"))
now := time.Unix(3_000_000, 0).UTC()
first := uuidBytes(150)
second := uuidBytes(170)
seedCommand(t, opened.DB(), first)
seedCommand(t, opened.DB(), second)
markTerminal(t, opened.DB(), first, now.Add(-2*time.Hour), now.Add(-time.Hour))
markTerminal(t, opened.DB(), second, now.Add(-time.Hour), now.Add(-time.Hour))
report, err := opened.RunRetention(context.Background(), now, store.RetentionPolicy{PressureBytes: 1, TombstoneMaxEntries: 1})
if err != nil {
t.Fatal(err)
}
// Zero-charge commands cannot satisfy byte pressure, so every eligible terminal command is reclaimed.
if report.CommandsEvicted != 2 || report.TombstonesTrimmed != 1 {
t.Fatalf("retention report = %+v", report)
}
var count int
var remaining []byte
if err := opened.DB().QueryRow(`SELECT count(*), max(issue_uuid) FROM command_tombstones`).Scan(&count, &remaining); err != nil {
t.Fatal(err)
}
if count != 1 || !bytes.Equal(remaining, second[:]) {
t.Fatalf("tombstones = count %d remaining %x, want %x", count, remaining, second)
}
if err := opened.Close(); err != nil {
t.Fatal(err)
}
}
func TestRetentionCrashStagesRollForward_CRASH_STORE_04(t *testing.T) {
t.Parallel()
for index, checkpoint := range []string{
store.FaultAfterEvictionMarked,
store.FaultAfterEvictionFilesMoved,
store.FaultAfterEvictionMetadataGone,
} {
t.Run(checkpoint, func(t *testing.T) {
dataDir := filepath.Join(t.TempDir(), "state")
injected := errors.New("injected retention crash")
opened := openStoreWithOptions(t, store.Options{
DataDir: dataDir, BusyTimeout: busyTimeout,
FaultInjector: &failOnceInjector{target: checkpoint, failure: injected},
})
now := time.Unix(4_000_000+int64(index), 0).UTC()
issue := uuidBytes(byte(190 + index*20))
seedCommand(t, opened.DB(), issue)
if _, err := opened.AppendCommandEvent(context.Background(), appendEvent(issue, 1, "evict me")); err != nil {
t.Fatal(err)
}
markTerminal(t, opened.DB(), issue, now.Add(-2*time.Hour), now.Add(-time.Hour))
if _, err := opened.RunRetention(context.Background(), now, store.RetentionPolicy{PressureBytes: 1, TombstoneMaxEntries: 10}); !errors.Is(err, injected) {
t.Fatalf("RunRetention error = %v", err)
}
if err := opened.Close(); err != nil {
t.Fatal(err)
}
reopened := openStore(t, dataDir)
if _, err := reopened.RecoverEvictions(context.Background(), 10); err != nil {
t.Fatal(err)
}
var commandCount, tombstoneCount int
if err := reopened.DB().QueryRow(`SELECT count(*) FROM commands WHERE issue_uuid = ?`, issue[:]).Scan(&commandCount); err != nil {
t.Fatal(err)
}
if err := reopened.DB().QueryRow(`SELECT count(*) FROM command_tombstones WHERE issue_uuid = ?`, issue[:]).Scan(&tombstoneCount); err != nil {
t.Fatal(err)
}
if commandCount != 0 || tombstoneCount != 1 {
t.Fatalf("recovered counts = commands %d tombstones %d", commandCount, tombstoneCount)
}
for _, directory := range []string{"segments", "deleting"} {
entries, err := os.ReadDir(filepath.Join(dataDir, directory))
if err != nil || len(entries) != 0 {
t.Fatalf("%s entries = %v, err = %v", directory, entries, err)
}
}
if _, err := reopened.RecoverCommandSegments(context.Background()); err != nil {
t.Fatal(err)
}
if err := reopened.Close(); err != nil {
t.Fatal(err)
}
})
}
}
func openStore(t *testing.T, dataDir string) *store.Store {
t.Helper()
return openStoreWithOptions(t, store.Options{DataDir: dataDir, BusyTimeout: busyTimeout})
@@ -746,3 +888,10 @@ func assertCommandEventState(t *testing.T, database *sql.DB, issue [16]byte, wan
t.Fatalf("event state = (last=%d, rows=%d), want (%d, %d)", last, events, wantLast, wantEvents)
}
}
func markTerminal(t *testing.T, database *sql.DB, issue [16]byte, issueTime, terminalTime time.Time) {
t.Helper()
if _, err := database.Exec(`UPDATE commands SET issue_time = ?, lifecycle = 5, terminal_time = ? WHERE issue_uuid = ?`, issueTime.UnixNano(), terminalTime.UnixNano(), issue[:]); err != nil {
t.Fatal(err)
}
}