v0.6.7: graceful state-lock release (#190) + gated share-viewer download #191

Merged
Cordy merged 2 commits from ship-v0.6.7 into main 2026-08-11 00:13:13 +00:00
Owner

What

Two changes, batched into one release to avoid two redeploy stalls.

1. Release the state-backend instance lock on graceful shutdown, even when draining errors (#190)

Lock.Release() already existed and was wired into the SIGTERM path, but it ran after srv.Shutdown(...) and was skipped by the os.Exit(1) on a drain error — so a long-lived connection that outlasts the 15s drain deadline would leave the lock held, forcing the Recreate-rollout replacement pod to wait out the full 5m stale window (exactly the v0.6.6 dogfood incident). The heartbeat goroutine also ran on an uncancellable context.Background(), so it could re-Save() the lock right after Release() deleted it.

Fix, all in cmd/cairnd/main.go:

  • A releaseStateLock() closure that stops the heartbeat first (so it cannot re-create the lock) then best-effort deletes it.
  • Called on the signal path regardless of the drain result (moved before the os.Exit), and on the serve-error (errCh) path too.
  • Heartbeat now runs on a cancellable context captured at startup.

Abnormal exits (crash, SIGKILL, config-error os.Exit during boot) still fall back to the 5m stale timeout by design — this only makes the graceful path deterministic.

New guard test TestLockReleaseAllowsForeignAcquire: after Release(), a different-ID instance acquires immediately (no stale wait) — the property the shutdown wiring depends on. The main.go wiring itself is integration-verified on the dogfood redeploy.

internal/share/viewer.go, fragment mode only: instead of firing the download on page load, the viewer now renders a Download button the recipient clicks. Cleaner UX and more robust (browsers increasingly block gesture-less programmatic downloads). The post-download control keeps its "Download again" label (now accurate). Password mode (already gated behind "Open") is unchanged.

Validation

The branch commit was produced by a one-shot workflow that ran gofmt, go vet ./..., go test ./... (all green), a dual-arch go build, and node --check on the rendered viewer script before committing. This PR re-runs CI on the pull_request event.

Closes #190.

## What Two changes, batched into one release to avoid two redeploy stalls. ### 1. Release the state-backend instance lock on graceful shutdown, even when draining errors (#190) `Lock.Release()` already existed and was wired into the SIGTERM path, but it ran **after** `srv.Shutdown(...)` and was skipped by the `os.Exit(1)` on a drain error — so a long-lived connection that outlasts the 15s drain deadline would leave the lock held, forcing the Recreate-rollout replacement pod to wait out the full 5m stale window (exactly the v0.6.6 dogfood incident). The heartbeat goroutine also ran on an uncancellable `context.Background()`, so it could re-`Save()` the lock right after `Release()` deleted it. Fix, all in `cmd/cairnd/main.go`: - A `releaseStateLock()` closure that **stops the heartbeat first** (so it cannot re-create the lock) then best-effort deletes it. - Called on the signal path **regardless of the drain result** (moved before the `os.Exit`), and on the serve-error (`errCh`) path too. - Heartbeat now runs on a cancellable context captured at startup. Abnormal exits (crash, `SIGKILL`, config-error `os.Exit` during boot) still fall back to the 5m stale timeout by design — this only makes the *graceful* path deterministic. New guard test `TestLockReleaseAllowsForeignAcquire`: after `Release()`, a different-ID instance acquires immediately (no stale wait) — the property the shutdown wiring depends on. The main.go wiring itself is integration-verified on the dogfood redeploy. ### 2. Encrypted share links no longer auto-download for the recipient (item 3) `internal/share/viewer.go`, fragment mode only: instead of firing the download on page load, the viewer now renders a **Download** button the recipient clicks. Cleaner UX and more robust (browsers increasingly block gesture-less programmatic downloads). The post-download control keeps its "Download again" label (now accurate). Password mode (already gated behind "Open") is unchanged. ## Validation The branch commit was produced by a one-shot workflow that ran `gofmt`, `go vet ./...`, `go test ./...` (all green), a dual-arch `go build`, and `node --check` on the rendered viewer script before committing. This PR re-runs CI on the pull_request event. Closes #190.
add v0.6.7 one-shot: graceful lock release (#190) + gated share download
All checks were successful
ship-v067 / ship (push) Successful in 37s
91bfefc895
v0.6.7: graceful state-lock release (#190) + gated share download (item 3)
All checks were successful
ci / test-and-build (pull_request) Successful in 34s
0080acf19e
Cordy merged commit 4737e7b916 into main 2026-08-11 00:13:13 +00:00
Cordy deleted branch ship-v0.6.7 2026-08-11 00:13:15 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#191
No description provided.