Evict archive writers on deletion and sweep idle archives (closes #89)
All checks were successful
check / check (push) Successful in 3m10s
All checks were successful
check / check (push) Successful in 3m10s
The per-webhook archiveWriter registry in the database delivery target was never evicted, so a deleted webhook's writer -- and any archive file handle open within its debounce window -- lingered for the process lifetime. Separately, expiry pruning ran only when an archive was (re)opened, and reopens only happen on writes, so an archive belonging to a webhook that stopped receiving events kept its expired rows forever. Eviction: a new one-method delivery.WebhookEvictor interface (kept separate from Notifier: archiving lifecycle is not notification) is implemented by the Engine and injected into the handlers. Deleting a webhook, or deleting its last database target, drops the writer from the registry and closes its handle under the writer's own mutex, so eviction can never race an in-flight write. An evicted writer refuses further writes rather than reopening a file nothing holds. The archive file is deliberately left on disk: it is long-term storage an operator may want to keep or move away, and destroying it as a side effect of deleting a webhook would be unrecoverable. Idle sweep: a new ArchiveSweeper, modelled on the event RetentionReaper (fx lifecycle hooks, cancellable context, WaitGroup, ticker loop), prunes archives whose database target declares a positive expiry. It reuses the existing RETENTION_SWEEP_INTERVAL rather than adding a config key. It never creates an archive -- a missing file is skipped, and the reopen uses SQLite mode=rw so the file cannot be conjured even if it disappears mid-sweep -- routes the prune through the per-webhook writer so its mutex orders the sweep against concurrent writes, and leaves the archive closed so the move-the-file-away workflow keeps working. A failure for one webhook is logged and the sweep continues. Archives with no expiry or the expiry "never" are untouched.
This commit is contained in:
24
README.md
24
README.md
@@ -531,6 +531,30 @@ older than the expiry are pruned each time the archive is (re)opened. An
|
||||
archive write failure is never silent success: the delivery records a
|
||||
failed attempt with the error and is marked failed.
|
||||
|
||||
Because reopens only happen on writes, an archive belonging to a webhook
|
||||
that has stopped receiving events would never be pruned. A background
|
||||
**archive sweeper** closes that gap: on the same interval as the event
|
||||
retention reaper (`RETENTION_SWEEP_INTERVAL`) it prunes every archive
|
||||
whose database target declares a positive expiry, whether or not the
|
||||
webhook is still receiving traffic. The sweep never creates an archive —
|
||||
a webhook whose archive file does not yet exist is skipped, not
|
||||
initialised — it takes the same per-webhook lock the write path uses, so
|
||||
it can never interleave with a write, and it leaves the archive closed
|
||||
afterwards so the move-the-file-away workflow keeps working. Archives
|
||||
with no expiry, or the expiry `never`, are not touched by the sweep at
|
||||
all.
|
||||
|
||||
Deleting a webhook releases its archive: the delivery engine's cached
|
||||
archive writer is dropped and its file handle closed, so nothing lingers
|
||||
after the webhook is gone. The archive **file itself is deliberately
|
||||
left on disk**. Unlike the event database — per-webhook working storage
|
||||
that is hard-deleted with the webhook — an archive is long-term storage
|
||||
an operator may still want to keep or move away for offline retention,
|
||||
and destroying it as a side effect of deleting a webhook would be
|
||||
unrecoverable. Removing `archive-{webhookID}.db` is the operator's call.
|
||||
Deleting a webhook's last `database` target releases the writer the same
|
||||
way, and for the same reason leaves the file alone.
|
||||
|
||||
The **Slack target type** sends webhook events as formatted messages to
|
||||
any Slack-compatible incoming webhook URL (works with Slack, Mattermost,
|
||||
and other compatible services). Each message includes event metadata
|
||||
|
||||
Reference in New Issue
Block a user