Skip to content

Commit 467e01e

Browse files
matthewdaleblink1073
authored andcommitted
GODRIVER-3145 Don't retry on context timeout or cancellation. (mongodb#1598)
(cherry picked from commit 86cb647)
1 parent b59ada6 commit 467e01e

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

internal/integration/client_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,77 @@ func TestClient(t *testing.T) {
660660
"expected 'OP_MSG' OpCode in wire message, got %q", pair.Sent.OpCode.String())
661661
}
662662
})
663+
664+
opts := mtest.NewOptions().
665+
// Blocking failpoints don't work on pre-4.2 and sharded clusters.
666+
Topologies(mtest.Single, mtest.ReplicaSet).
667+
MinServerVersion("4.2").
668+
// Expliticly enable retryable reads and retryable writes.
669+
ClientOptions(options.Client().SetRetryReads(true).SetRetryWrites(true))
670+
mt.RunOpts("operations don't retry after a context timeout", opts, func(mt *mtest.T) {
671+
testCases := []struct {
672+
desc string
673+
operation func(context.Context, *mongo.Collection) error
674+
}{
675+
{
676+
desc: "read op",
677+
operation: func(ctx context.Context, coll *mongo.Collection) error {
678+
return coll.FindOne(ctx, bson.D{}).Err()
679+
},
680+
},
681+
{
682+
desc: "write op",
683+
operation: func(ctx context.Context, coll *mongo.Collection) error {
684+
_, err := coll.InsertOne(ctx, bson.D{})
685+
return err
686+
},
687+
},
688+
}
689+
690+
for _, tc := range testCases {
691+
mt.Run(tc.desc, func(mt *mtest.T) {
692+
_, err := mt.Coll.InsertOne(context.Background(), bson.D{})
693+
require.NoError(mt, err)
694+
695+
mt.SetFailPoint(mtest.FailPoint{
696+
ConfigureFailPoint: "failCommand",
697+
Mode: "alwaysOn",
698+
Data: mtest.FailPointData{
699+
FailCommands: []string{"find", "insert"},
700+
BlockConnection: true,
701+
BlockTimeMS: 500,
702+
},
703+
})
704+
705+
mt.ClearEvents()
706+
707+
for i := 0; i < 50; i++ {
708+
// Run 50 operations, each with a timeout of 50ms. Expect
709+
// them to all return a timeout error because the failpoint
710+
// blocks find operations for 500ms. Run 50 to increase the
711+
// probability that an operation will time out in a way that
712+
// can cause a retry.
713+
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
714+
err = tc.operation(ctx, mt.Coll)
715+
cancel()
716+
assert.ErrorIs(mt, err, context.DeadlineExceeded)
717+
assert.True(mt, mongo.IsTimeout(err), "expected mongo.IsTimeout(err) to be true")
718+
719+
// Assert that each operation reported exactly one command
720+
// started events, which means the operation did not retry
721+
// after the context timeout.
722+
evts := mt.GetAllStartedEvents()
723+
require.Len(mt,
724+
mt.GetAllStartedEvents(),
725+
1,
726+
"expected exactly 1 command started event per operation, but got %d after %d iterations",
727+
len(evts),
728+
i)
729+
mt.ClearEvents()
730+
}
731+
})
732+
}
733+
})
663734
}
664735

665736
func TestClient_BSONOptions(t *testing.T) {

x/mongo/driver/operation.go

+7
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,13 @@ func (op Operation) Execute(ctx context.Context) error {
601601
}
602602
}()
603603
for {
604+
// If we're starting a retry and the the error from the previous try was
605+
// a context canceled or deadline exceeded error, stop retrying and
606+
// return that error.
607+
if errors.Is(prevErr, context.Canceled) || errors.Is(prevErr, context.DeadlineExceeded) {
608+
return prevErr
609+
}
610+
604611
requestID := wiremessage.NextRequestID()
605612

606613
// If the server or connection are nil, try to select a new server and get a new connection.

0 commit comments

Comments
 (0)