@@ -75,12 +75,6 @@ type Pooler interface {
7575 Put (context.Context , * Conn )
7676 Remove (context.Context , * Conn , error )
7777
78- // RemoveWithoutTurn removes a connection from the pool without freeing a turn.
79- // This should be used when removing a connection from a context that didn't acquire
80- // a turn via Get() (e.g., background workers, cleanup tasks).
81- // For normal removal after Get(), use Remove() instead.
82- RemoveWithoutTurn (context.Context , * Conn , error )
83-
8478 Len () int
8579 IdleLen () int
8680 Stats () * Stats
@@ -102,6 +96,7 @@ type Options struct {
10296
10397 PoolFIFO bool
10498 PoolSize int32
99+ MaxConcurrentDials int
105100 DialTimeout time.Duration
106101 PoolTimeout time.Duration
107102 MinIdleConns int32
@@ -130,6 +125,9 @@ type ConnPool struct {
130125 dialErrorsNum uint32 // atomic
131126 lastDialError atomic.Value
132127
128+ queue chan struct {}
129+ dialsInProgress chan struct {}
130+ dialsQueue * wantConnQueue
133131 // Fast atomic semaphore for connection limiting
134132 // Replaces the old channel-based queue for better performance
135133 semaphore * internal.FastSemaphore
@@ -167,8 +165,11 @@ func NewConnPool(opt *Options) *ConnPool {
167165 p := & ConnPool {
168166 cfg : opt ,
169167 semaphore : internal .NewFastSemaphore (semSize ),
170- conns : make (map [uint64 ]* Conn ),
171- idleConns : make ([]* Conn , 0 , opt .PoolSize ),
168+ queue : make (chan struct {}, opt .PoolSize ),
169+ conns : make (map [uint64 ]* Conn ),
170+ dialsInProgress : make (chan struct {}, opt .MaxConcurrentDials ),
171+ dialsQueue : newWantConnQueue (),
172+ idleConns : make ([]* Conn , 0 , opt .PoolSize ),
172173 }
173174
174175 // Only create MinIdleConns if explicitly requested (> 0)
@@ -517,9 +518,8 @@ func (p *ConnPool) getConn(ctx context.Context) (*Conn, error) {
517518
518519 atomic .AddUint32 (& p .stats .Misses , 1 )
519520
520- newcn , err := p .newConn (ctx , true )
521+ newcn , err := p .queuedNewConn (ctx )
521522 if err != nil {
522- p .freeTurn ()
523523 return nil , err
524524 }
525525
@@ -538,6 +538,97 @@ func (p *ConnPool) getConn(ctx context.Context) (*Conn, error) {
538538 return newcn , nil
539539}
540540
541+ func (p * ConnPool ) queuedNewConn (ctx context.Context ) (* Conn , error ) {
542+ select {
543+ case p .dialsInProgress <- struct {}{}:
544+ // Got permission, proceed to create connection
545+ case <- ctx .Done ():
546+ p .freeTurn ()
547+ return nil , ctx .Err ()
548+ }
549+
550+ dialCtx , cancel := context .WithTimeout (context .Background (), p .cfg .DialTimeout )
551+
552+ w := & wantConn {
553+ ctx : dialCtx ,
554+ cancelCtx : cancel ,
555+ result : make (chan wantConnResult , 1 ),
556+ }
557+ var err error
558+ defer func () {
559+ if err != nil {
560+ if cn := w .cancel (); cn != nil {
561+ p .putIdleConn (ctx , cn )
562+ p .freeTurn ()
563+ }
564+ }
565+ }()
566+
567+ p .dialsQueue .enqueue (w )
568+
569+ go func (w * wantConn ) {
570+ var freeTurnCalled bool
571+ defer func () {
572+ if err := recover (); err != nil {
573+ if ! freeTurnCalled {
574+ p .freeTurn ()
575+ }
576+ internal .Logger .Printf (context .Background (), "queuedNewConn panic: %+v" , err )
577+ }
578+ }()
579+
580+ defer w .cancelCtx ()
581+ defer func () { <- p .dialsInProgress }() // Release connection creation permission
582+
583+ dialCtx := w .getCtxForDial ()
584+ cn , cnErr := p .newConn (dialCtx , true )
585+ delivered := w .tryDeliver (cn , cnErr )
586+ if cnErr == nil && delivered {
587+ return
588+ } else if cnErr == nil && ! delivered {
589+ p .putIdleConn (dialCtx , cn )
590+ p .freeTurn ()
591+ freeTurnCalled = true
592+ } else {
593+ p .freeTurn ()
594+ freeTurnCalled = true
595+ }
596+ }(w )
597+
598+ select {
599+ case <- ctx .Done ():
600+ err = ctx .Err ()
601+ return nil , err
602+ case result := <- w .result :
603+ err = result .err
604+ return result .cn , err
605+ }
606+ }
607+
608+ func (p * ConnPool ) putIdleConn (ctx context.Context , cn * Conn ) {
609+ for {
610+ w , ok := p .dialsQueue .dequeue ()
611+ if ! ok {
612+ break
613+ }
614+ if w .tryDeliver (cn , nil ) {
615+ return
616+ }
617+ }
618+
619+ p .connsMu .Lock ()
620+ defer p .connsMu .Unlock ()
621+
622+ if p .closed () {
623+ _ = cn .Close ()
624+ return
625+ }
626+
627+ // poolSize is increased in newConn
628+ p .idleConns = append (p .idleConns , cn )
629+ p .idleConnsLen .Add (1 )
630+ }
631+
541632func (p * ConnPool ) waitTurn (ctx context.Context ) error {
542633 // Fast path: check context first
543634 select {
0 commit comments