@@ -2,6 +2,7 @@ package pool
22
33import  (
44	"context" 
5+ 	"sync" 
56	"time" 
67
78	"golang.org/x/sync/errgroup" 
@@ -60,6 +61,10 @@ type (
6061		done  chan  struct {}
6162
6263		stats  * safeStats 
64+ 
65+ 		spawnCancel  context.CancelFunc 
66+ 
67+ 		wg  * sync.WaitGroup 
6368	}
6469	option [PT  Item [T ], T  any ] func (p  * Pool [PT , T ])
6570)
@@ -202,9 +207,11 @@ func New[PT Item[T], T any](
202207		onChange : p .trace .OnChange ,
203208	}
204209
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 )
208215
209216	return  p 
210217}
@@ -213,45 +220,55 @@ func New[PT Item[T], T any](
213220// It ensures that pool would always have amount of connections equal to configured limit. 
214221// If item creation ended with error it will be retried infinity with configured interval until success. 
215222func  (p  * Pool [PT , T ]) spawnItems (ctx  context.Context ) {
216- spawnLoop: 
223+ 	 defer   p . wg . Done () 
217224	for  {
218225		select  {
219226		case  <- ctx .Done ():
220- 			break  spawnLoop 
227+ 			return 
221228		case  <- p .done :
222- 			break  spawnLoop 
229+ 			return 
223230		case  <- p .itemTokens :
224231			// got token, must create item 
232+ 		createLoop:
225233			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 
236244					}
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
249245				}
246+ 				// spawn was unsuccessful, need to try again. 
247+ 				// token must always result in new item and not be lost. 
250248			}
251249		}
252250	}
253251}
254252
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+ 
255272// defaultCreateItem returns a new item 
256273func  defaultCreateItem [T  any , PT  Item [T ]](ctx  context.Context ) (PT , error ) {
257274	var  item  T 
@@ -365,15 +382,13 @@ func (p *Pool[PT, T]) getItem(ctx context.Context) (_ PT, finalErr error) {
365382			if  item  !=  nil  {
366383				if  item .IsAlive () {
367384					// item is alive, return it 
368- 					p .stats .InUse ().Inc ()
369385
370386					return  item , nil 
371387				}
372388				// item is not alive 
373389				_  =  p .closeItem (ctx , item ) // clean up dead item 
374390			}
375391			p .itemTokens  <-  struct {}{} // signal spawn goroutine to create a new item 
376- 
377392			// and try again 
378393		}
379394	}
@@ -400,7 +415,6 @@ func (p *Pool[PT, T]) putItem(ctx context.Context, item PT) (finalErr error) {
400415	case  <- ctx .Done ():
401416		return  xerrors .WithStackTrace (ctx .Err ())
402417	default :
403- 		p .stats .InUse ().Dec ()
404418		if  item .IsAlive () {
405419			// put back in the queue 
406420			select  {
@@ -455,9 +469,11 @@ func (p *Pool[PT, T]) try(ctx context.Context, f func(ctx context.Context, item
455469
456470		return  xerrors .WithStackTrace (err )
457471	}
472+ 	p .stats .InUse ().Inc ()
458473
459474	defer  func () {
460475		_  =  p .putItem (ctx , item )
476+ 		p .stats .InUse ().Dec ()
461477	}()
462478
463479	err  =  f (ctx , item )
@@ -519,10 +535,16 @@ func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) {
519535		})
520536	}()
521537
538+ 	// canceling spawner (and any underlying createItem calls) 
539+ 	p .spawnCancel ()
540+ 
522541	// Only closing done channel. 
523542	// Due to multiple senders queue is not closed here, 
524543	// we're just making sure to drain it fully to close any existing item. 
525544	close (p .done )
545+ 
546+ 	p .wg .Wait ()
547+ 
526548	var  g  errgroup.Group 
527549shutdownLoop:
528550	for  {
0 commit comments