Handle worker Goexit cleanup and discard orphaned queued tasks

This commit is contained in:
2026-10-20 21:29:45 +08:00
parent aa93168583
commit 5bf542fc67
3 changed files with 334 additions and 5 deletions

36
pool.go
View File

@@ -84,8 +84,10 @@ func (p *Pool) Submit(task func()) error {
}
}
// Close stops accepting tasks and waits for accepted tasks and workers to
// finish. It is safe to call Close concurrently or more than once.
// Close stops accepting tasks and waits for workers to exit. Tasks accepted
// before Close normally finish before it returns. If a task calls
// runtime.Goexit and that causes the last worker to exit, queued tasks are
// discarded. It is safe to call Close concurrently or more than once.
func (p *Pool) Close() {
p.mu.Lock()
p.closed = true
@@ -116,12 +118,19 @@ func (p *Pool) startWorkerLocked(task func(), withTask bool) {
}
func (p *Pool) run(w *worker) {
normalExit := false
defer func() {
if !normalExit {
p.workerExitedAbnormally()
}
}()
for {
task, ok := <-w.inbox
if !ok {
p.mu.Lock()
p.total--
p.workerExitedLocked()
normalExit = true
p.mu.Unlock()
return
}
@@ -138,6 +147,7 @@ func (p *Pool) run(w *worker) {
otherIdle := len(p.idle)
if p.closed || (p.total > p.min && otherIdle > p.min/2) {
p.total--
normalExit = true
if !p.closed {
p.submitCond.Signal()
} else {
@@ -180,6 +190,28 @@ func (p *Pool) workerExitedLocked() {
}
}
// workerExitedAbnormally accounts for a worker terminated by runtime.Goexit.
// If other workers remain, they retain the FIFO queue. The last worker clears
// the queue because no worker remains to consume it; all of this is protected
// by mu so Submit and Close observe the same state transition.
func (p *Pool) workerExitedAbnormally() {
p.mu.Lock()
p.total--
if p.total == 0 {
for i := range p.queue {
p.queue[i] = nil
}
p.queueHead = 0
p.queueTail = 0
p.queueLen = 0
p.submitCond.Broadcast()
} else {
p.submitCond.Signal()
}
p.workerExitedLocked()
p.mu.Unlock()
}
func runTask(task func()) {
defer func() {
if recovered := recover(); recovered != nil {