Skip to content

Commit e45c8f3

Browse files
committed
Raise an actionable error message when retryWrites fails due to
using an unsupported storage engine JAVA-3374
1 parent 4e5fefa commit e45c8f3

File tree

7 files changed

+259
-7
lines changed

7 files changed

+259
-7
lines changed

.evergreen/.evg.yml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ functions:
201201
params:
202202
script: |
203203
${PREPARE_SHELL}
204-
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
204+
MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
205205
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
206206
- command: expansions.update
207207
params:
@@ -263,6 +263,16 @@ functions:
263263
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
264264
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} MONGODB_URI=${gssapi_auth_mongodb_uri} KDC=${gssapi_auth_kdc} REALM=${gssapi_auth_realm} KEYTAB_BASE64=${gssapi_auth_keytab_base64} .evergreen/run-gssapi-auth-test.sh
265265
266+
"run mmapv1 storage test":
267+
- command: shell.exec
268+
type: test
269+
params:
270+
silent: true
271+
working_dir: "src"
272+
script: |
273+
${PREPARE_SHELL}
274+
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} TOPOLOGY=${TOPOLOGY} STORAGE_ENGINE=${STORAGE_ENGINE} MONGODB_URI="${MONGODB_URI}" .evergreen/run-mmapv1-storage-test.sh
275+
266276
"run atlas test":
267277
- command: shell.exec
268278
type: test
@@ -433,6 +443,12 @@ tasks:
433443
- func: "bootstrap mongo-orchestration"
434444
- func: "run perf tests"
435445
- func: "send dashboard data"
446+
447+
- name: "mmapv1-storage-test"
448+
commands:
449+
- func: "bootstrap mongo-orchestration"
450+
- func: "run mmapv1 storage test"
451+
436452
axes:
437453
- id: version
438454
display_name: MongoDB Version
@@ -561,6 +577,15 @@ axes:
561577
variables:
562578
JDK: "jdk6"
563579

580+
# Choice of MongoDB storage engine
581+
- id: storage-engine
582+
display_name: Storage
583+
values:
584+
- id: mmapv1
585+
display_name: MMAPv1
586+
variables:
587+
STORAGE_ENGINE: "mmapv1"
588+
564589
buildvariants:
565590

566591
# Test packaging and other release related routines
@@ -688,3 +713,9 @@ buildvariants:
688713
run_on: ubuntu1804-test
689714
tasks:
690715
- name: "publish-snapshot"
716+
717+
- matrix_name: "tests-storage-engines"
718+
matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk8", os: "linux", version: ["3.6", "4.0"], topology: ["replicaset", "sharded-cluster"], storage-engine: "mmapv1" }
719+
display_name: "${version} Storage ${storage-engine} ${jdk} ${os} ${topology}"
720+
tasks:
721+
- name: "mmapv1-storage-test"

.evergreen/run-mmapv1-storage-test.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
3+
set -o errexit # Exit the script with error if any of the commands fail
4+
5+
# Supported/used environment variables:
6+
# MONGODB_URI Set the URI, including username/password to use to connect to the server via PLAIN authentication mechanism
7+
# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java
8+
# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9"
9+
10+
JDK=${JDK:-jdk}
11+
12+
############################################
13+
# Main Program #
14+
############################################
15+
16+
echo "Running MMAPv1 Storage Test"
17+
18+
echo "Compiling java driver with jdk9"
19+
20+
# We always compile with the latest version of java
21+
export JAVA_HOME="/opt/java/jdk9"
22+
23+
echo "Running tests with ${JDK}"
24+
./gradlew -version
25+
./gradlew -PjdkHome=/opt/java/${JDK} --stacktrace --info \
26+
-Dorg.mongodb.test.uri=${MONGODB_URI} \
27+
-Dtest.single=RetryableWritesProseTest driver-sync:test driver-async:test
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.async.client;
18+
19+
import com.mongodb.MongoClientException;
20+
import com.mongodb.MongoException;
21+
import com.mongodb.async.FutureResultCallback;
22+
import com.mongodb.client.test.CollectionHelper;
23+
import org.bson.Document;
24+
import org.bson.codecs.DocumentCodec;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
28+
import java.util.concurrent.TimeUnit;
29+
30+
import static com.mongodb.ClusterFixture.getServerStatus;
31+
import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet;
32+
import static com.mongodb.ClusterFixture.isSharded;
33+
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
34+
import static com.mongodb.ClusterFixture.serverVersionLessThan;
35+
import static org.junit.Assert.assertTrue;
36+
import static org.junit.Assume.assumeTrue;
37+
38+
public class RetryableWritesProseTest extends DatabaseTestCase {
39+
private CollectionHelper<Document> collectionHelper;
40+
41+
@Before
42+
@Override
43+
public void setUp() {
44+
assumeTrue(canRunTests());
45+
super.setUp();
46+
47+
collectionHelper = new CollectionHelper<Document>(new DocumentCodec(), collection.getNamespace());
48+
collectionHelper.create();
49+
}
50+
51+
@Test
52+
public void testRetryWritesWithInsertOneAgainstMMAPv1RaisesError() {
53+
boolean exceptionFound = false;
54+
55+
try {
56+
FutureResultCallback<Void> callback = new FutureResultCallback<Void>();
57+
collection.insertOne(Document.parse("{ x : 1 }"), callback);
58+
futureResult(callback);
59+
} catch (MongoClientException e) {
60+
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
61+
+ "Please add retryWrites=false to your connection string."));
62+
assertTrue(((MongoException) e.getCause()).getCode() == 20);
63+
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
64+
exceptionFound = true;
65+
}
66+
assertTrue(exceptionFound);
67+
}
68+
69+
@Test
70+
public void testRetryWritesWithFindOneAndDeleteAgainstMMAPv1RaisesError() {
71+
boolean exceptionFound = false;
72+
73+
try {
74+
FutureResultCallback<Document> callback = new FutureResultCallback<Document>();
75+
collection.findOneAndDelete(Document.parse("{ x : 1 }"), callback);
76+
futureResult(callback);
77+
} catch (MongoClientException e) {
78+
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
79+
+ "Please add retryWrites=false to your connection string."));
80+
assertTrue(((MongoException) e.getCause()).getCode() == 20);
81+
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
82+
exceptionFound = true;
83+
}
84+
assertTrue(exceptionFound);
85+
}
86+
87+
private boolean canRunTests() {
88+
Document storageEngine = (Document) getServerStatus().get("storageEngine");
89+
90+
return ((isSharded() || isDiscoverableReplicaSet())
91+
&& storageEngine != null && storageEngine.get("name").equals("mmapv1")
92+
&& serverVersionAtLeast(3, 6) && serverVersionLessThan(4, 1));
93+
}
94+
95+
private <T> T futureResult(final FutureResultCallback<T> callback) {
96+
try {
97+
return callback.get(30, TimeUnit.SECONDS);
98+
} catch (Throwable t) {
99+
throw MongoException.fromThrowable(t);
100+
}
101+
}
102+
}

driver-core/src/main/com/mongodb/operation/CommandOperationHelper.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb.operation;
1818

1919
import com.mongodb.Function;
20+
import com.mongodb.MongoClientException;
2021
import com.mongodb.MongoCommandException;
2122
import com.mongodb.MongoException;
2223
import com.mongodb.MongoNodeIsRecoveringException;
@@ -714,7 +715,7 @@ public R call(final ConnectionSource source, final Connection connection) {
714715
if (isRetryWritesEnabled(command)) {
715716
logUnableToRetry(command.getFirstKey(), e);
716717
}
717-
throw exception;
718+
throw transformWriteException(exception);
718719
}
719720
} finally {
720721
connection.release();
@@ -824,7 +825,8 @@ private void checkRetryableException(final Throwable originalError, final Single
824825
if (isRetryWritesEnabled(command)) {
825826
logUnableToRetry(command.getFirstKey(), originalError);
826827
}
827-
releasingCallback.onResult(null, originalError);
828+
releasingCallback.onResult(null, originalError instanceof MongoException
829+
? transformWriteException((MongoException) originalError) : originalError);
828830
} else {
829831
oldConnection.release();
830832
oldSource.release();
@@ -1009,6 +1011,14 @@ static void logUnableToRetry(final String operation, final Throwable originalErr
10091011
}
10101012
}
10111013

1014+
static MongoException transformWriteException(final MongoException exception) {
1015+
if (exception.getCode() == 20 && exception.getMessage().contains("Transaction numbers")) {
1016+
return new MongoClientException("This MongoDB deployment does not support retryable writes. "
1017+
+ "Please add retryWrites=false to your connection string.", exception);
1018+
}
1019+
return exception;
1020+
}
1021+
10121022
private CommandOperationHelper() {
10131023
}
10141024
}

driver-core/src/main/com/mongodb/operation/MixedBulkWriteOperation.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import static com.mongodb.operation.CommandOperationHelper.logRetryExecute;
5353
import static com.mongodb.operation.CommandOperationHelper.logUnableToRetry;
5454
import static com.mongodb.operation.CommandOperationHelper.shouldAttemptToRetryWrite;
55+
import static com.mongodb.operation.CommandOperationHelper.transformWriteException;
5556
import static com.mongodb.operation.OperationHelper.AsyncCallableWithConnection;
5657
import static com.mongodb.operation.OperationHelper.AsyncCallableWithConnectionAndSource;
5758
import static com.mongodb.operation.OperationHelper.CallableWithConnectionAndSource;
@@ -290,7 +291,7 @@ private BulkWriteResult executeBulkWriteBatch(final WriteBinding binding, final
290291
if (originalBatch.getRetryWrites()) {
291292
logUnableToRetry(originalBatch.getPayload().getPayloadType().toString(), exception);
292293
}
293-
throw exception;
294+
throw transformWriteException(exception);
294295
} else {
295296
return retryExecuteBatches(binding, currentBatch, exception);
296297
}
@@ -487,7 +488,7 @@ public void onResult(final BsonDocument result, final Throwable t) {
487488
addBatchResult((BsonDocument) ((MongoWriteConcernWithResponseException) t).getResponse(), binding, connection,
488489
batch, retryWrites, callback);
489490
} else {
490-
callback.onResult(null, t);
491+
callback.onResult(null, t instanceof MongoException ? transformWriteException((MongoException) t) : t);
491492
}
492493
} else {
493494
retryExecuteBatchesAsync(binding, batch, t, callback.releaseConnectionAndGetWrapped());
@@ -521,7 +522,7 @@ private void addBatchResult(final BsonDocument result, final AsyncWriteBinding b
521522
if (retryWrites) {
522523
logUnableToRetry(batch.getPayload().getPayloadType().toString(), batch.getError());
523524
}
524-
callback.onResult(null, batch.getError());
525+
callback.onResult(null, transformWriteException(batch.getError()));
525526
} else {
526527
callback.onResult(batch.getResult(), null);
527528
}

driver-core/src/test/functional/com/mongodb/ClusterFixture.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public static boolean serverVersionLessThan(final List<Integer> versionArray) {
140140
}
141141

142142
public static boolean serverVersionLessThan(final int majorVersion, final int minorVersion) {
143-
return serverVersionAtLeast(asList(majorVersion, minorVersion, 0));
143+
return serverVersionLessThan(asList(majorVersion, minorVersion, 0));
144144
}
145145

146146
public static boolean serverVersionLessThan(final String versionString) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.client;
18+
19+
import com.mongodb.MongoClientException;
20+
import com.mongodb.MongoException;
21+
import org.bson.Document;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
25+
import static com.mongodb.ClusterFixture.getServerStatus;
26+
import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet;
27+
import static com.mongodb.ClusterFixture.isSharded;
28+
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
29+
import static com.mongodb.ClusterFixture.serverVersionLessThan;
30+
import static org.junit.Assert.assertTrue;
31+
import static org.junit.Assume.assumeTrue;
32+
33+
public class RetryableWritesProseTest extends DatabaseTestCase {
34+
35+
@Before
36+
@Override
37+
public void setUp() {
38+
assumeTrue(canRunTests());
39+
super.setUp();
40+
}
41+
42+
@Test
43+
public void testRetryWritesWithInsertOneAgainstMMAPv1RaisesError() {
44+
boolean exceptionFound = false;
45+
46+
try {
47+
collection.insertOne(Document.parse("{x: 1}"));
48+
} catch (MongoClientException e) {
49+
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
50+
+ "Please add retryWrites=false to your connection string."));
51+
assertTrue(((MongoException) e.getCause()).getCode() == 20);
52+
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
53+
exceptionFound = true;
54+
}
55+
assertTrue(exceptionFound);
56+
}
57+
58+
@Test
59+
public void testRetryWritesWithFindOneAndDeleteAgainstMMAPv1RaisesError() {
60+
boolean exceptionFound = false;
61+
62+
try {
63+
collection.findOneAndDelete(Document.parse("{x: 1}"));
64+
} catch (MongoClientException e) {
65+
assertTrue(e.getMessage().equals("This MongoDB deployment does not support retryable writes. "
66+
+ "Please add retryWrites=false to your connection string."));
67+
assertTrue(((MongoException) e.getCause()).getCode() == 20);
68+
assertTrue(e.getCause().getMessage().contains("Transaction numbers"));
69+
exceptionFound = true;
70+
}
71+
assertTrue(exceptionFound);
72+
}
73+
74+
private boolean canRunTests() {
75+
Document storageEngine = (Document) getServerStatus().get("storageEngine");
76+
77+
return ((isSharded() || isDiscoverableReplicaSet())
78+
&& storageEngine != null && storageEngine.get("name").equals("mmapv1")
79+
&& serverVersionAtLeast(3, 6) && serverVersionLessThan(4, 1));
80+
}
81+
}

0 commit comments

Comments
 (0)