#15 waf/alerts.go: AlertWriter.Close() flusht gepufferte Alerts + stoppt die Goroutine (stop/done-Channels, sync.Once, atomic closed; Kanal wird NIE geschlossen → Send racet ohne Panic). Wiring in cmd/edgeguard-waf nach ListenAndServe (graceful shutdown). -race-Test alerts_test.go. #19 handlers/cluster_rollingupdate.go: (a) RollingUpdateStatus mutiert State nicht mehr beim GET — terminale Zustände altern in readRollingUpdateState nach 10 min aus (kein verlorenes 'done' bei parallelen Pollern). (b) State-File via sync.Mutex + configgen.AtomicWrite (kein partieller Read / Race zwischen Handler & Goroutine). (c) Version-Flip wird gegen die VORHER erfasste Secondary-Baseline geprüft statt gegen die Primary-Version (verhindert sofort-/nie-Flip). Bewusst belassen: geteilter upgrade.sh-Pfad ist deterministischer Inhalt + an exakte sudoers-Zeile gebunden → Überschreib-Race benign; MST-Timestamp-Parse locale (Server laufen C-Locale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ package waf
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -26,8 +28,12 @@ type Alert struct {
|
||||
// AlertWriter accepts Alert values via a buffered channel and writes
|
||||
// them to PostgreSQL asynchronously so SPOE handling stays low-latency.
|
||||
type AlertWriter struct {
|
||||
pool *pgxpool.Pool
|
||||
ch chan Alert
|
||||
pool *pgxpool.Pool
|
||||
ch chan Alert
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
// NewAlertWriter creates an AlertWriter and starts its background goroutine.
|
||||
@@ -36,14 +42,19 @@ func NewAlertWriter(pool *pgxpool.Pool, bufSize int) *AlertWriter {
|
||||
aw := &AlertWriter{
|
||||
pool: pool,
|
||||
ch: make(chan Alert, bufSize),
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go aw.run()
|
||||
return aw
|
||||
}
|
||||
|
||||
// Send enqueues an alert. Drops silently if the channel is full to
|
||||
// avoid slowing down SPOE request handling.
|
||||
// Send enqueues an alert. Drops silently if the channel is full (or the
|
||||
// writer is closing) to avoid slowing down / panicking SPOE handling.
|
||||
func (aw *AlertWriter) Send(a Alert) {
|
||||
if aw.closed.Load() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case aw.ch <- a:
|
||||
default:
|
||||
@@ -51,9 +62,34 @@ func (aw *AlertWriter) Send(a Alert) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops the writer and flushes buffered alerts (best-effort).
|
||||
// Safe to call multiple times. The channel is never closed → Send never
|
||||
// panics even if it races with Close.
|
||||
func (aw *AlertWriter) Close() {
|
||||
aw.closeOnce.Do(func() {
|
||||
aw.closed.Store(true)
|
||||
close(aw.stop)
|
||||
})
|
||||
<-aw.done
|
||||
}
|
||||
|
||||
func (aw *AlertWriter) run() {
|
||||
for a := range aw.ch {
|
||||
aw.write(a)
|
||||
defer close(aw.done)
|
||||
for {
|
||||
select {
|
||||
case a := <-aw.ch:
|
||||
aw.write(a)
|
||||
case <-aw.stop:
|
||||
// Restliche gepufferte Alerts noch wegschreiben, dann Ende.
|
||||
for {
|
||||
select {
|
||||
case a := <-aw.ch:
|
||||
aw.write(a)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user