@@ -147,12 +147,16 @@ type Pool struct {
147147 // WithGroup("tarantool") namespace.
148148 rawLogger * slog.Logger
149149
150- state state
151- done chan struct {}
152- roPool * roundRobinStrategy
153- rwPool * roundRobinStrategy
154- anyPool * roundRobinStrategy
155- poolsMutex sync.RWMutex
150+ state state
151+ done chan struct {}
152+ roPool * roundRobinStrategy
153+ rwPool * roundRobinStrategy
154+ anyPool * roundRobinStrategy
155+ poolsMutex sync.RWMutex
156+ // connEvent is closed and replaced with a fresh channel whenever the set
157+ // of active connections changes. It lets WaitConnected block until the
158+ // pool becomes usable. Guarded by poolsMutex.
159+ connEvent chan struct {}
156160 watcherContainer watcherContainer
157161}
158162
@@ -204,6 +208,13 @@ func newEndpoint(name string, dialer tarantool.Dialer, opts tarantool.Opts) *end
204208
205209// NewWithOpts creates pool for instances with specified instances and
206210// opts. Instances must have unique names.
211+ //
212+ // It dials the instances concurrently and returns once the first connection
213+ // is established; ctx bounds that wait. If ctx is done before any instance
214+ // connects, an error is returned. If ctx has no deadline and every instance
215+ // is unreachable, a pool with no live connections is returned (the pool keeps
216+ // reconnecting in the background) — use Pool.WaitConnected if you need a
217+ // usable connection before proceeding.
207218func NewWithOpts (ctx context.Context , instances []Instance ,
208219 opts Opts ) (* Pool , error ) {
209220 unique := make (map [string ]bool )
@@ -232,13 +243,14 @@ func NewWithOpts(ctx context.Context, instances []Instance,
232243 anyPool := newRoundRobinStrategy (size )
233244
234245 p := & Pool {
235- ends : make (map [string ]* endpoint ),
236- opts : opts ,
237- state : connectedState ,
238- done : make (chan struct {}),
239- rwPool : rwPool ,
240- roPool : roPool ,
241- anyPool : anyPool ,
246+ ends : make (map [string ]* endpoint ),
247+ opts : opts ,
248+ state : connectedState ,
249+ done : make (chan struct {}),
250+ rwPool : rwPool ,
251+ roPool : roPool ,
252+ anyPool : anyPool ,
253+ connEvent : make (chan struct {}),
242254 }
243255
244256 if opts .Logger == nil {
@@ -248,33 +260,26 @@ func NewWithOpts(ctx context.Context, instances []Instance,
248260 p .rawLogger = opts .Logger
249261 }
250262
263+ // Dial all instances concurrently. Block until the first one connects
264+ // (so the returned pool is immediately usable), then cancel the slower
265+ // dials and let the background controllers reconnect them. The only
266+ // deadline is ctx: if it expires before anything connects, fail; if it
267+ // has no deadline and every dial fails fast, return a pool with no live
268+ // connections (the controllers keep retrying) — callers that require a
269+ // connection should follow up with Pool.WaitConnected.
251270 fillCtx , fillCancel := context .WithCancel (ctx )
252271 defer fillCancel ()
253272
254- var timeout <- chan time.Time
255-
256- timeout = make (chan time.Time )
257273 filled := p .fillPools (fillCtx , instances )
258- done := 0
259- success := len (instances ) == 0
260-
261- for done < len (instances ) {
262- select {
263- case <- timeout :
274+ connected := len (instances ) == 0
275+ for range instances {
276+ if err := <- filled ; err == nil && ! connected {
277+ connected = true
264278 fillCancel ()
265- // To be sure that the branch is called only once.
266- timeout = make (chan time.Time )
267- case err := <- filled :
268- done ++
269-
270- if err == nil && ! success {
271- timeout = time .After (opts .CheckTimeout )
272- success = true
273- }
274279 }
275280 }
276281
277- if ! success && ctx .Err () != nil {
282+ if ! connected && ctx .Err () != nil {
278283 p .state .set (closedState )
279284 return nil , ctx .Err ()
280285 }
@@ -297,30 +302,68 @@ func New(ctx context.Context, instances []Instance) (*Pool, error) {
297302 return NewWithOpts (ctx , instances , opts )
298303}
299304
300- // ConnectedNow gets connected status of pool.
301- func (p * Pool ) ConnectedNow (mode Mode ) (bool , error ) {
302- p .poolsMutex .RLock ()
303- defer p .poolsMutex .RUnlock ()
304-
305- if p .state .get () != connectedState {
306- return false , nil
307- }
305+ // hasConnection reports whether the pool currently holds a connection
306+ // satisfying mode. The caller must hold poolsMutex (read or write).
307+ func (p * Pool ) hasConnection (mode Mode ) (bool , error ) {
308308 switch mode {
309309 case ModeAny :
310310 return ! p .anyPool .IsEmpty (), nil
311311 case ModeRW :
312312 return ! p .rwPool .IsEmpty (), nil
313313 case ModeRO :
314314 return ! p .roPool .IsEmpty (), nil
315- case ModePreferRW :
316- fallthrough
317- case ModePreferRO :
315+ case ModePreferRW , ModePreferRO :
318316 return ! p .rwPool .IsEmpty () || ! p .roPool .IsEmpty (), nil
319317 default :
320318 return false , ErrNoHealthyInstance
321319 }
322320}
323321
322+ // ConnectedNow gets connected status of pool.
323+ func (p * Pool ) ConnectedNow (mode Mode ) (bool , error ) {
324+ p .poolsMutex .RLock ()
325+ defer p .poolsMutex .RUnlock ()
326+
327+ if p .state .get () != connectedState {
328+ return false , nil
329+ }
330+ return p .hasConnection (mode )
331+ }
332+
333+ // WaitConnected blocks until the pool holds at least one connection
334+ // satisfying mode, returning nil. It returns ctx.Err() if ctx is done first
335+ // and ErrClosed if the pool is (or becomes) closed.
336+ //
337+ // NewWithOpts does not fail when instances are unreachable: it returns a pool
338+ // that may have no live connections yet and keeps reconnecting in the
339+ // background. Callers that need a usable connection before proceeding should
340+ // use WaitConnected (typically with a deadline on ctx) instead of relying on
341+ // the racy ConnectedNow snapshot.
342+ func (p * Pool ) WaitConnected (ctx context.Context , mode Mode ) error {
343+ for {
344+ p .poolsMutex .RLock ()
345+ closed := p .state .get () != connectedState
346+ ok , err := p .hasConnection (mode )
347+ event := p .connEvent
348+ p .poolsMutex .RUnlock ()
349+
350+ switch {
351+ case closed :
352+ return ErrClosed
353+ case err != nil :
354+ return err
355+ case ok :
356+ return nil
357+ }
358+
359+ select {
360+ case <- ctx .Done ():
361+ return ctx .Err ()
362+ case <- event :
363+ }
364+ }
365+ }
366+
324367// ConfiguredTimeout gets timeout of current connection.
325368func (p * Pool ) ConfiguredTimeout (mode Mode ) (time.Duration , error ) {
326369 conn , err := p .getNextConnection (mode )
@@ -457,6 +500,10 @@ func (p *Pool) Close() error {
457500 close (s .close )
458501 }
459502 p .endsMutex .RUnlock ()
503+
504+ p .poolsMutex .Lock ()
505+ p .notifyConnEvent ()
506+ p .poolsMutex .Unlock ()
460507 }
461508
462509 return p .waitClose ()
@@ -472,6 +519,10 @@ func (p *Pool) CloseGraceful() error {
472519 close (s .shutdown )
473520 }
474521 p .endsMutex .RUnlock ()
522+
523+ p .poolsMutex .Lock ()
524+ p .notifyConnEvent ()
525+ p .poolsMutex .Unlock ()
475526 }
476527
477528 return p .waitClose ()
@@ -692,11 +743,20 @@ func (p *Pool) getConnectionFromPool(name string) (*tarantool.Connection, Role)
692743 return p .anyPool .GetConnection (name ), RoleUnknown
693744}
694745
746+ // notifyConnEvent wakes WaitConnected callers by closing the current
747+ // connEvent channel and installing a fresh one. The caller must hold
748+ // poolsMutex for writing.
749+ func (p * Pool ) notifyConnEvent () {
750+ close (p .connEvent )
751+ p .connEvent = make (chan struct {})
752+ }
753+
695754func (p * Pool ) deleteConnection (name string ) {
696755 if conn := p .anyPool .DeleteConnection (name ); conn != nil {
697756 if conn := p .rwPool .DeleteConnection (name ); conn == nil {
698757 p .roPool .DeleteConnection (name )
699758 }
759+ p .notifyConnEvent ()
700760 // The internal connection deinitialization.
701761 p .watcherContainer .mutex .RLock ()
702762 defer p .watcherContainer .mutex .RUnlock ()
@@ -754,6 +814,7 @@ func (p *Pool) addConnection(name string,
754814 case RoleReplica :
755815 p .roPool .AddConnection (name , conn )
756816 }
817+ p .notifyConnEvent ()
757818 return nil
758819}
759820
0 commit comments