@@ -91,6 +91,18 @@ type Topology struct {
91
91
var _ driver.Deployment = & Topology {}
92
92
var _ driver.Subscriber = & Topology {}
93
93
94
+ type serverSelectionState struct {
95
+ selector description.ServerSelector
96
+ timeoutChan <- chan time.Time
97
+ }
98
+
99
+ func newServerSelectionState (selector description.ServerSelector , timeoutChan <- chan time.Time ) serverSelectionState {
100
+ return serverSelectionState {
101
+ selector : selector ,
102
+ timeoutChan : timeoutChan ,
103
+ }
104
+ }
105
+
94
106
// New creates a new topology.
95
107
func New (opts ... Option ) (* Topology , error ) {
96
108
cfg , err := newConfig (opts ... )
@@ -295,16 +307,39 @@ func (t *Topology) SelectServer(ctx context.Context, ss description.ServerSelect
295
307
defer ssTimeout .Stop ()
296
308
}
297
309
298
- sub , err := t .Subscribe ()
299
- if err != nil {
300
- return nil , err
301
- }
302
- defer t .Unsubscribe (sub )
303
-
310
+ var doneOnce bool
311
+ var sub * driver.Subscription
312
+ selectionState := newServerSelectionState (ss , ssTimeoutCh )
304
313
for {
305
- suitable , err := t .selectServer (ctx , sub .Updates , ss , ssTimeoutCh )
306
- if err != nil {
307
- return nil , err
314
+ var suitable []description.Server
315
+ var selectErr error
316
+
317
+ if ! doneOnce {
318
+ // for the first pass, select a server from the current description.
319
+ // this improves selection speed for up-to-date topology descriptions.
320
+ suitable , selectErr = t .selectServerFromDescription (ctx , t .Description (), selectionState )
321
+ doneOnce = true
322
+ } else {
323
+ // if the first pass didn't select a server, the previous description did not contain a suitable server, so
324
+ // we subscribe to the topology and attempt to obtain a server from that subscription
325
+ if sub == nil {
326
+ var err error
327
+ sub , err = t .Subscribe ()
328
+ if err != nil {
329
+ return nil , err
330
+ }
331
+ defer t .Unsubscribe (sub )
332
+ }
333
+
334
+ suitable , selectErr = t .selectServerFromSubscription (ctx , sub .Updates , selectionState )
335
+ }
336
+ if selectErr != nil {
337
+ return nil , selectErr
338
+ }
339
+
340
+ if len (suitable ) == 0 {
341
+ // try again if there are no servers available
342
+ continue
308
343
}
309
344
310
345
selected := suitable [rand .Intn (len (suitable ))]
@@ -344,8 +379,9 @@ func (t *Topology) SelectServerLegacy(ctx context.Context, ss description.Server
344
379
}
345
380
defer t .Unsubscribe (sub )
346
381
382
+ selectionState := newServerSelectionState (ss , ssTimeoutCh )
347
383
for {
348
- suitable , err := t .selectServer (ctx , sub .Updates , ss , ssTimeoutCh )
384
+ suitable , err := t .selectServerFromSubscription (ctx , sub .Updates , selectionState )
349
385
if err != nil {
350
386
return nil , err
351
387
}
@@ -390,39 +426,60 @@ func wrapServerSelectionError(err error, t *Topology) error {
390
426
return fmt .Errorf ("server selection error: %v, current topology: { %s }" , err , t .String ())
391
427
}
392
428
393
- // selectServer is the core piece of server selection. It handles getting
394
- // topology descriptions and running sever selection on those descriptions.
395
- func (t * Topology ) selectServer (ctx context.Context , subscriptionCh <- chan description.Topology , ss description.ServerSelector , timeoutCh <- chan time.Time ) ([]description.Server , error ) {
429
+ // selectServerFromSubscription loops until a topology description is available for server selection. It returns
430
+ // when the given context expires, server selection timeout is reached, or a description containing a selectable
431
+ // server is available.
432
+ func (t * Topology ) selectServerFromSubscription (ctx context.Context , subscriptionCh <- chan description.Topology ,
433
+ selectionState serverSelectionState ) ([]description.Server , error ) {
434
+
396
435
var current description.Topology
397
436
for {
398
437
select {
399
438
case <- ctx .Done ():
400
439
return nil , ctx .Err ()
401
- case <- timeoutCh :
440
+ case <- selectionState . timeoutChan :
402
441
return nil , wrapServerSelectionError (ErrServerSelectionTimeout , t )
403
442
case current = <- subscriptionCh :
404
443
}
405
444
406
- var allowed []description.Server
407
- for _ , s := range current .Servers {
408
- if s .Kind != description .Unknown {
409
- allowed = append (allowed , s )
410
- }
411
- }
412
-
413
- suitable , err := ss .SelectServer (current , allowed )
445
+ suitable , err := t .selectServerFromDescription (ctx , current , selectionState )
414
446
if err != nil {
415
- return nil , wrapServerSelectionError ( err , t )
447
+ return nil , err
416
448
}
417
449
418
450
if len (suitable ) > 0 {
419
451
return suitable , nil
420
452
}
421
-
422
453
t .RequestImmediateCheck ()
423
454
}
424
455
}
425
456
457
+ // selectServerFromDescription process the given topology description and returns a slice of suitable servers.
458
+ func (t * Topology ) selectServerFromDescription (ctx context.Context , desc description.Topology ,
459
+ selectionState serverSelectionState ) ([]description.Server , error ) {
460
+
461
+ select {
462
+ case <- ctx .Done ():
463
+ return nil , ctx .Err ()
464
+ case <- selectionState .timeoutChan :
465
+ return nil , wrapServerSelectionError (ErrServerSelectionTimeout , t )
466
+ default :
467
+ }
468
+
469
+ var allowed []description.Server
470
+ for _ , s := range desc .Servers {
471
+ if s .Kind != description .Unknown {
472
+ allowed = append (allowed , s )
473
+ }
474
+ }
475
+
476
+ suitable , err := selectionState .selector .SelectServer (desc , allowed )
477
+ if err != nil {
478
+ return nil , wrapServerSelectionError (err , t )
479
+ }
480
+ return suitable , nil
481
+ }
482
+
426
483
func (t * Topology ) pollSRVRecords () {
427
484
defer t .pollingwg .Done ()
428
485
0 commit comments