fix(symlinkguard): test the WithoutCancel final flush; log what shutdown drops
PR #180 round-2 review (comments 15467, 15487): - toggleNotifier now fails on a done ctx, like audit.NtfyNotifier, so replacing context.WithoutCancel(ctx) with ctx in the final flush turns TestWatchFinalFlushSendsAQueuedAlertOnACtxThatOutlivesTheCancel (renamed from ...FlushesTheQueueOnceMoreAtShutdown) red. - After the final flush, any undelivered or suppressed alerts are logged as one error with both counts, never silently. - The !ok first-receive branch now swaps overflow into suppressed. - Deterministic deliver() tests for the drain-on-close path and the closed-and-empty overflow case. - Watch doc states the shutdown path only runs for a cancelling caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,11 @@ func Scan(brainDir string) (Result, error) {
|
||||
// scan waited on it a planted link stayed readable for several intervals.
|
||||
// The scan hands alerts over without blocking; any that do not fit are
|
||||
// counted in the summary. When ctx ends, Watch returns after a final flush
|
||||
// of what was handed over (bounded by shutdownFlushTimeout).
|
||||
// of what was handed over (bounded by shutdownFlushTimeout); alerts that
|
||||
// flush cannot deliver are counted in one error log line instead. That
|
||||
// shutdown path only runs for a caller that cancels ctx: cmd/server passes
|
||||
// context.Background(), so in production the flush on handoff is what keeps
|
||||
// alerts from waiting on a restart.
|
||||
func Watch(ctx context.Context, brainDir string, interval time.Duration, n Notifier) {
|
||||
if interval <= 0 {
|
||||
interval = DefaultInterval
|
||||
@@ -211,7 +215,8 @@ func (s alertSender) push(msg string) {
|
||||
// over, and again every interval while any are pending (outage retries).
|
||||
// When alerts is closed (Watch is stopping) it drains it and flushes once
|
||||
// more, bounded by shutdownFlushTimeout, so an alert handed over just before
|
||||
// shutdown is not lost (PR #180 review).
|
||||
// shutdown is not lost (PR #180 review). Whatever that flush cannot deliver
|
||||
// is logged as one error with its counts, never dropped silently.
|
||||
func deliver(ctx context.Context, interval time.Duration, n Notifier, alerts <-chan string, overflow *atomic.Int64) {
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
@@ -240,9 +245,14 @@ func deliver(ctx context.Context, interval time.Duration, n Notifier, alerts <-c
|
||||
ok = drain()
|
||||
}
|
||||
if !ok {
|
||||
q.suppressed += int(overflow.Swap(0))
|
||||
fctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), shutdownFlushTimeout)
|
||||
q.flush(fctx, n)
|
||||
cancel()
|
||||
if len(q.items) > 0 || q.suppressed > 0 {
|
||||
slog.Error("symlink tripwire alerts not delivered at shutdown",
|
||||
"undelivered", len(q.items), "suppressed", q.suppressed)
|
||||
}
|
||||
return
|
||||
}
|
||||
q.flush(ctx, n)
|
||||
|
||||
Reference in New Issue
Block a user