mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
refactor(33-data-model-integrity): harden shutdown lifecycle in pkg/audit
This commit is contained in:
@@ -31,6 +31,8 @@ type SQLiteLogger struct {
|
||||
retentionDays int
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
// NewSQLiteLogger creates a new SQLite-backed audit logger.
|
||||
@@ -387,20 +389,24 @@ func (l *SQLiteLogger) VerifySignature(event Event) bool {
|
||||
|
||||
// Close gracefully shuts down the logger.
|
||||
func (l *SQLiteLogger) Close() error {
|
||||
close(l.stopChan)
|
||||
l.closeOnce.Do(func() {
|
||||
close(l.stopChan)
|
||||
|
||||
if l.webhookDelivery != nil {
|
||||
l.webhookDelivery.Stop()
|
||||
}
|
||||
if l.webhookDelivery != nil {
|
||||
l.webhookDelivery.Stop()
|
||||
}
|
||||
|
||||
l.wg.Wait()
|
||||
l.wg.Wait()
|
||||
|
||||
if err := l.db.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close audit database: %w", err)
|
||||
}
|
||||
if err := l.db.Close(); err != nil {
|
||||
l.closeErr = fmt.Errorf("failed to close audit database: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msg("SQLite audit logger closed")
|
||||
return nil
|
||||
log.Info().Msg("SQLite audit logger closed")
|
||||
})
|
||||
|
||||
return l.closeErr
|
||||
}
|
||||
|
||||
// loadWebhookURLs loads webhook URLs from the config table.
|
||||
|
||||
@@ -502,3 +502,23 @@ func TestSQLiteLoggerConcurrentAccess(t *testing.T) {
|
||||
t.Errorf("Expected 100 events, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLiteLoggerCloseIdempotent(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
||||
DataDir: tempDir,
|
||||
CryptoMgr: newMockCryptoManager(),
|
||||
RetentionDays: 30,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
||||
}
|
||||
|
||||
if err := logger.Close(); err != nil {
|
||||
t.Fatalf("first Close failed: %v", err)
|
||||
}
|
||||
if err := logger.Close(); err != nil {
|
||||
t.Fatalf("second Close failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type WebhookDelivery struct {
|
||||
client *http.Client
|
||||
queue chan Event
|
||||
stopChan chan struct{}
|
||||
stopOnce sync.Once
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
@@ -72,9 +73,11 @@ func (w *WebhookDelivery) Start() {
|
||||
|
||||
// Stop gracefully stops the delivery workers.
|
||||
func (w *WebhookDelivery) Stop() {
|
||||
close(w.stopChan)
|
||||
w.wg.Wait()
|
||||
log.Debug().Msg("Audit webhook delivery stopped")
|
||||
w.stopOnce.Do(func() {
|
||||
close(w.stopChan)
|
||||
w.wg.Wait()
|
||||
log.Debug().Msg("Audit webhook delivery stopped")
|
||||
})
|
||||
}
|
||||
|
||||
// Enqueue adds an event to the delivery queue.
|
||||
|
||||
@@ -158,3 +158,11 @@ func TestWebhookDeliveryDeliverInvalidURL(t *testing.T) {
|
||||
t.Fatalf("expected URL blocked error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookDeliveryStopIdempotent(t *testing.T) {
|
||||
delivery := NewWebhookDelivery([]string{})
|
||||
delivery.Start()
|
||||
|
||||
delivery.Stop()
|
||||
delivery.Stop()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user