gomcp/internal/domain/alert/bus.go

107 lines
2.3 KiB
Go

// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package alert
import "sync"
// Bus is a thread-safe event bus for Alert distribution.
// Uses a ring buffer for bounded memory. Supports multiple subscribers.
type Bus struct {
mu sync.RWMutex
ring []Alert
capacity int
writePos int
count int
subscribers []chan Alert
}
// NewBus creates a new Alert bus with the given capacity.
func NewBus(capacity int) *Bus {
if capacity <= 0 {
capacity = 100
}
return &Bus{
ring: make([]Alert, capacity),
capacity: capacity,
}
}
// Emit publishes an alert to the bus.
// Stored in ring buffer and sent to all subscribers.
func (b *Bus) Emit(a Alert) {
b.mu.Lock()
b.ring[b.writePos] = a
b.writePos = (b.writePos + 1) % b.capacity
if b.count < b.capacity {
b.count++
}
// Copy subscribers under lock to avoid race.
subs := make([]chan Alert, len(b.subscribers))
copy(subs, b.subscribers)
b.mu.Unlock()
// Non-blocking send to subscribers.
for _, ch := range subs {
select {
case ch <- a:
default:
// Subscriber too slow — drop alert.
}
}
}
// Recent returns the last n alerts, newest first.
func (b *Bus) Recent(n int) []Alert {
b.mu.RLock()
defer b.mu.RUnlock()
if n > b.count {
n = b.count
}
if n <= 0 {
return nil
}
result := make([]Alert, n)
for i := 0; i < n; i++ {
// Read backwards from writePos.
idx := (b.writePos - 1 - i + b.capacity) % b.capacity
result[i] = b.ring[idx]
}
return result
}
// Subscribe returns a channel that receives new alerts.
// Buffer size determines how many alerts can queue before dropping.
func (b *Bus) Subscribe(bufSize int) <-chan Alert {
if bufSize <= 0 {
bufSize = 10
}
ch := make(chan Alert, bufSize)
b.mu.Lock()
b.subscribers = append(b.subscribers, ch)
b.mu.Unlock()
return ch
}
// Count returns the total number of stored alerts.
func (b *Bus) Count() int {
b.mu.RLock()
defer b.mu.RUnlock()
return b.count
}
// MaxSeverity returns the highest severity among recent alerts.
func (b *Bus) MaxSeverity(n int) Severity {
alerts := b.Recent(n)
max := SeverityInfo
for _, a := range alerts {
if a.Severity > max {
max = a.Severity
}
}
return max
}