Skip to content

Commit 6f63c26

Browse files
authored
GODRIVER-2218 Bump connection idle deadline on pool checkIn. (#869)
1 parent 001e550 commit 6f63c26

File tree

4 files changed

+77
-38
lines changed

4 files changed

+77
-38
lines changed

x/mongo/driver/topology/cmap_prose_test.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -186,40 +186,6 @@ func TestCMAPProse(t *testing.T) {
186186
assert.Equal(t, event.ReasonError, evt.Reason, "expected reason %q, got %q",
187187
event.ReasonError, evt.Reason)
188188
})
189-
t.Run("expired connection", func(t *testing.T) {
190-
// If the connection being returned to the pool is expired, it should be removed from the pool and an
191-
// event should be published.
192-
clearEvents()
193-
194-
var dialer DialerFunc = func(context.Context, string, string) (net.Conn, error) {
195-
return &testNetConn{}, nil
196-
}
197-
198-
// We don't use the WithHandshaker option so the connection won't error during handshaking.
199-
// WithIdleTimeout must be used because the connection.idleTimeoutExpired() function only checks the
200-
// deadline if the idleTimeout option is greater than 0.
201-
connOpts := []ConnectionOption{
202-
WithDialer(func(Dialer) Dialer { return dialer }),
203-
WithIdleTimeout(func(time.Duration) time.Duration { return 1 * time.Second }),
204-
}
205-
pool := createTestPool(t, getConfig(), connOpts...)
206-
defer pool.close(context.Background())
207-
208-
conn, err := pool.checkOut(context.Background())
209-
assert.Nil(t, err, "checkOut() error: %v", err)
210-
211-
// Set the idleDeadline to a time in the past to simulate expiration.
212-
pastTime := time.Now().Add(-10 * time.Second)
213-
conn.idleDeadline.Store(pastTime)
214-
215-
err = pool.checkIn(conn)
216-
assert.Nil(t, err, "checkIn() error: %v", err)
217-
218-
assertConnectionCounts(t, pool, 1, 1)
219-
evt := <-closed
220-
assert.Equal(t, event.ReasonIdle, evt.Reason, "expected reason %q, got %q",
221-
event.ReasonIdle, evt.Reason)
222-
})
223189
})
224190
t.Run("close", func(t *testing.T) {
225191
t.Run("connections returned gracefully", func(t *testing.T) {

x/mongo/driver/topology/connection.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,6 @@ func (c *connection) connect(ctx context.Context) (err error) {
208208
c.nc = tlsNc
209209
}
210210

211-
c.bumpIdleDeadline()
212-
213211
// running hello and authentication is handled by a handshaker on the configuration instance.
214212
handshaker := c.config.handshaker
215213
if handshaker == nil {
@@ -364,7 +362,6 @@ func (c *connection) writeWireMessage(ctx context.Context, wm []byte) error {
364362
}
365363
}
366364

367-
c.bumpIdleDeadline()
368365
return nil
369366
}
370367

@@ -429,7 +426,6 @@ func (c *connection) readWireMessage(ctx context.Context, dst []byte) ([]byte, e
429426
}
430427
}
431428

432-
c.bumpIdleDeadline()
433429
return dst, nil
434430
}
435431

x/mongo/driver/topology/pool.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,14 @@ func (p *pool) checkInNoEvent(conn *connection) error {
596596
return ErrWrongPool
597597
}
598598

599+
// Bump the connection idle deadline here because we're about to make the connection "available".
600+
// The idle deadline is used to determine when a connection has reached its max idle time and
601+
// should be closed. A connection reaches its max idle time when it has been "available" in the
602+
// idle connections stack for more than the configured duration (maxIdleTimeMS). Set it before
603+
// we call connectionPerished(), which checks the idle deadline, because a newly "available"
604+
// connection should never be perished due to max idle time.
605+
conn.bumpIdleDeadline()
606+
599607
if reason, perished := connectionPerished(conn); perished {
600608
_ = p.removeConnection(conn, reason)
601609
go func() {

x/mongo/driver/topology/pool_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,75 @@ func TestPool(t *testing.T) {
840840
p1.close(context.Background())
841841
p2.close(context.Background())
842842
})
843+
t.Run("bumps the connection idle deadline", func(t *testing.T) {
844+
t.Parallel()
845+
846+
cleanup := make(chan struct{})
847+
defer close(cleanup)
848+
addr := bootstrapConnections(t, 1, func(nc net.Conn) {
849+
<-cleanup
850+
_ = nc.Close()
851+
})
852+
853+
d := newdialer(&net.Dialer{})
854+
p := newPool(poolConfig{
855+
Address: address.Address(addr.String()),
856+
MaxIdleTime: 100 * time.Millisecond,
857+
}, WithDialer(func(Dialer) Dialer { return d }))
858+
err := p.ready()
859+
noerr(t, err)
860+
defer p.close(context.Background())
861+
862+
c, err := p.checkOut(context.Background())
863+
noerr(t, err)
864+
865+
// Sleep for 110ms, which will exceed the 100ms connection idle timeout. Then check the
866+
// connection back in and expect that it is not closed because checkIn() should bump the
867+
// connection idle deadline.
868+
time.Sleep(110 * time.Millisecond)
869+
err = p.checkIn(c)
870+
noerr(t, err)
871+
872+
assert.Equalf(t, 0, d.lenclosed(), "should have closed 0 connections")
873+
assert.Equalf(t, 1, p.availableConnectionCount(), "should have 1 idle connections in pool")
874+
assert.Equalf(t, 1, p.totalConnectionCount(), "should have 1 total connection in pool")
875+
})
876+
t.Run("sets minPoolSize connection idle deadline", func(t *testing.T) {
877+
t.Parallel()
878+
879+
cleanup := make(chan struct{})
880+
defer close(cleanup)
881+
addr := bootstrapConnections(t, 4, func(nc net.Conn) {
882+
<-cleanup
883+
_ = nc.Close()
884+
})
885+
886+
d := newdialer(&net.Dialer{})
887+
p := newPool(poolConfig{
888+
Address: address.Address(addr.String()),
889+
MinPoolSize: 3,
890+
MaxIdleTime: 10 * time.Millisecond,
891+
}, WithDialer(func(Dialer) Dialer { return d }))
892+
err := p.ready()
893+
noerr(t, err)
894+
defer p.close(context.Background())
895+
896+
// Wait for maintain() to open 3 connections.
897+
assertConnectionsOpened(t, d, 3)
898+
899+
// Sleep for 100ms, which will exceed the 10ms connection idle timeout, then try to check
900+
// out a connection. Expect that all minPoolSize connections checked into the pool by
901+
// maintain() have passed their idle deadline, so checkOut() closes all 3 connections
902+
// and tries to create a new connection.
903+
time.Sleep(100 * time.Millisecond)
904+
_, err = p.checkOut(context.Background())
905+
noerr(t, err)
906+
907+
assertConnectionsClosed(t, d, 3)
908+
assert.Equalf(t, 4, d.lenopened(), "should have opened 4 connections")
909+
assert.Equalf(t, 0, p.availableConnectionCount(), "should have 0 idle connections in pool")
910+
assert.Equalf(t, 1, p.totalConnectionCount(), "should have 1 total connection in pool")
911+
})
843912
})
844913
t.Run("maintain", func(t *testing.T) {
845914
t.Parallel()

0 commit comments

Comments
 (0)