@@ -62,7 +62,7 @@ type pool struct {
62
62
maxConnecting uint64
63
63
monitor * event.PoolMonitor
64
64
65
- opts []ConnectionOption
65
+ connOpts []ConnectionOption
66
66
generation * poolGenerationMap
67
67
68
68
maintainInterval time.Duration // maintainInterval is the maintain() loop interval.
@@ -94,12 +94,10 @@ func connectionPerished(conn *connection) (string, bool) {
94
94
return "" , false
95
95
}
96
96
97
- // newPool creates a new pool. It will use the
98
- // provided options when creating connections.
99
- func newPool (config poolConfig , connOpts ... ConnectionOption ) (* pool , error ) {
100
- opts := connOpts
97
+ // newPool creates a new pool. It will use the provided options when creating connections.
98
+ func newPool (config poolConfig , connOpts ... ConnectionOption ) * pool {
101
99
if config .MaxIdleTime != time .Duration (0 ) {
102
- opts = append (opts , WithIdleTimeout (func (_ time.Duration ) time.Duration { return config .MaxIdleTime }))
100
+ connOpts = append (connOpts , WithIdleTimeout (func (_ time.Duration ) time.Duration { return config .MaxIdleTime }))
103
101
}
104
102
105
103
pool := & pool {
@@ -108,29 +106,30 @@ func newPool(config poolConfig, connOpts ...ConnectionOption) (*pool, error) {
108
106
maxSize : config .MaxPoolSize ,
109
107
maxConnecting : 2 ,
110
108
monitor : config .PoolMonitor ,
111
- opts : opts ,
109
+ connOpts : connOpts ,
112
110
generation : newPoolGenerationMap (),
113
111
connected : disconnected ,
114
112
maintainInterval : 10 * time .Second ,
115
113
connsCond : sync .NewCond (& sync.Mutex {}),
116
114
conns : make (map [uint64 ]* connection , config .MaxPoolSize ),
117
115
idleConns : make ([]* connection , 0 , config .MaxPoolSize ),
118
116
}
119
- pool .opts = append (pool .opts , withGenerationNumberFn (func (_ generationNumberFn ) generationNumberFn { return pool .getGenerationForNewConnection }))
117
+ pool .connOpts = append (pool .connOpts , withGenerationNumberFn (func (_ generationNumberFn ) generationNumberFn { return pool .getGenerationForNewConnection }))
120
118
121
119
if pool .monitor != nil {
122
120
pool .monitor .Event (& event.PoolEvent {
123
121
Type : event .PoolCreated ,
124
122
PoolOptions : & event.MonitorPoolOptions {
125
- MaxPoolSize : config .MaxPoolSize ,
126
- MinPoolSize : config .MinPoolSize ,
123
+ MaxPoolSize : config .MaxPoolSize ,
124
+ MinPoolSize : config .MinPoolSize ,
125
+ // TODO: We don't use this value, should we publish it?
127
126
WaitQueueTimeoutMS : uint64 (config .MaxIdleTime ) / uint64 (time .Millisecond ),
128
127
},
129
128
Address : pool .address .String (),
130
129
})
131
130
}
132
131
133
- return pool , nil
132
+ return pool
134
133
}
135
134
136
135
// stale checks if a given connection's generation is below the generation of the pool
@@ -176,7 +175,10 @@ func (p *pool) disconnect(ctx context.Context) error {
176
175
}
177
176
178
177
// Call cancelBackground() to exit the maintain() background goroutine and broadcast to the
179
- // connsCond to wake up all createConnections() goroutines.
178
+ // connsCond to wake up all createConnections() goroutines. We must hold the connsCond lock here
179
+ // because we're changing the condition by cancelling the "background goroutine" Context, even
180
+ // tho cancelling the Context is also synchronized by a lock. Otherwise, we run into an
181
+ // intermittent bug that prevents the createConnections() goroutines from exiting.
180
182
p .connsCond .L .Lock ()
181
183
p .cancelBackground ()
182
184
p .connsCond .L .Unlock ()
@@ -256,7 +258,6 @@ func (p *pool) disconnect(ctx context.Context) error {
256
258
w .tryDeliver (nil , ErrPoolDisconnected )
257
259
}
258
260
259
- // Mark the pool state as disconnected.
260
261
atomic .StoreInt64 (& p .connected , disconnected )
261
262
262
263
if p .monitor != nil {
@@ -290,6 +291,7 @@ func (p *pool) unpinConnectionFromTransaction() {
290
291
// checkOut checks out a connection from the pool. If an idle connection is not available, the
291
292
// checkOut enters a queue waiting for either the next idle or new connection. If the pool is
292
293
// disconnected, checkOut returns an error.
294
+ // Based partially on https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/transport.go;l=1324
293
295
func (p * pool ) checkOut (ctx context.Context ) (conn * connection , err error ) {
294
296
if atomic .LoadInt64 (& p .connected ) != connected {
295
297
if p .monitor != nil {
@@ -299,21 +301,27 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
299
301
Reason : event .ReasonPoolClosed ,
300
302
})
301
303
}
302
- err = ErrPoolDisconnected
303
- return
304
+ return nil , ErrPoolDisconnected
304
305
}
305
306
306
307
if ctx == nil {
307
308
ctx = context .Background ()
308
309
}
309
310
311
+ // Create a wantConn, which we will use to request an existing idle or new connection. Always
312
+ // cancel the wantConn if checkOut() returned an error to make sure any delivered connections
313
+ // are returned to the pool (e.g. if a connection was delivered immediately after the Context
314
+ // timed out).
310
315
w := newWantConn ()
311
316
defer func () {
312
317
if err != nil {
313
318
w .cancel (p , err )
314
319
}
315
320
}()
316
321
322
+ // Get in the queue for an idle connection. If queueForIdleConn returns true, it was able to
323
+ // immediately deliver an idle connection to the wantConn, so we can return the connection or
324
+ // error from the wantConn without waiting for "ready".
317
325
if delivered := p .queueForIdleConn (w ); delivered {
318
326
if w .err != nil {
319
327
if p .monitor != nil {
@@ -323,8 +331,7 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
323
331
Reason : event .ReasonConnectionErrored ,
324
332
})
325
333
}
326
- err = w .err
327
- return
334
+ return nil , w .err
328
335
}
329
336
330
337
if p .monitor != nil {
@@ -334,12 +341,14 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
334
341
ConnectionID : w .conn .poolID ,
335
342
})
336
343
}
337
- conn = w .conn
338
- return
344
+ return w .conn , nil
339
345
}
340
346
347
+ // If we didn't get an immediately available idle connection, also get in the queue for a new
348
+ // connection while we're waiting for an idle connection.
341
349
p .queueForNewConn (w )
342
350
351
+ // Wait for either the wantConn to be ready or for the Context to time out.
343
352
select {
344
353
case <- w .ready :
345
354
if w .err != nil {
@@ -350,8 +359,7 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
350
359
Reason : event .ReasonConnectionErrored ,
351
360
})
352
361
}
353
- err = w .err
354
- return
362
+ return nil , w .err
355
363
}
356
364
357
365
if p .monitor != nil {
@@ -361,8 +369,7 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
361
369
ConnectionID : w .conn .poolID ,
362
370
})
363
371
}
364
- conn = w .conn
365
- return
372
+ return w .conn , nil
366
373
case <- ctx .Done ():
367
374
if p .monitor != nil {
368
375
p .monitor .Event (& event.PoolEvent {
@@ -371,14 +378,13 @@ func (p *pool) checkOut(ctx context.Context) (conn *connection, err error) {
371
378
Reason : event .ReasonTimedOut ,
372
379
})
373
380
}
374
- err = WaitQueueTimeoutError {
381
+ return nil , WaitQueueTimeoutError {
375
382
Wrapped : ctx .Err (),
376
383
PinnedCursorConnections : atomic .LoadUint64 (& p .pinnedCursorConnections ),
377
384
PinnedTransactionConnections : atomic .LoadUint64 (& p .pinnedTransactionConnections ),
378
385
maxPoolSize : p .maxSize ,
379
386
totalConnectionCount : p .totalConnectionCount (),
380
387
}
381
- return
382
388
}
383
389
}
384
390
@@ -407,6 +413,10 @@ func (p *pool) getGenerationForNewConnection(serviceID *primitive.ObjectID) uint
407
413
408
414
// removeConnection removes a connection from the pool and emits a "ConnectionClosed" event.
409
415
func (p * pool ) removeConnection (conn * connection , reason string ) error {
416
+ if conn == nil {
417
+ return nil
418
+ }
419
+
410
420
if conn .pool != p {
411
421
return ErrWrongPool
412
422
}
@@ -614,7 +624,7 @@ func (p *pool) createConnections(ctx context.Context, wg *sync.WaitGroup) {
614
624
return nil , nil , false
615
625
}
616
626
617
- conn , err := newConnection (p .address , p .opts ... )
627
+ conn , err := newConnection (p .address , p .connOpts ... )
618
628
if err != nil {
619
629
w .tryDeliver (nil , err )
620
630
return nil , nil , false
@@ -779,7 +789,7 @@ func compact(arr []*connection) []*connection {
779
789
// The conn may be gotten by creating a new connection or by finding an idle connection, or a
780
790
// cancellation may make the conn no longer wanted. These three options are racing against each
781
791
// other and use wantConn to coordinate and agree about the winning outcome.
782
- // Based on https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/transport.go;l=1174-1240;bpv=0;bpt=1
792
+ // Based on https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/transport.go;l=1174-1240
783
793
type wantConn struct {
784
794
ready chan struct {}
785
795
@@ -841,7 +851,7 @@ func (w *wantConn) cancel(p *pool, err error) {
841
851
}
842
852
843
853
// A wantConnQueue is a queue of wantConns.
844
- // Based on https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/transport.go;l=1242-1306;bpv=0;bpt=1
854
+ // Based on https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/transport.go;l=1242-1306
845
855
type wantConnQueue struct {
846
856
// This is a queue, not a deque.
847
857
// It is split into two stages - head[headPos:] and tail.
0 commit comments