Skip to content

Commit c9ef687

Browse files
GODRIVER-3173 Organize prose tests
1 parent 5afbc96 commit c9ef687

File tree

8 files changed

+237
-185
lines changed

8 files changed

+237
-185
lines changed

event/monitoring.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,20 @@ const (
7575

7676
// strings for pool command monitoring types
7777
const (
78-
ConnectionPoolCreated = "ConnectionPoolCreated"
79-
ConnectionPoolReady = "ConnectionPoolReady"
80-
ConnectionPoolCleared = "ConnectionPoolCleared"
81-
ConnectionPoolClosed = "ConnectionPoolClosed"
82-
ConnectionCreated = "ConnectionCreated"
83-
ConnectionReady = "ConnectionReady"
84-
ConnectionClosed = "ConnectionClosed"
85-
ConnectionCheckOutStarted = "ConnectionCheckOutStarted"
86-
ConnectionCheckOutFailed = "ConnectionCheckOutFailed"
87-
ConnectionCheckedOut = "ConnectionCheckedOut"
88-
ConnectionCheckedIn = "ConnectionCheckedIn"
89-
ConnectionPendingReadStarted = "ConnectionPendingReadStarted"
90-
ConnectionPendingReadSucceeded = "ConnectionPendingReadSucceeded"
91-
ConnectionPendingReadFailed = "ConnectionPendingReadFailed"
78+
ConnectionPoolCreated = "ConnectionPoolCreated"
79+
ConnectionPoolReady = "ConnectionPoolReady"
80+
ConnectionPoolCleared = "ConnectionPoolCleared"
81+
ConnectionPoolClosed = "ConnectionPoolClosed"
82+
ConnectionCreated = "ConnectionCreated"
83+
ConnectionReady = "ConnectionReady"
84+
ConnectionClosed = "ConnectionClosed"
85+
ConnectionCheckOutStarted = "ConnectionCheckOutStarted"
86+
ConnectionCheckOutFailed = "ConnectionCheckOutFailed"
87+
ConnectionCheckedOut = "ConnectionCheckedOut"
88+
ConnectionCheckedIn = "ConnectionCheckedIn"
89+
ConnectionPendingResponseStarted = "ConnectionPendingResponseStarted"
90+
ConnectionPendingResponseSucceeded = "ConnectionPendingResponseSucceeded"
91+
ConnectionPendingResponseFailed = "ConnectionPendingResponseFailed"
9292
)
9393

9494
// MonitorPoolOptions contains pool options as formatted in pool events

internal/integration/mtest/mongotest.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,9 +662,9 @@ func (t *T) createTestClient() {
662662
atomic.AddInt64(&t.connsCheckedOut, 1)
663663
case event.ConnectionCheckedIn:
664664
atomic.AddInt64(&t.connsCheckedOut, -1)
665-
case event.ConnectionPendingReadStarted:
665+
case event.ConnectionPendingResponseStarted:
666666
atomic.AddInt64(&t.connPendingReadStarted, 1)
667-
case event.ConnectionPendingReadSucceeded:
667+
case event.ConnectionPendingResponseSucceeded:
668668
atomic.AddInt64(&t.connPendingReadSucceeded, 1)
669669
case event.ConnectionCheckOutFailed:
670670
atomic.AddInt64(&t.connPendingReadFailed, 1)

internal/integration/unified/event.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ func monitoringEventTypeFromPoolEvent(evt *event.PoolEvent) monitoringEventType
121121
return connectionCheckedOutEvent
122122
case event.ConnectionCheckedIn:
123123
return connectionCheckedInEvent
124-
case event.ConnectionPendingReadStarted:
124+
case event.ConnectionPendingResponseStarted:
125125
return connectionPendingReadStarted
126-
case event.ConnectionPendingReadSucceeded:
126+
case event.ConnectionPendingResponseSucceeded:
127127
return connectionPendingReadSucceeded
128-
case event.ConnectionPendingReadFailed:
128+
case event.ConnectionPendingResponseFailed:
129129
return connectionPendingReadFailed
130130
default:
131131
return ""

internal/integration/unified/event_verification.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,15 @@ func verifyCMAPEvents(client *clientEntity, expectedEvents *expectedEvents) erro
363363
return newEventVerificationError(idx, client, "failed to get next pool event: %v", err.Error())
364364
}
365365
case evt.ConnectionPendingReadStarted != nil:
366-
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingReadStarted); err != nil {
366+
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingResponseStarted); err != nil {
367367
return newEventVerificationError(idx, client, "failed to get next pool event: %v", err.Error())
368368
}
369369
case evt.ConnectionPendingreadSucceeded != nil:
370-
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingReadSucceeded); err != nil {
370+
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingResponseSucceeded); err != nil {
371371
return newEventVerificationError(idx, client, "failed to get next pool event: %v", err.Error())
372372
}
373373
case evt.ConnectionPendingReadFailed != nil:
374-
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingReadFailed); err != nil {
374+
if _, pooled, err = getNextPoolEvent(pooled, event.ConnectionPendingResponseFailed); err != nil {
375375
return newEventVerificationError(idx, client, "failed to get next pool event: %v", err.Error())
376376
}
377377
case evt.PoolClearedEvent != nil:

x/mongo/driver/topology/cmap_prose_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ package topology
99
import (
1010
"context"
1111
"errors"
12+
"io"
1213
"net"
14+
"regexp"
15+
"sync"
1316
"testing"
1417
"time"
1518

1619
"go.mongodb.org/mongo-driver/v2/event"
1720
"go.mongodb.org/mongo-driver/v2/internal/assert"
21+
"go.mongodb.org/mongo-driver/v2/internal/csot"
22+
"go.mongodb.org/mongo-driver/v2/internal/driverutil"
1823
"go.mongodb.org/mongo-driver/v2/internal/require"
24+
"go.mongodb.org/mongo-driver/v2/mongo/address"
1925
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
2026
)
2127

@@ -263,6 +269,201 @@ func TestCMAPProse(t *testing.T) {
263269
})
264270
})
265271
})
272+
273+
// Need to test the case where we attempt a non-blocking read to determine if
274+
// we should refresh the remaining time. In the case of the Go Driver, we do
275+
// this by attempt to "pee" at 1 byte with a deadline of 1ns.
276+
t.Run("connection attempts peek but fails", func(t *testing.T) {
277+
const requestID = int32(-1)
278+
timeout := 10 * time.Millisecond
279+
280+
// Mock a TCP listener that will write a byte sequence > 5 (to avoid errors
281+
// due to size) to the TCP socket. Have the listener sleep for 2x the
282+
// timeout provided to the connection AFTER writing the byte sequence. This
283+
// wiill cause the connection to timeout while reading from the socket.
284+
addr := bootstrapConnections(t, 1, func(nc net.Conn) {
285+
defer func() {
286+
_ = nc.Close()
287+
}()
288+
289+
_, err := nc.Write([]byte{12, 0, 0, 0, 0, 0, 0, 0, 1})
290+
require.NoError(t, err)
291+
time.Sleep(timeout * 2)
292+
293+
// Write nothing so that the 1 millisecond "non-blocking" peek fails.
294+
})
295+
296+
poolEventsByType := make(map[string][]event.PoolEvent)
297+
poolEventsByTypeMu := &sync.Mutex{}
298+
299+
monitor := &event.PoolMonitor{
300+
Event: func(pe *event.PoolEvent) {
301+
poolEventsByTypeMu.Lock()
302+
poolEventsByType[pe.Type] = append(poolEventsByType[pe.Type], *pe)
303+
poolEventsByTypeMu.Unlock()
304+
},
305+
}
306+
307+
p := newPool(
308+
poolConfig{
309+
Address: address.Address(addr.String()),
310+
PoolMonitor: monitor,
311+
},
312+
)
313+
defer p.close(context.Background())
314+
err := p.ready()
315+
require.NoError(t, err)
316+
317+
// Check out a connection and read from the socket, causing a timeout and
318+
// pinning the connection to a pending read state.
319+
conn, err := p.checkOut(context.Background())
320+
require.NoError(t, err)
321+
322+
ctx, cancel := csot.WithTimeout(context.Background(), &timeout)
323+
defer cancel()
324+
325+
ctx = driverutil.WithValueHasMaxTimeMS(ctx, true)
326+
ctx = driverutil.WithRequestID(ctx, requestID)
327+
328+
_, err = conn.readWireMessage(ctx)
329+
regex := regexp.MustCompile(
330+
`^connection\(.*\[-\d+\]\) incomplete read of full message: context deadline exceeded: read tcp 127.0.0.1:.*->127.0.0.1:.*: i\/o timeout$`,
331+
)
332+
assert.True(t, regex.MatchString(err.Error()), "error %q does not match pattern %q", err, regex)
333+
334+
// Check in the connection with a pending read state. The next time this
335+
// connection is checked out, it should attempt to read the pending
336+
// response.
337+
err = p.checkIn(conn)
338+
require.NoError(t, err)
339+
340+
// Wait 3s to make sure there is no remaining time on the pending read
341+
// state.
342+
time.Sleep(3 * time.Second)
343+
344+
// Check out the connection again. The remaining time should be exhausted
345+
// requiring us to "peek" at the connection to determine if we should
346+
_, err = p.checkOut(context.Background())
347+
assert.ErrorIs(t, err, io.EOF)
348+
349+
// There should be 1 ConnectionPendingResponseStarted event.
350+
started := poolEventsByType[event.ConnectionPendingResponseStarted]
351+
require.Len(t, started, 1)
352+
353+
assert.Equal(t, addr.String(), started[0].Address)
354+
assert.Equal(t, conn.driverConnectionID, started[0].ConnectionID)
355+
assert.Equal(t, requestID, started[0].RequestID)
356+
357+
// There should be 1 ConnectionPendingResponseFailed event.
358+
failed := poolEventsByType[event.ConnectionPendingResponseFailed]
359+
require.Len(t, failed, 1)
360+
361+
assert.Equal(t, addr.String(), failed[0].Address)
362+
assert.Equal(t, conn.driverConnectionID, failed[0].ConnectionID)
363+
assert.Equal(t, requestID, failed[0].RequestID)
364+
assert.Equal(t, io.EOF.Error(), failed[0].Reason)
365+
assert.ErrorIs(t, failed[0].Error, io.EOF)
366+
assert.Equal(t, time.Duration(0), failed[0].RemainingTime)
367+
368+
// There should be 0 ConnectionPendingResponseSucceeded event.
369+
require.Len(t, poolEventsByType[event.ConnectionPendingResponseSucceeded], 0)
370+
})
371+
372+
t.Run("connection attempts peek and succeeds", func(t *testing.T) {
373+
const requestID = int32(-1)
374+
timeout := 10 * time.Millisecond
375+
376+
// Mock a TCP listener that will write a byte sequence > 5 (to avoid errors
377+
// due to size) to the TCP socket. Have the listener sleep for 2x the
378+
// timeout provided to the connection AFTER writing the byte sequence. This
379+
// wiill cause the connection to timeout while reading from the socket.
380+
addr := bootstrapConnections(t, 1, func(nc net.Conn) {
381+
defer func() {
382+
_ = nc.Close()
383+
}()
384+
385+
_, err := nc.Write([]byte{12, 0, 0, 0, 0, 0, 0, 0, 1})
386+
require.NoError(t, err)
387+
time.Sleep(timeout * 2)
388+
389+
// Write data that can be peeked at.
390+
_, err = nc.Write([]byte{12, 0, 0, 0, 0, 0, 0, 0, 1})
391+
require.NoError(t, err)
392+
393+
})
394+
395+
poolEventsByType := make(map[string][]event.PoolEvent)
396+
poolEventsByTypeMu := &sync.Mutex{}
397+
398+
monitor := &event.PoolMonitor{
399+
Event: func(pe *event.PoolEvent) {
400+
poolEventsByTypeMu.Lock()
401+
poolEventsByType[pe.Type] = append(poolEventsByType[pe.Type], *pe)
402+
poolEventsByTypeMu.Unlock()
403+
},
404+
}
405+
406+
p := newPool(
407+
poolConfig{
408+
Address: address.Address(addr.String()),
409+
PoolMonitor: monitor,
410+
},
411+
)
412+
defer p.close(context.Background())
413+
err := p.ready()
414+
require.NoError(t, err)
415+
416+
// Check out a connection and read from the socket, causing a timeout and
417+
// pinning the connection to a pending read state.
418+
conn, err := p.checkOut(context.Background())
419+
require.NoError(t, err)
420+
421+
ctx, cancel := csot.WithTimeout(context.Background(), &timeout)
422+
defer cancel()
423+
424+
ctx = driverutil.WithValueHasMaxTimeMS(ctx, true)
425+
ctx = driverutil.WithRequestID(ctx, requestID)
426+
427+
_, err = conn.readWireMessage(ctx)
428+
regex := regexp.MustCompile(
429+
`^connection\(.*\[-\d+\]\) incomplete read of full message: context deadline exceeded: read tcp 127.0.0.1:.*->127.0.0.1:.*: i\/o timeout$`,
430+
)
431+
assert.True(t, regex.MatchString(err.Error()), "error %q does not match pattern %q", err, regex)
432+
433+
// Check in the connection with a pending read state. The next time this
434+
// connection is checked out, it should attempt to read the pending
435+
// response.
436+
err = p.checkIn(conn)
437+
require.NoError(t, err)
438+
439+
// Wait 3s to make sure there is no remaining time on the pending read
440+
// state.
441+
time.Sleep(3 * time.Second)
442+
443+
// Check out the connection again. The remaining time should be exhausted
444+
// requiring us to "peek" at the connection to determine if we should
445+
_, err = p.checkOut(context.Background())
446+
require.NoError(t, err)
447+
448+
// There should be 1 ConnectionPendingResponseStarted event.
449+
started := poolEventsByType[event.ConnectionPendingResponseStarted]
450+
require.Len(t, started, 1)
451+
452+
assert.Equal(t, addr.String(), started[0].Address)
453+
assert.Equal(t, conn.driverConnectionID, started[0].ConnectionID)
454+
assert.Equal(t, requestID, started[0].RequestID)
455+
456+
// There should be 0 ConnectionPendingResponseFailed event.
457+
require.Len(t, poolEventsByType[event.ConnectionPendingResponseFailed], 0)
458+
459+
// There should be 1 ConnectionPendingResponseSucceeded event.
460+
succeeded := poolEventsByType[event.ConnectionPendingResponseSucceeded]
461+
require.Len(t, succeeded, 1)
462+
463+
assert.Equal(t, addr.String(), succeeded[0].Address)
464+
assert.Equal(t, conn.driverConnectionID, succeeded[0].ConnectionID)
465+
assert.Greater(t, int(succeeded[0].Duration), 0)
466+
})
266467
}
267468

268469
func createTestPool(t *testing.T, cfg poolConfig, opts ...ConnectionOption) *pool {

x/mongo/driver/topology/pool.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ func publishPendingReadStarted(pool *pool, conn *connection) {
811811
// publish an event to the pool monitor if it is set.
812812
if pool.monitor != nil {
813813
event := &event.PoolEvent{
814-
Type: event.ConnectionPendingReadStarted,
814+
Type: event.ConnectionPendingResponseStarted,
815815
Address: pool.address.String(),
816816
ConnectionID: conn.driverConnectionID,
817817
RequestID: prs.requestID,
@@ -840,7 +840,7 @@ func publishPendingReadFailed(pool *pool, conn *connection, err error) {
840840

841841
if pool.monitor != nil {
842842
event := &event.PoolEvent{
843-
Type: event.ConnectionPendingReadFailed,
843+
Type: event.ConnectionPendingResponseFailed,
844844
Address: pool.address.String(),
845845
ConnectionID: conn.driverConnectionID,
846846
RequestID: prs.requestID,
@@ -853,7 +853,7 @@ func publishPendingReadFailed(pool *pool, conn *connection, err error) {
853853
}
854854
}
855855

856-
func publishPendingReadSucceeded(pool *pool, conn *connection) {
856+
func publishPendingReadSucceeded(pool *pool, conn *connection, dur time.Duration) {
857857
prs := conn.pendingReadState
858858
if prs == nil {
859859
return
@@ -870,10 +870,10 @@ func publishPendingReadSucceeded(pool *pool, conn *connection) {
870870

871871
if pool.monitor != nil {
872872
event := &event.PoolEvent{
873-
Type: event.ConnectionPendingReadSucceeded,
873+
Type: event.ConnectionPendingResponseSucceeded,
874874
Address: pool.address.String(),
875875
ConnectionID: conn.driverConnectionID,
876-
//Duration: time.Since(st),
876+
Duration: dur,
877877
}
878878

879879
pool.monitor.Event(event)
@@ -1002,6 +1002,7 @@ func awaitPendingRead(ctx context.Context, pool *pool, conn *connection) error {
10021002
bytesRead int
10031003
)
10041004

1005+
st := time.Now()
10051006
if remainingTime <= 0 {
10061007
// If there is no remaining time, we can just peek at the connection to check
10071008
// aliveness. In such cases, we don't want to close the connection.
@@ -1011,6 +1012,7 @@ func awaitPendingRead(ctx context.Context, pool *pool, conn *connection) error {
10111012
}
10121013

10131014
endTime := time.Now()
1015+
endDuration := time.Since(st)
10141016

10151017
if err != nil {
10161018
// No matter what happens, always check the connection back into the
@@ -1052,7 +1054,7 @@ func awaitPendingRead(ctx context.Context, pool *pool, conn *connection) error {
10521054
return err
10531055
}
10541056

1055-
publishPendingReadSucceeded(pool, conn)
1057+
publishPendingReadSucceeded(pool, conn, endDuration)
10561058

10571059
conn.pendingReadState = nil
10581060

0 commit comments

Comments
 (0)