Skip to content

Commit 7d3355f

Browse files
author
Divjot Arora
authored
GODRIVER-1581 Transform network errors into context.DeadlineExceeded (#430)
If a context is translated to a network read/write deadline and the read/write call returns a timeout error, this commit will swallow that error and replace it with context.DeadlineExceeded so the resulting error will return true for errors.Is(err, context.DeadlineExceeded).
1 parent 954304d commit 7d3355f

File tree

5 files changed

+93
-6
lines changed

5 files changed

+93
-6
lines changed

mongo/errors.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ func replaceErrors(err error) error {
4747
}
4848
if qe, ok := err.(driver.QueryFailureError); ok {
4949
// qe.Message is "command failure"
50-
ce := CommandError{Name: qe.Message}
50+
ce := CommandError{
51+
Name: qe.Message,
52+
Wrapped: qe.Wrapped,
53+
}
5154

5255
dollarErr, err := qe.Response.LookupErr("$err")
5356
if err == nil {

mongo/integration/errors_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import (
1212
"context"
1313
"errors"
1414
"io"
15+
"net"
1516
"testing"
17+
"time"
1618

19+
"go.mongodb.org/mongo-driver/bson"
1720
"go.mongodb.org/mongo-driver/internal/testutil/assert"
1821
"go.mongodb.org/mongo-driver/mongo"
1922
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
@@ -57,4 +60,49 @@ func TestErrors(t *testing.T) {
5760
assert.True(mt, errors.Is(err, io.EOF), "expected error %v, got %v", io.EOF, err)
5861
})
5962
})
63+
64+
mt.RunOpts("network timeouts", noClientOpts, func(mt *mtest.T) {
65+
mt.Run("context timeouts return DeadlineExceeded", func(mt *mtest.T) {
66+
_, err := mt.Coll.InsertOne(mtest.Background, bson.D{{"x", 1}})
67+
assert.Nil(mt, err, "InsertOne error: %v", err)
68+
69+
mt.ClearEvents()
70+
filter := bson.M{
71+
"$where": "function() { sleep(1000); return false; }",
72+
}
73+
timeoutCtx, cancel := context.WithTimeout(mtest.Background, 100*time.Millisecond)
74+
defer cancel()
75+
_, err = mt.Coll.Find(timeoutCtx, filter)
76+
77+
evt := mt.GetStartedEvent()
78+
assert.Equal(mt, "find", evt.CommandName, "expected command 'find', got %q", evt.CommandName)
79+
assert.True(mt, errors.Is(err, context.DeadlineExceeded),
80+
"errors.Is failure: expected error %v to be %v", err, context.DeadlineExceeded)
81+
})
82+
83+
socketTimeoutOpts := options.Client().
84+
SetSocketTimeout(100 * time.Millisecond)
85+
socketTimeoutMtOpts := mtest.NewOptions().
86+
ClientOptions(socketTimeoutOpts)
87+
mt.RunOpts("socketTimeoutMS timeouts return network errors", socketTimeoutMtOpts, func(mt *mtest.T) {
88+
_, err := mt.Coll.InsertOne(mtest.Background, bson.D{{"x", 1}})
89+
assert.Nil(mt, err, "InsertOne error: %v", err)
90+
91+
mt.ClearEvents()
92+
filter := bson.M{
93+
"$where": "function() { sleep(1000); return false; }",
94+
}
95+
_, err = mt.Coll.Find(mtest.Background, filter)
96+
97+
evt := mt.GetStartedEvent()
98+
assert.Equal(mt, "find", evt.CommandName, "expected command 'find', got %q", evt.CommandName)
99+
100+
assert.False(mt, errors.Is(err, context.DeadlineExceeded),
101+
"errors.Is failure: expected error %v to not be %v", err, context.DeadlineExceeded)
102+
var netErr net.Error
103+
ok := errors.As(err, &netErr)
104+
assert.True(mt, ok, "errors.As failure: expected error %v to be a net.Error", err)
105+
assert.True(mt, netErr.Timeout(), "expected error %v to be a network timeout", err)
106+
})
107+
})
60108
}

x/mongo/driver/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ var (
4444
type QueryFailureError struct {
4545
Message string
4646
Response bsoncore.Document
47+
Wrapped error
4748
}
4849

4950
// Error implements the error interface.
5051
func (e QueryFailureError) Error() string {
5152
return fmt.Sprintf("%s: %v", e.Message, e.Response)
5253
}
5354

55+
// Unwrap returns the underlying error.
56+
func (e QueryFailureError) Unwrap() error {
57+
return e.Wrapped
58+
}
59+
5460
// ResponseError is an error parsing the response to a command.
5561
type ResponseError struct {
5662
Message string

x/mongo/driver/operation_legacy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -648,12 +648,12 @@ func (op Operation) roundTripLegacyCursor(ctx context.Context, wm []byte, srvr S
648648
func (op Operation) roundTripLegacy(ctx context.Context, conn Connection, wm []byte) ([]byte, error) {
649649
err := conn.WriteWireMessage(ctx, wm)
650650
if err != nil {
651-
return nil, Error{Message: err.Error(), Labels: []string{TransientTransactionError, NetworkError}}
651+
return nil, Error{Message: err.Error(), Labels: []string{TransientTransactionError, NetworkError}, Wrapped: err}
652652
}
653653

654654
wm, err = conn.ReadWireMessage(ctx, wm[:0])
655655
if err != nil {
656-
err = Error{Message: err.Error(), Labels: []string{TransientTransactionError, NetworkError}}
656+
err = Error{Message: err.Error(), Labels: []string{TransientTransactionError, NetworkError}, Wrapped: err}
657657
}
658658
return wm, err
659659
}

x/mongo/driver/topology/connection.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,20 @@ func (c *connection) closeConnectContext() {
198198
c.cancelConnectContext()
199199
}
200200

201+
func transformNetworkError(originalError error, contextDeadlineUsed bool) error {
202+
if originalError == nil {
203+
return nil
204+
}
205+
if !contextDeadlineUsed {
206+
return originalError
207+
}
208+
209+
if netErr, ok := originalError.(net.Error); ok && netErr.Timeout() {
210+
return context.DeadlineExceeded
211+
}
212+
return originalError
213+
}
214+
201215
func (c *connection) writeWireMessage(ctx context.Context, wm []byte) error {
202216
var err error
203217
if atomic.LoadInt32(&c.connected) != connected {
@@ -214,7 +228,9 @@ func (c *connection) writeWireMessage(ctx context.Context, wm []byte) error {
214228
deadline = time.Now().Add(c.writeTimeout)
215229
}
216230

231+
var contextDeadlineUsed bool
217232
if dl, ok := ctx.Deadline(); ok && (deadline.IsZero() || dl.Before(deadline)) {
233+
contextDeadlineUsed = true
218234
deadline = dl
219235
}
220236

@@ -225,7 +241,11 @@ func (c *connection) writeWireMessage(ctx context.Context, wm []byte) error {
225241
_, err = c.nc.Write(wm)
226242
if err != nil {
227243
c.close()
228-
return ConnectionError{ConnectionID: c.id, Wrapped: err, message: "unable to write wire message to network"}
244+
return ConnectionError{
245+
ConnectionID: c.id,
246+
Wrapped: transformNetworkError(err, contextDeadlineUsed),
247+
message: "unable to write wire message to network",
248+
}
229249
}
230250

231251
c.bumpIdleDeadline()
@@ -251,7 +271,9 @@ func (c *connection) readWireMessage(ctx context.Context, dst []byte) ([]byte, e
251271
deadline = time.Now().Add(c.readTimeout)
252272
}
253273

274+
var contextDeadlineUsed bool
254275
if dl, ok := ctx.Deadline(); ok && (deadline.IsZero() || dl.Before(deadline)) {
276+
contextDeadlineUsed = true
255277
deadline = dl
256278
}
257279

@@ -270,7 +292,11 @@ func (c *connection) readWireMessage(ctx context.Context, dst []byte) ([]byte, e
270292
if err != nil {
271293
// We closeConnection the connection because we don't know if there are other bytes left to read.
272294
c.close()
273-
return nil, ConnectionError{ConnectionID: c.id, Wrapped: err, message: "incomplete read of message header"}
295+
return nil, ConnectionError{
296+
ConnectionID: c.id,
297+
Wrapped: transformNetworkError(err, contextDeadlineUsed),
298+
message: "incomplete read of message header",
299+
}
274300
}
275301

276302
// read the length as an int32
@@ -289,7 +315,11 @@ func (c *connection) readWireMessage(ctx context.Context, dst []byte) ([]byte, e
289315
if err != nil {
290316
// We closeConnection the connection because we don't know if there are other bytes left to read.
291317
c.close()
292-
return nil, ConnectionError{ConnectionID: c.id, Wrapped: err, message: "incomplete read of full message"}
318+
return nil, ConnectionError{
319+
ConnectionID: c.id,
320+
Wrapped: transformNetworkError(err, contextDeadlineUsed),
321+
message: "incomplete read of full message",
322+
}
293323
}
294324

295325
c.bumpIdleDeadline()

0 commit comments

Comments
 (0)