Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GODRIVER-2651 Break NoWritesPerformed-Only Error Sequence #1135

Merged
merged 19 commits into from Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion mongo/integration/retryable_writes_prose_test.go
Expand Up @@ -282,7 +282,7 @@ func TestRetryableWritesProse(t *testing.T) {

require.True(mt, secondFailPointConfigured)

// Assert that the "NotWritablePrimary" error is returned.
// Assert that the "ShutdownInProgress" error is returned.
require.True(mt, err.(mongo.WriteException).HasErrorCode(int(shutdownInProgressErrorCode)))
})
}
@@ -0,0 +1,54 @@
description: "retryable-writes insertOne noWritesPerformedErrors"

schemaVersion: "1.0"

runOnRequirements:
- minServerVersion: "6.0"
topologies: [ replicaset ]

createEntities:
- client:
id: &client0 client0
useMultipleMongoses: false
observeEvents: [ commandFailedEvent ]
- database:
id: &database0 database0
client: *client0
databaseName: &databaseName retryable-writes-tests
- collection:
id: &collection0 collection0
database: *database0
collectionName: &collectionName no-writes-performed-collection

tests:
- description: "InsertOne fails after NoWritesPerformed error"
operations:
- name: failPoint
object: testRunner
arguments:
client: *client0
failPoint:
configureFailPoint: failCommand
mode:
times: 2
data:
failCommands:
- insert
errorCode: 64
errorLabels:
- NoWritesPerformed
- RetryableWriteError
- name: insertOne
object: *collection0
arguments:
document:
x: 1
expectError:
errorCode: 64
errorLabelsContain:
- NoWritesPerformed
- RetryableWriteError
outcome:
- collectionName: *collectionName
databaseName: *databaseName
documents: []
@@ -0,0 +1,90 @@
{
"description": "retryable-writes insertOne noWritesPerformedErrors",
"schemaVersion": "1.0",
"runOnRequirements": [
{
"minServerVersion": "6.0",
"topologies": [
"replicaset"
]
}
],
"createEntities": [
{
"client": {
"id": "client0",
"useMultipleMongoses": false,
"observeEvents": [
"commandFailedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "retryable-writes-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "no-writes-performed-collection"
}
}
],
"tests": [
{
"description": "InsertOne fails after NoWritesPerformed error",
"operations": [
{
"name": "failPoint",
"object": "testRunner",
"arguments": {
"client": "client0",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert"
],
"errorCode": 64,
"errorLabels": [
"NoWritesPerformed",
"RetryableWriteError"
]
}
}
}
},
{
"name": "insertOne",
"object": "collection0",
"arguments": {
"document": {
"x": 1
}
},
"expectError": {
"errorCode": 64,
"errorLabelsContain": [
"NoWritesPerformed",
"RetryableWriteError"
]
}
}
],
"outcome": [
{
"collectionName": "no-writes-performed-collection",
"databaseName": "retryable-writes-tests",
"documents": []
}
]
}
]
}
61 changes: 61 additions & 0 deletions x/mongo/driver/errors.go
Expand Up @@ -117,6 +117,37 @@ func (wce WriteCommandError) Error() string {
return buf.String()
}

// Is reports whether any error in err's chain matches target by comparing the error codes for the WriteConcernError
// and all of the WriteErrors. If any values from either field are "false", the function will return false.
func (wce WriteCommandError) Is(tgt error) bool {
target, ok := tgt.(WriteCommandError)
if !ok {
return false
}

targetWCE := target.WriteConcernError
wceWCE := wce.WriteConcernError

if targetWCE != nil && wceWCE != nil && !wceWCE.Is(*targetWCE) {
return false
}

// If the WriteError lengths are not equal, then the errors are not equal.
if len(target.WriteErrors) != len(wce.WriteErrors) {
return false
}

if len(wce.WriteErrors) > 0 {
for idx, twe := range target.WriteErrors {
if !wce.WriteErrors[idx].Is(twe) {
return false
}
}
}
prestonvasquez marked this conversation as resolved.
Show resolved Hide resolved

return true
}

// Retryable returns true if the error is retryable
func (wce WriteCommandError) Retryable(wireVersion *description.VersionRange) bool {
for _, label := range wce.Labels {
Expand Down Expand Up @@ -165,6 +196,16 @@ func (wce WriteConcernError) Error() string {
return wce.Message
}

// Is reports whether any error in err's chain matches target by comparing the error codes for the WriteConcernError.
func (wce WriteConcernError) Is(tgt error) bool {
target, ok := tgt.(WriteConcernError)
if !ok {
return false
}

return wce.Code == target.Code
}

// Retryable returns true if the error is retryable
func (wce WriteConcernError) Retryable() bool {
for _, code := range retryableCodes {
Expand Down Expand Up @@ -221,6 +262,16 @@ type WriteError struct {

func (we WriteError) Error() string { return we.Message }

// Is reports whether any error in err's chain matches target by comparing the error codes for the WriteError.
func (we WriteError) Is(tgt error) bool {
target, ok := tgt.(WriteError)
if !ok {
return false
}

return we.Code == target.Code
}

// WriteErrors is a group of non-write concern failures that occurred as a result
// of a write operation.
type WriteErrors []WriteError
Expand Down Expand Up @@ -267,6 +318,16 @@ func (e Error) Unwrap() error {
return e.Wrapped
}

// Is reports whether any error in err's chain matches target by comparing the error codes for the Error.
func (e Error) Is(tgt error) bool {
target, ok := tgt.(Error)
if !ok {
return false
}

return errors.Is(e.Wrapped, target.Wrapped) && e.Code == target.Code
}

// HasErrorLabel returns true if the error contains the specified label.
func (e Error) HasErrorLabel(label string) bool {
if e.Labels != nil {
Expand Down