When the WebSocket connection drops, session cleanup waits for all worker pools to drain. SNMP/MikroTik operations against unresponsive devices can block for 30+ seconds per worker, preventing the agent from reconnecting during that time. Add stopWithTimeout(5s) so pool shutdown is bounded. If workers don't finish within the timeout, they're abandoned and reconnection proceeds immediately.
75 lines
1.5 KiB
Go
75 lines
1.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"runtime/debug"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// workerPool is a fixed-size goroutine pool for executing tasks.
|
|
type workerPool struct {
|
|
tasks chan func()
|
|
wg sync.WaitGroup
|
|
once sync.Once
|
|
}
|
|
|
|
// newWorkerPool creates a pool with n worker goroutines.
|
|
func newWorkerPool(n int) *workerPool {
|
|
p := &workerPool{
|
|
tasks: make(chan func(), n*4),
|
|
}
|
|
p.wg.Add(n)
|
|
for range n {
|
|
go func() {
|
|
defer p.wg.Done()
|
|
for fn := range p.tasks {
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
slog.Error("worker panic recovered", "error", r, "stack", string(debug.Stack()))
|
|
}
|
|
}()
|
|
fn()
|
|
}()
|
|
}
|
|
}()
|
|
}
|
|
return p
|
|
}
|
|
|
|
// submit enqueues a task. Returns false if the context is cancelled before the task can be queued.
|
|
func (p *workerPool) submit(ctx context.Context, fn func()) bool {
|
|
select {
|
|
case p.tasks <- fn:
|
|
return true
|
|
case <-ctx.Done():
|
|
return false
|
|
}
|
|
}
|
|
|
|
// stop closes the task channel and waits for all workers to finish.
|
|
func (p *workerPool) stop() {
|
|
p.once.Do(func() {
|
|
close(p.tasks)
|
|
p.wg.Wait()
|
|
})
|
|
}
|
|
|
|
// stopWithTimeout closes the task channel and waits up to timeout for workers
|
|
// to finish. Returns true if all workers completed, false if the timeout was
|
|
// reached and some workers were abandoned.
|
|
func (p *workerPool) stopWithTimeout(timeout time.Duration) bool {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
p.stop()
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
return true
|
|
case <-time.After(timeout):
|
|
return false
|
|
}
|
|
}
|