@@ -491,6 +491,141 @@ func TestPoolResize(t *testing.T) {
491491 pool .Stop ().Wait ()
492492}
493493
494+ func TestPoolResizeBeforeWorkersRetire (t * testing.T ) {
495+ for _ , subpool := range []bool {false , true } {
496+ for _ , test := range []struct {
497+ name string
498+ retired int
499+ maxWorkers int
500+ running int64
501+ startedQueued int
502+ }{
503+ {name : "still_above_limit" , maxWorkers : 3 , running : 5 },
504+ {name : "partially_retired" , retired : 2 , maxWorkers : 4 , running : 4 , startedQueued : 1 },
505+ {name : "grow_above_original_limit" , maxWorkers : 7 , running : 7 , startedQueued : 2 },
506+ } {
507+ name := test .name
508+ if subpool {
509+ name = "subpool/" + name
510+ }
511+ t .Run (name , func (t * testing.T ) {
512+ var parent , pool Pool
513+ if subpool {
514+ parent = NewPool (10 )
515+ pool = parent .NewSubpool (5 )
516+ } else {
517+ pool = NewPool (5 )
518+ }
519+
520+ started := make (chan struct {}, 5 )
521+ queuedStarted := make (chan struct {}, 2 )
522+ releaseQueued := make (chan struct {})
523+ releases := make ([]chan struct {}, 5 )
524+ for i := range releases {
525+ releases [i ] = make (chan struct {})
526+ }
527+ released := 0
528+ t .Cleanup (func () {
529+ for _ , release := range releases [released :] {
530+ close (release )
531+ }
532+ close (releaseQueued )
533+ pool .StopAndWait ()
534+ if parent != nil {
535+ parent .StopAndWait ()
536+ }
537+ })
538+
539+ for _ , release := range releases {
540+ release := release
541+ pool .Submit (func () {
542+ started <- struct {}{}
543+ <- release
544+ })
545+ }
546+ for range releases {
547+ select {
548+ case <- started :
549+ case <- time .After (2 * time .Second ):
550+ t .Fatal ("timed out waiting for initial workers" )
551+ }
552+ }
553+ for i := 0 ; i < 2 ; i ++ {
554+ pool .Submit (func () {
555+ queuedStarted <- struct {}{}
556+ <- releaseQueued
557+ })
558+ }
559+
560+ pool .Resize (1 )
561+ for _ , release := range releases [:test .retired ] {
562+ close (release )
563+ released ++
564+ }
565+ deadline := time .Now ().Add (2 * time .Second )
566+ for pool .RunningWorkers () != int64 (5 - test .retired ) {
567+ if time .Now ().After (deadline ) {
568+ t .Fatal ("timed out waiting for workers to retire" )
569+ }
570+ time .Sleep (time .Millisecond )
571+ }
572+
573+ pool .Resize (test .maxWorkers )
574+ assert .Equal (t , test .maxWorkers , pool .MaxConcurrency ())
575+ assert .Equal (t , test .running , pool .RunningWorkers ())
576+ for i := 0 ; i < test .startedQueued ; i ++ {
577+ select {
578+ case <- queuedStarted :
579+ case <- time .After (2 * time .Second ):
580+ t .Fatal ("timed out waiting for queued tasks" )
581+ }
582+ }
583+ assert .Equal (t , test .startedQueued , 2 - int (pool .WaitingTasks ()))
584+ if parent != nil {
585+ assert .Equal (t , uint64 (5 + 2 * test .startedQueued ), parent .SubmittedTasks ())
586+ }
587+ })
588+ }
589+ }
590+ }
591+
592+ func TestSubpoolResizeBeforeWorkersRetireWithFullParent (t * testing.T ) {
593+ parent := NewPool (5 , WithQueueSize (0 ))
594+ pool := parent .NewSubpool (5 , WithQueueSize (2 ))
595+ started := make (chan struct {}, 5 )
596+ release := make (chan struct {})
597+ resized := make (chan struct {})
598+ t .Cleanup (func () {
599+ close (release )
600+ <- resized
601+ pool .StopAndWait ()
602+ parent .StopAndWait ()
603+ })
604+
605+ for i := 0 ; i < 5 ; i ++ {
606+ pool .Submit (func () {
607+ started <- struct {}{}
608+ <- release
609+ })
610+ }
611+ for i := 0 ; i < 5 ; i ++ {
612+ <- started
613+ }
614+ pool .Submit (func () {})
615+ pool .Submit (func () {})
616+ pool .Resize (1 )
617+ go func () {
618+ pool .Resize (3 )
619+ close (resized )
620+ }()
621+
622+ select {
623+ case <- resized :
624+ case <- time .After (2 * time .Second ):
625+ t .Fatal ("resize blocked on the full parent while existing workers exceed the new limit" )
626+ }
627+ }
628+
494629func TestPoolResizeWithZeroMaxConcurrency (t * testing.T ) {
495630 pool := NewPool (10 )
496631
0 commit comments