@@ -2,6 +2,7 @@ package pool
2
2
3
3
import (
4
4
"context"
5
+ "sync"
5
6
"time"
6
7
7
8
"golang.org/x/sync/errgroup"
@@ -60,6 +61,10 @@ type (
60
61
done chan struct {}
61
62
62
63
stats * safeStats
64
+
65
+ spawnCancel context.CancelFunc
66
+
67
+ wg * sync.WaitGroup
63
68
}
64
69
option [PT Item [T ], T any ] func (p * Pool [PT , T ])
65
70
)
@@ -202,9 +207,11 @@ func New[PT Item[T], T any](
202
207
onChange : p .trace .OnChange ,
203
208
}
204
209
205
- for i := 0 ; i < defaultSpawnGoroutinesNumber ; i ++ {
206
- go p .spawnItems (ctx )
207
- }
210
+ var spawnCtx context.Context
211
+ p .wg = & sync.WaitGroup {}
212
+ spawnCtx , p .spawnCancel = xcontext .WithCancel (xcontext .ValueOnly (ctx ))
213
+ p .wg .Add (1 )
214
+ go p .spawnItems (spawnCtx )
208
215
209
216
return p
210
217
}
@@ -213,45 +220,55 @@ func New[PT Item[T], T any](
213
220
// It ensures that pool would always have amount of connections equal to configured limit.
214
221
// If item creation ended with error it will be retried infinity with configured interval until success.
215
222
func (p * Pool [PT , T ]) spawnItems (ctx context.Context ) {
216
- spawnLoop:
223
+ defer p . wg . Done ()
217
224
for {
218
225
select {
219
226
case <- ctx .Done ():
220
- break spawnLoop
227
+ return
221
228
case <- p .done :
222
- break spawnLoop
229
+ return
223
230
case <- p .itemTokens :
224
231
// got token, must create item
232
+ createLoop:
225
233
for {
226
- item , err := p . createItem ( ctx )
227
- if err != nil {
228
- select {
229
- case <- ctx . Done () :
230
- break spawnLoop
231
- case <- p . done :
232
- break spawnLoop
233
- case <- time . After ( defaultCreateRetryDelay ):
234
- // try again.
235
- // token must always result in new item and not be lost.
234
+ select {
235
+ case <- ctx . Done ():
236
+ return
237
+ case <- p . done :
238
+ return
239
+ default :
240
+ p . wg . Add ( 1 )
241
+ err := p . trySpawn ( ctx )
242
+ if err == nil {
243
+ break createLoop
236
244
}
237
- } else {
238
- // item is created successfully, put it in queue
239
- select {
240
- case <- ctx .Done ():
241
- break spawnLoop
242
- case <- p .done :
243
- break spawnLoop
244
- case p .queue <- item :
245
- p .stats .Idle ().Inc ()
246
- }
247
-
248
- continue spawnLoop
249
245
}
246
+ // spawn was unsuccessful, need to try again.
247
+ // token must always result in new item and not be lost.
250
248
}
251
249
}
252
250
}
253
251
}
254
252
253
+ func (p * Pool [PT , T ]) trySpawn (ctx context.Context ) error {
254
+ defer p .wg .Done ()
255
+ item , err := p .createItem (ctx )
256
+ if err != nil {
257
+ return err
258
+ }
259
+ // item was created successfully, put it in queue
260
+ select {
261
+ case <- ctx .Done ():
262
+ return nil
263
+ case <- p .done :
264
+ return nil
265
+ case p .queue <- item :
266
+ p .stats .Idle ().Inc ()
267
+ }
268
+
269
+ return nil
270
+ }
271
+
255
272
// defaultCreateItem returns a new item
256
273
func defaultCreateItem [T any , PT Item [T ]](ctx context.Context ) (PT , error ) {
257
274
var item T
@@ -365,15 +382,13 @@ func (p *Pool[PT, T]) getItem(ctx context.Context) (_ PT, finalErr error) {
365
382
if item != nil {
366
383
if item .IsAlive () {
367
384
// item is alive, return it
368
- p .stats .InUse ().Inc ()
369
385
370
386
return item , nil
371
387
}
372
388
// item is not alive
373
389
_ = p .closeItem (ctx , item ) // clean up dead item
374
390
}
375
391
p .itemTokens <- struct {}{} // signal spawn goroutine to create a new item
376
-
377
392
// and try again
378
393
}
379
394
}
@@ -400,7 +415,6 @@ func (p *Pool[PT, T]) putItem(ctx context.Context, item PT) (finalErr error) {
400
415
case <- ctx .Done ():
401
416
return xerrors .WithStackTrace (ctx .Err ())
402
417
default :
403
- p .stats .InUse ().Dec ()
404
418
if item .IsAlive () {
405
419
// put back in the queue
406
420
select {
@@ -455,9 +469,11 @@ func (p *Pool[PT, T]) try(ctx context.Context, f func(ctx context.Context, item
455
469
456
470
return xerrors .WithStackTrace (err )
457
471
}
472
+ p .stats .InUse ().Inc ()
458
473
459
474
defer func () {
460
475
_ = p .putItem (ctx , item )
476
+ p .stats .InUse ().Dec ()
461
477
}()
462
478
463
479
err = f (ctx , item )
@@ -519,10 +535,16 @@ func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) {
519
535
})
520
536
}()
521
537
538
+ // canceling spawner (and any underlying createItem calls)
539
+ p .spawnCancel ()
540
+
522
541
// Only closing done channel.
523
542
// Due to multiple senders queue is not closed here,
524
543
// we're just making sure to drain it fully to close any existing item.
525
544
close (p .done )
545
+
546
+ p .wg .Wait ()
547
+
526
548
var g errgroup.Group
527
549
shutdownLoop:
528
550
for {
0 commit comments