Skip to content

Commit

Permalink
GODRIVER-2603 Use errors.Is/As for IsTimeout and IsDuplicateKeyError.
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewdale committed Aug 31, 2023
1 parent 2f372fd commit 6f2eb29
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 37 deletions.
74 changes: 40 additions & 34 deletions mongo/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,50 +102,56 @@ func replaceErrors(err error) error {
return err
}

// IsDuplicateKeyError returns true if err is a duplicate key error
// IsDuplicateKeyError returns true if err is a duplicate key error.
func IsDuplicateKeyError(err error) bool {
// handles SERVER-7164 and SERVER-11493
for ; err != nil; err = unwrap(err) {
if e, ok := err.(ServerError); ok {
return e.HasErrorCode(11000) || e.HasErrorCode(11001) || e.HasErrorCode(12582) ||
e.HasErrorCodeWithMessage(16460, " E11000 ")
}
if se := ServerError(nil); errors.As(err, &se) {
return se.HasErrorCode(11000) || // Duplicate key error.
se.HasErrorCode(11001) || // Duplicate key error on update.
// Duplicate key error in a capped collection. See SERVER-7164.
se.HasErrorCode(12582) ||
// Mongos insert error caused by a duplicate key error. See
// SERVER-11493.
se.HasErrorCodeWithMessage(16460, " E11000 ")
}
return false
}

// IsTimeout returns true if err is from a timeout
// timeoutErrs is a list of error values that indicate a timeout happened.
var timeoutErrs = [...]error{
context.DeadlineExceeded,
driver.ErrDeadlineWouldBeExceeded,
topology.ErrServerSelectionTimeout,
}

// IsTimeout returns true if err was caused by a timeout. For error chains,
// IsTimeout returns true if any error in the chain was caused by a timeout.
func IsTimeout(err error) bool {
for ; err != nil; err = unwrap(err) {
// check unwrappable errors together
if err == context.DeadlineExceeded {
return true
}
if err == driver.ErrDeadlineWouldBeExceeded {
return true
}
if err == topology.ErrServerSelectionTimeout {
return true
}
if _, ok := err.(topology.WaitQueueTimeoutError); ok {
return true
}
if ce, ok := err.(CommandError); ok && ce.IsMaxTimeMSExpiredError() {
// Check if the error chain contains any of the timeout error values.
for _, target := range timeoutErrs {
if errors.Is(err, target) {
return true
}
if we, ok := err.(WriteException); ok && we.WriteConcernError != nil &&
we.WriteConcernError.IsMaxTimeMSExpiredError() {
}

// Check if the error chain contains any error types that can indicate
// timeout.
if errors.As(err, &topology.WaitQueueTimeoutError{}) {
return true
}
if ce := (CommandError{}); errors.As(err, &ce) && ce.IsMaxTimeMSExpiredError() {
return true
}
if we := (WriteException{}); errors.As(err, &we) && we.WriteConcernError != nil && we.WriteConcernError.IsMaxTimeMSExpiredError() {
return true
}
if ne := net.Error(nil); errors.As(err, &ne) {
return ne.Timeout()
}
// Check timeout error labels.
if le := LabeledError(nil); errors.As(err, &le) {
if le.HasErrorLabel("NetworkTimeoutError") || le.HasErrorLabel("ExceededTimeLimitError") {
return true
}
if ne, ok := err.(net.Error); ok {
return ne.Timeout()
}
//timeout error labels
if le, ok := err.(LabeledError); ok {
if le.HasErrorLabel("NetworkTimeoutError") || le.HasErrorLabel("ExceededTimeLimitError") {
return true
}
}
}

return false
Expand Down
2 changes: 1 addition & 1 deletion mongo/integration/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ func TestClient(t *testing.T) {
err := mt.Client.Ping(ctx, nil)
cancel()
assert.NotNil(mt, err, "expected Ping to return an error")
assert.True(mt, mongo.IsTimeout(err), "expected a timeout error: got %v", err)
assert.True(mt, mongo.IsTimeout(err), "expected a timeout error, got: %v", err)
}

// Assert that the Ping timeouts result in no connections being closed.
Expand Down
9 changes: 7 additions & 2 deletions x/mongo/driver/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,15 @@ func (e Error) UnsupportedStorageEngine() bool {

// Error implements the error interface.
func (e Error) Error() string {
var msg string
if e.Name != "" {
return fmt.Sprintf("(%v) %v", e.Name, e.Message)
msg = fmt.Sprintf("(%v)", e.Name)
}
return e.Message
msg += " " + e.Message
if e.Wrapped != nil {
msg += ": " + e.Wrapped.Error()
}
return msg
}

// Unwrap returns the underlying error.
Expand Down

0 comments on commit 6f2eb29

Please sign in to comment.