fix: EventBus panic on SSE disconnect + JWT secret persistence + recover() guards
- eventbus.go: Fix send-on-closed-channel panic in SubscribeGlobal by using a done channel; add recover() in fan-out goroutine; track active global subs for proper cleanup on unsubscribe - config.go: Persist JWT secret to $DATA_DIR/.jwt_secret instead of regenerating a random one on every restart (which invalidated all sessions) - handlers_ws.go: Replace time.After with time.Ticker to fix timer leak in SSE keepalive loop - handlers_jobs.go: Add recover() in fire-and-forget job goroutine; fix nil pointer deref when GetByID fails after job creation - handlers_machines.go: Add recover() in ProbeAllMachines goroutine - scheduler.go: Add recover() in scheduled job run goroutine - engine.go: Add recover() in per-machine probe goroutines
This commit is contained in:
@@ -10,6 +10,12 @@ type EventBus struct {
|
||||
mu sync.RWMutex
|
||||
global chan Event
|
||||
bufferSize int
|
||||
globalSubs []globalSub
|
||||
}
|
||||
|
||||
type globalSub struct {
|
||||
ch chan Event
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewEventBus(bufferSize int) *EventBus {
|
||||
@@ -17,6 +23,7 @@ func NewEventBus(bufferSize int) *EventBus {
|
||||
subscribers: make(map[int64]map[chan Event]struct{}),
|
||||
global: make(chan Event, bufferSize),
|
||||
bufferSize: bufferSize,
|
||||
globalSubs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,20 +50,42 @@ func (eb *EventBus) Subscribe(jobID int64) (chan Event, func()) {
|
||||
}
|
||||
|
||||
func (eb *EventBus) SubscribeGlobal() (chan Event, func()) {
|
||||
eb.mu.RLock()
|
||||
ch := make(chan Event, eb.bufferSize)
|
||||
eb.mu.RUnlock()
|
||||
done := make(chan struct{})
|
||||
eb.mu.Lock()
|
||||
eb.globalSubs = append(eb.globalSubs, globalSub{ch: ch, done: done})
|
||||
eb.mu.Unlock()
|
||||
go func() {
|
||||
for evt := range eb.global {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
slog.Error("SubscribeGlobal goroutine panicked", "reason", r)
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case ch <- evt:
|
||||
default:
|
||||
slog.Warn("global event subscriber buffer full, dropping event", "type", evt.Type)
|
||||
case evt := <-eb.global:
|
||||
select {
|
||||
case ch <- evt:
|
||||
default:
|
||||
slog.Warn("global event subscriber buffer full, dropping event", "type", evt.Type)
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch, func() { close(ch) }
|
||||
return ch, func() {
|
||||
close(done)
|
||||
eb.mu.Lock()
|
||||
for i, s := range eb.globalSubs {
|
||||
if s.ch == ch {
|
||||
eb.globalSubs = append(eb.globalSubs[:i], eb.globalSubs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
eb.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (eb *EventBus) Publish(evt Event) {
|
||||
|
||||
Reference in New Issue
Block a user