Skip to content

Commit fa16d59

Browse files
committed
JAVA-2815: Support TransientTransactionError for MongoSocketException
This commit adds the TransientTransactionError error label to any MongoSocketException thrown while a transaction is active.
1 parent 9c9ab77 commit fa16d59

File tree

7 files changed

+294
-31
lines changed

7 files changed

+294
-31
lines changed

driver-async/src/main/com/mongodb/async/client/OperationExecutorImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package com.mongodb.async.client;
1818

1919
import com.mongodb.MongoClientException;
20+
import com.mongodb.MongoException;
2021
import com.mongodb.MongoInternalException;
22+
import com.mongodb.MongoSocketException;
2123
import com.mongodb.ReadConcern;
2224
import com.mongodb.ReadPreference;
2325
import com.mongodb.async.SingleResultCallback;
@@ -107,6 +109,9 @@ public void onResult(final ClientSession clientSession, final Throwable t) {
107109
@Override
108110
public void onResult(final T result, final Throwable t) {
109111
try {
112+
if (t instanceof MongoSocketException && session != null && session.hasActiveTransaction()) {
113+
((MongoException) t).addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL);
114+
}
110115
errHandlingCallback.onResult(result, t);
111116
} finally {
112117
binding.release();

driver-async/src/test/functional/com/mongodb/async/client/TransactionsTest.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.mongodb.Block;
2020
import com.mongodb.ClientSessionOptions;
2121
import com.mongodb.MongoCommandException;
22+
import com.mongodb.MongoException;
2223
import com.mongodb.MongoNamespace;
2324
import com.mongodb.MongoWriteConcernException;
2425
import com.mongodb.ReadConcern;
@@ -121,6 +122,11 @@ public void setUp() {
121122
collectionHelper.insertDocuments(documents, WriteConcern.MAJORITY);
122123
}
123124

125+
126+
if (definition.containsKey("failPoint")) {
127+
collectionHelper.runAdminCommand(definition.getDocument("failPoint"));
128+
}
129+
124130
BsonDocument clientOptions = definition.getDocument("clientOptions", new BsonDocument());
125131

126132
mongoClient = MongoClients.create(Fixture.getMongoClientBuilderFromConnectionString()
@@ -213,6 +219,12 @@ public void cleanUp() {
213219
if (mongoClient != null) {
214220
mongoClient.close();
215221
}
222+
223+
if (definition.containsKey("failPoint")) {
224+
collectionHelper.runAdminCommand(new BsonDocument("configureFailPoint",
225+
definition.getDocument("failPoint").getString("configureFailPoint"))
226+
.append("mode", new BsonString("off")));
227+
}
216228
}
217229

218230
private void closeAllSessions() {
@@ -278,24 +290,37 @@ public void execute() {
278290
assertFalse(String.format("Expected error code '%s' but none thrown", getErrorCodeNameField(expectedResult)),
279291
hasErrorCodeNameField(expectedResult));
280292
} catch (RuntimeException e) {
293+
boolean passedAssertion = false;
294+
if (hasErrorLabelContainsField(expectedResult)) {
295+
if (e instanceof MongoException) {
296+
MongoException mongoException = (MongoException) e;
297+
for (String curErrorLabel : getErrorLabelContainsField(expectedResult)) {
298+
assertTrue(String.format("Expected error label '%s but found labels '%s'", curErrorLabel,
299+
mongoException.getErrorLabels()),
300+
mongoException.hasErrorLabel(curErrorLabel));
301+
}
302+
passedAssertion = true;
303+
}
304+
}
281305
if (hasErrorContainsField(expectedResult)) {
282306
String expectedError = getErrorContainsField(expectedResult);
283307
assertTrue(String.format("Expected '%s' but got '%s'", expectedError, e.getMessage()),
284308
e.getMessage().toLowerCase().contains(expectedError.toLowerCase()));
285-
} else if (hasErrorCodeNameField(expectedResult) || (e instanceof MongoCommandException)
286-
|| (e instanceof MongoWriteConcernException)) {
309+
passedAssertion = true;
310+
}
311+
if (hasErrorCodeNameField(expectedResult)) {
287312
String expectedErrorCodeName = getErrorCodeNameField(expectedResult);
288313
if (e instanceof MongoCommandException) {
289314
assertEquals(expectedErrorCodeName, ((MongoCommandException) e).getErrorCodeName());
315+
passedAssertion = true;
290316
} else if (e instanceof MongoWriteConcernException) {
291317
assertEquals(expectedErrorCodeName, ((MongoWriteConcernException) e).getWriteConcernError().getCodeName());
292-
} else {
293-
throw e;
318+
passedAssertion = true;
294319
}
295-
} else {
296-
throw e;
297320
}
298-
}
321+
if (!passedAssertion) {
322+
throw e;
323+
} }
299324
}
300325
} finally {
301326
closeAllSessions();
@@ -333,6 +358,18 @@ private String getErrorField(final BsonValue expectedResult, final String key) {
333358
}
334359
}
335360

361+
private boolean hasErrorLabelContainsField(final BsonValue expectedResult) {
362+
return hasErrorField(expectedResult, "errorLabelsContain");
363+
}
364+
365+
private List<String> getErrorLabelContainsField(final BsonValue expectedResult) {
366+
List<String> errorLabelContainsList = new ArrayList<String>();
367+
for (BsonValue cur : expectedResult.asDocument().getArray("errorLabelsContain")) {
368+
errorLabelContainsList.add(cur.asString().getValue());
369+
}
370+
return errorLabelContainsList;
371+
}
372+
336373
private boolean hasErrorContainsField(final BsonValue expectedResult) {
337374
return hasErrorField(expectedResult, "errorContains");
338375
}

driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,4 +371,8 @@ public void killAllSessions() {
371371
new CommandWriteOperation<BsonDocument>("admin", new BsonDocument("killAllSessions", new BsonArray()),
372372
new BsonDocumentCodec());
373373
}
374+
375+
public void runAdminCommand(final BsonDocument command) {
376+
new CommandWriteOperation<BsonDocument>("admin", command, new BsonDocumentCodec()).execute(getBinding());
377+
}
374378
}

driver-core/src/test/resources/transactions/README.rst

Lines changed: 101 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,56 @@ tests and the Command Monitoring Spec tests.
1717
Several prose tests, which are not easily expressed in YAML, are also presented
1818
in this file. Those tests will need to be manually implemented by each driver.
1919

20+
Server Fail Point
21+
=================
22+
23+
Some tests depend on a server fail point, expressed in the ``failPoint`` field.
24+
For example the ``failCommand`` fail point allows the client to force the
25+
server to return an error. Keep in mind that the fail point only triggers for
26+
commands listed in the "failCommands" field. See `SERVER-35004`_ and
27+
`SERVER-35083`_ for more information.
28+
29+
.. _SERVER-35004: https://jira.mongodb.org/browse/SERVER-35004
30+
.. _SERVER-35083: https://jira.mongodb.org/browse/SERVER-35083
31+
32+
The ``failCommand`` fail point may be configured like so::
33+
34+
db.adminCommand({
35+
configureFailPoint: "failCommand",
36+
mode: <string|document>,
37+
data: {
38+
failCommands: ["commandName", "commandName2"],
39+
closeConnection: <true|false>,
40+
errorCode: <Number>,
41+
writeConcernError: <document>
42+
}
43+
});
44+
45+
``mode`` is a generic fail point option and may be assigned a string or document
46+
value. The string values ``"alwaysOn"`` and ``"off"`` may be used to enable or
47+
disable the fail point, respectively. A document may be used to specify either
48+
``times`` or ``skip``, which are mutually exclusive:
49+
50+
- ``{ times: <integer> }`` may be used to limit the number of times the fail
51+
point may trigger before transitioning to ``"off"``.
52+
- ``{ skip: <integer> }`` may be used to defer the first trigger of a fail
53+
point, after which it will transition to ``"alwaysOn"``.
54+
55+
The ``data`` option is a document that may be used to specify options that
56+
control the fail point's behavior. ``failCommand`` supports the following
57+
``data`` options, which may be combined if desired:
58+
59+
- ``failCommands``: Required, the list of command names to fail.
60+
- ``closeConnection``: Boolean option, which defaults to ``false``. If
61+
``true``, the connection on which the command is executed will be closed
62+
and the client will see a network error.
63+
- ``errorCode``: Integer option, which is unset by default. If set, the
64+
specified command error code will be returned as a command error.
65+
- ``writeConcernError``: A document, which is unset by default. If set, the
66+
server will return this document in the "writeConcernError" field. This
67+
failure response only applies to commands that support write concern and
68+
happens *after* the command finishes (regardless of success or failure).
69+
2070
Test Format
2171
===========
2272

@@ -35,41 +85,54 @@ Each YAML file has the following keys:
3585

3686
- ``clientOptions``: Optional, parameters to pass to MongoClient().
3787

88+
- ``failPoint``: Optional, a server failpoint to enable expressed as the
89+
configureFailPoint command to run on the admin database.
90+
3891
- ``sessionOptions``: Optional, parameters to pass to
3992
MongoClient.startSession().
4093

4194
- ``operations``: Array of documents, each describing an operation to be
4295
executed. Each document has the following fields:
4396

44-
- ``name``: The name of the operation on ``object``.
97+
- ``name``: The name of the operation on ``object``.
98+
99+
- ``object``: The name of the object to perform the operation on. Can be
100+
"database", "collection", "session0", or "session1".
101+
102+
- ``collectionOptions``: Optional, parameters to pass to the Collection()
103+
used for this operation.
104+
105+
- ``command_name``: Present only when ``name`` is "runCommand". The name
106+
of the command to run. Required for languages that are unable preserve
107+
the order keys in the "command" argument when parsing JSON/YAML.
108+
109+
- ``arguments``: Optional, the names and values of arguments.
45110

46-
- ``object``: The name of the object to perform the operation on. Can be
47-
"database", collection", "session0", or "session1".
111+
- ``result``: The return value from the operation, if any. If the
112+
operation is expected to return an error, the ``result`` has one of
113+
the following fields:
48114

49-
- ``collectionOptions``: Optional, parameters to pass to the Collection()
50-
used for this operation.
115+
- ``errorContains``: A substring of the expected error message.
51116

52-
- ``command_name``: Present only when ``name`` is "runCommand". The name
53-
of the command to run. Required for languages that are unable preserve
54-
the order keys in the "command" argument when parsing JSON/YAML.
117+
- ``errorCodeName``: The expected "codeName" field in the server
118+
error response.
55119

56-
- ``arguments``: Optional, the names and values of arguments.
120+
- ``errorLabelsContain``: A list of error label strings that the
121+
error is expected to have.
57122

58-
- ``result``: The return value from the operation, if any. If the
59-
operation is expected to return an error, the ``result`` has one field,
60-
``errorContains``, which is a substring of the expected error message
61-
or ``errorCodeName``, which is the expected server error "codeName".
123+
- ``errorLabelsOmit``: A list of error label strings that the
124+
error is expected not to have.
62125

63126
- ``expectations``: Optional list of command-started events.
64127

65128
- ``outcome``: Document describing the return value and/or expected state of
66129
the collection after the operation is executed. Contains the following
67130
fields:
68131

69-
- ``collection``:
132+
- ``collection``:
70133

71-
- ``data``: The data that should exist in the collection after the
72-
operations have run.
134+
- ``data``: The data that should exist in the collection after the
135+
operations have run.
73136

74137
Use as integration tests
75138
========================
@@ -96,6 +159,8 @@ Then for each element in ``tests``:
96159
create it explicitly.)
97160
#. If the YAML file contains a ``data`` array, insert the documents in ``data``
98161
into the test collection, using writeConcern "majority".
162+
#. If ``failPoint`` is specified, its value is a configureFailPoint command.
163+
Run the command on the admin database to enable the fail point.
99164
#. Create a **new** MongoClient ``client``, with Command Monitoring listeners
100165
enabled. (Using a new MongoClient for each test ensures a fresh session pool
101166
that hasn't executed any transactions previously, so the tests can assert
@@ -125,11 +190,22 @@ Then for each element in ``tests``:
125190
method threw an exception or returned an error, and that the value of the
126191
"errorContains" field matches the error string. "errorContains" is a
127192
substring (case-insensitive) of the actual error message.
193+
128194
If the result document has an "errorCodeName" field, verify that the
129195
method threw a command failed exception or returned an error, and that
130196
the value of the "errorCodeName" field matches the "codeName" in the
131197
server error response.
132-
If the operation returns a raw command response, eg from ``runCommand``,
198+
199+
If the result document has an "errorLabelsContain" field, verify that the
200+
method threw an exception or returned an error. Verify that all of the
201+
error labels in "errorLabelsContain" are present in the error or exception
202+
using the ``hasErrorLabel`` method.
203+
204+
If the result document has an "errorLabelsOmit" field, verify that the
205+
method threw an exception or returned an error. Verify that none of the
206+
error labels in "errorLabelsOmit" are present in the error or exception
207+
using the ``hasErrorLabel`` method.
208+
- If the operation returns a raw command response, eg from ``runCommand``,
133209
then compare only the fields present in the expected result document.
134210
Otherwise, compare the method's return value to ``result`` using the same
135211
logic as the CRUD Spec Tests runner.
@@ -139,6 +215,14 @@ Then for each element in ``tests``:
139215
compare them to the actual command-started events using the
140216
same logic as the Command Monitoring Spec Tests runner, plus the rules in
141217
the Command-Started Events instructions below.
218+
#. If ``failPoint`` is specified, disable the fail point to avoid spurious
219+
failures in subsequent tests. The fail point may be disabled like so::
220+
221+
db.adminCommand({
222+
configureFailPoint: <fail point name>,
223+
mode: "off"
224+
});
225+
142226
#. For each element in ``outcome``:
143227

144228
- If ``name`` is "collection", verify that the test collection contains
@@ -148,7 +232,6 @@ Then for each element in ``tests``:
148232

149233
TODO:
150234

151-
- drivers MUST NOT retry writes in a transaction even when retryWrites=true, needs to use failpoint.
152235
- drivers MUST retry commit/abort, needs to use failpoint.
153236
- test writeConcernErrors
154237

0 commit comments

Comments
 (0)