carlos / platform Public

ops litestream: daemon-reload under a one-minute deadline (#284)

Summary

The reload now runs while the drop-in's directory is locked, so it gets
a deadline: a systemd that never answers fails this run and leaves the
marker for the next, instead of holding every later run out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 01e1269152819ebf9da3f3c7afa884596d2028be parent 0fa3b84
3 files changed, +12 −5
  • internal/ops/ops.go +8 −1
  • internal/provision/plan14_root_reads_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-ls284.tsv +3 −3

CI cancelled — superseded by a newer push — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m

amadan: job cancelled — a newer push superseded it
diff --git a/internal/ops/ops.go b/internal/ops/ops.go
index e0855a3ba..f2d04b349 100644
--- a/internal/ops/ops.go
+++ b/internal/ops/ops.go
@@ -200,10 +200,17 @@ should no longer be replicated.`,
}
}
+// daemonReloadTimeout bounds the reload. It runs while the drop-in's
+// directory is locked, so a systemd that never answers must fail this run
+// (the marker makes the next one try again), not hold every later run out.
+const daemonReloadTimeout = time.Minute
+
// systemdDaemonReload has systemd re-read its unit files, which is what makes
// a drop-in this verb just wrote the unit's definition at its next start.
func systemdDaemonReload() error {
- out, err := exec.Command("systemctl", "daemon-reload").CombinedOutput()
+ ctx, cancel := context.WithTimeout(context.Background(), daemonReloadTimeout)
+ defer cancel()
+ out, err := exec.CommandContext(ctx, "systemctl", "daemon-reload").CombinedOutput()
if err != nil {
return fmt.Errorf("systemctl daemon-reload: %v: %s", err, strings.TrimSpace(string(out)))
}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 00669f8d0..ba614f33e 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -667,7 +667,7 @@ func TestPlan14RootReadCensus(t *testing.T) {
// Census ruling ls284 (#284): the litestream memory drop-in, the unit's
// restart backoff and the agent's restart-counter probe and its saved state add seven frozen
// declarations.
- seal = "13bc746ce6afb179c887894b36e18cbc0c1e7852e32317c6811aa8a8d3253c56"
+ seal = "edaf4676482aca981d259d4e574a09d53ef5f8330cbcc9bfa69057c644426ca5"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-ls284.tsv b/internal/provision/testdata/plan14-root-reads-ls284.tsv
index 8a7611406..5ac00676f 100644
--- a/internal/provision/testdata/plan14-root-reads-ls284.tsv
+++ b/internal/provision/testdata/plan14-root-reads-ls284.tsv
@@ -12,11 +12,11 @@ internal/host/units/carlos-litestream.service external legacy accepted-legacy ac
internal/ops/litestream_memory.go writeMemoryDropIn legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Root opens the drop-in's own directory to hold a lock, reads the root-owned drop-in this verb itself wrote, and lstats its own reload-pending marker beside it, to decide whether to rewrite and reload; the same compare-before-write as writeIfChanged under J4 root-input. The path is fixed or an operator-supplied flag. file:Lstat@613,file:Open@264,file:ReadFile@454 9b0fb46e224f97d4740165498abe45a1c25004a955c6176bce20b08398b46f7c
@legacy dc2175dd0445552045146497dd473327ec0ddca2de728f6b8a2c5c755b82420d internal/ops/ops.go runLitestream legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Same registry open and prior-config read. Adds one root-owned write, the memory drop-in beside the unit, sized from the database count this run computed, and a daemon-reload; no new input is trusted. data-literal@214,file:Open@1383,file:ReadFile@2357 380a01961cc02f2a18673bc4ad0a0bc1112905d4726f8ab9e7b370da36686306
internal/ops/ops.go runLitestream legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Same registry open and prior-config read. Adds one root-owned write, the memory drop-in beside the unit, sized from the database count this run computed, and a daemon-reload; no new input is trusted. data-literal@214,file:Open@1383,file:ReadFile@2357 380a01961cc02f2a18673bc4ad0a0bc1112905d4726f8ab9e7b370da36686306
-@legacy cb8030cd13f53803aca45d166742f194f1cb00e1d881263a776b79a334a37189 internal/ops/ops.go systemdDaemonReload legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Root runs a fixed systemctl daemon-reload with no caller input, the same class as internal/host/bootstrap.go systemctl under J4 root-input. file:Command@48 3edadda9e71b806a78d14160f2da0b6091212c4ee55933a327856be811290a74
-internal/ops/ops.go systemdDaemonReload legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Root runs a fixed systemctl daemon-reload with no caller input, the same class as internal/host/bootstrap.go systemctl under J4 root-input. file:Command@48 3edadda9e71b806a78d14160f2da0b6091212c4ee55933a327856be811290a74
+@legacy e2fa62786280b07144aec18b9255bcafb35176e10ca2f6467cfc1656691695cb internal/ops/ops.go systemdDaemonReload legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Root runs a fixed systemctl daemon-reload with no caller input, the same class as internal/host/bootstrap.go systemctl under J4 root-input. file:CommandContext@143 42322a947e1763aa00af63d28155b0919cf72ec9909d49e4ec860cfc7817fd4e
+internal/ops/ops.go systemdDaemonReload legacy accepted-legacy accepted-legacy;rev3=census-ruling-ls284;basis=Root runs a fixed systemctl daemon-reload with no caller input, the same class as internal/host/bootstrap.go systemctl under J4 root-input. file:CommandContext@143 42322a947e1763aa00af63d28155b0919cf72ec9909d49e4ec860cfc7817fd4e
@coverage internal/console 85d4f9521a2acc08aa1276f414be41cea32aa4275f761546e77e951d0d58b558
@coverage internal/console/templates e622e1e901447dc067aba32e4e520eef4279ffcdd807695f012fd38f646d48b4
@coverage internal/host 8bf2f3b97227361f378bbd8943d2d0e157a66f6f90baa5291fb8a13649d4e7de
@coverage internal/host/units e06e4a1a0d9d45f8e7494f0419834bf23e14fb2cd4861b94ef905bfaf795a7d1
@coverage internal/metrics 1adc51fc485ba895970c22277b7bff997c24ebededbd19f48a10e75cf531323a
-@coverage internal/ops 1bd2f9d768a424e8b1fbd1744bba475985b49f43e7ecca836bbadf593fe91ad0
+@coverage internal/ops 1ca0655a6fe33c03993a55c7c08689f8fc1b7adbba14efe5eead823657713353