From da86f9259b473de5164edb55cce0061700581f24 Mon Sep 17 00:00:00 2001 From: Zhe Chen <112833940+zhechen-cs@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:57:16 +0800 Subject: [PATCH] fix(pool): recover subpool workers after runtime.Goexit --- pool.go | 12 ++++++++++++ subpool_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/pool.go b/pool.go index 1ba2092..26b2045 100644 --- a/pool.go +++ b/pool.go @@ -281,12 +281,24 @@ func (p *pool) worker(task any) { func (p *pool) subpoolWorker(task any) func() (output any, err error) { return func() (output any, err error) { + exitedNormally := false + defer func() { + if !exitedNormally { + p.updateMetrics(fmt.Errorf("worker exited abnormally: %w", err)) + if nextTask, readErr := p.readTask(); readErr == nil { + p.launchWorker(nextTask) + } + } + }() + if task != nil { output, err = invokeTask[any](task, p.panicRecovery) p.updateMetrics(err) } + exitedNormally = true + // Attempt to submit the next task to the parent pool if task, err := p.readTask(); err == nil { for { diff --git a/subpool_test.go b/subpool_test.go index 875f9af..e1bd3d3 100644 --- a/subpool_test.go +++ b/subpool_test.go @@ -3,6 +3,7 @@ package pond import ( "context" "errors" + "runtime" "sync" "sync/atomic" "testing" @@ -467,3 +468,54 @@ func TestSubpoolResize(t *testing.T) { pool.Stop().Wait() } + +func TestSubpoolRecoversAfterGoexit(t *testing.T) { + for _, nested := range []bool{false, true} { + name := "direct" + if nested { + name = "nested" + } + t.Run(name, func(t *testing.T) { + parent := NewPool(1) + defer parent.StopAndWait() + subpool := parent.NewSubpool(1) + var intermediate Pool + if nested { + intermediate = subpool + subpool = intermediate.NewSubpool(1) + } + + started := make(chan struct{}) + release := make(chan struct{}) + aborted := subpool.Submit(func() { + close(started) + <-release + runtime.Goexit() + }) + <-started + next := subpool.Submit(func() {}) + close(release) + + done := make(chan struct{}) + go func() { + subpool.StopAndWait() + close(done) + }() + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("subpool did not recover after runtime.Goexit") + } + + assert.Equal(t, ErrPoolStopped, aborted.Wait()) + assert.Equal(t, nil, next.Wait()) + assert.Equal(t, int64(0), subpool.RunningWorkers()) + assert.Equal(t, uint64(0), subpool.WaitingTasks()) + assert.Equal(t, uint64(1), subpool.FailedTasks()) + assert.Equal(t, uint64(1), subpool.SuccessfulTasks()) + if intermediate != nil { + intermediate.StopAndWait() + } + }) + } +}