@@ -31,10 +31,12 @@ type prefetcherConfig struct {
3131 newWorkers func () WorkerPool
3232}
3333
34- // A WorkerPool executes functions asynchronously.
34+ // A WorkerPool executes functions asynchronously. Done() is called to signal
35+ // that the pool is no longer needed and that Execute() is guaranteed to not be
36+ // called again.
3537type WorkerPool interface {
3638 Execute (func ())
37- Wait ()
39+ Done ()
3840}
3941
4042// WithWorkerPools configures trie prefetching to execute asynchronously. The
@@ -49,6 +51,7 @@ func WithWorkerPools(ctor func() WorkerPool) PrefetcherOption {
4951type subfetcherPool struct {
5052 workers WorkerPool
5153 tries sync.Pool
54+ wg sync.WaitGroup
5255}
5356
5457// applyTo configures the [subfetcher] to use a [WorkerPool] if one was provided
@@ -68,10 +71,9 @@ func (c *prefetcherConfig) applyTo(sf *subfetcher) {
6871 }
6972}
7073
71- // abortFetchersConcurrently calls [subfetcher.abort] on every fetcher, blocking
72- // until all return. Calling abort() sequentially may result in later fetchers
73- // accepting new work in the interim.
74- func (p * triePrefetcher ) abortFetchersConcurrently () {
74+ func (p * triePrefetcher ) abortFetchersAndReleaseWorkerPools () {
75+ // Calling abort() sequentially may result in later fetchers accepting new
76+ // work in the interim.
7577 var wg sync.WaitGroup
7678 for _ , f := range p .fetchers {
7779 wg .Add (1 )
@@ -80,26 +82,35 @@ func (p *triePrefetcher) abortFetchersConcurrently() {
8082 wg .Done ()
8183 }(f )
8284 }
85+
86+ // A WorkerPool is allowed to be shared between fetchers so we MUST wait for
87+ // them to finish all tasks otherwise they could call Execute() after
88+ // Done(), which we guarantee in the public API to be impossible.
8389 wg .Wait ()
90+ for _ , f := range p .fetchers {
91+ if w := f .pool .workers ; w != nil {
92+ w .Done ()
93+ }
94+ }
8495}
8596
8697func (p * subfetcherPool ) wait () {
87- if p == nil || p .workers == nil {
88- return
89- }
90- p .workers .Wait ()
98+ p .wg .Wait ()
9199}
92100
93101// execute runs the provided function with a copy of the subfetcher's Trie.
94102// Copies are stored in a [sync.Pool] to reduce creation overhead. If p was
95103// configured with a [WorkerPool] then it is used for function execution,
96104// otherwise `fn` is just called directly.
97105func (p * subfetcherPool ) execute (fn func (Trie )) {
106+ p .wg .Add (1 )
98107 do := func () {
99108 t := p .tries .Get ().(Trie )
100109 fn (t )
101110 p .tries .Put (t )
111+ p .wg .Done ()
102112 }
113+
103114 if w := p .workers ; w != nil {
104115 w .Execute (do )
105116 } else {
0 commit comments