towerops-agent/workerpool.go
Graham McIntire 87606bb84a
security: fixes 1-5 — payload limits, non-blocking submit, panic recovery, handshake timeout, write error propagation
Fix 1: Limit inbound job payload size to 4MB to prevent OOM from malicious server
Fix 2: Worker pool submit accepts context and returns bool, prevents blocking event loop
Fix 3: Panic recovery in worker pool goroutines prevents permanent worker death
Fix 4: WebSocket handshake timeout (30s) prevents indefinite blocking on slow server
Fix 5: Writer goroutine propagates errors to main loop via writeErrCh
2026-02-12 10:55:24 -06:00

56 lines
1 KiB
Go

package main
import (
"context"
"log/slog"
"sync"
)
// 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)
}
}()
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()
})
}