Skip to content

Commit ee972e8

Browse files
committed
Discard ServerSessions involved in network errors
JAVA-3295
1 parent 2f3e156 commit ee972e8

File tree

18 files changed

+1260
-112
lines changed

18 files changed

+1260
-112
lines changed

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

Lines changed: 74 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import com.mongodb.connection.SocketSettings;
3838
import com.mongodb.connection.SslSettings;
3939
import com.mongodb.event.CommandEvent;
40+
import com.mongodb.event.CommandStartedEvent;
4041
import com.mongodb.internal.connection.TestCommandListener;
4142
import com.mongodb.lang.Nullable;
4243
import org.bson.BsonArray;
@@ -52,20 +53,14 @@
5253
import org.junit.Test;
5354
import org.junit.runner.RunWith;
5455
import org.junit.runners.Parameterized;
55-
import util.JsonPoweredTestHelper;
5656

57-
import java.io.File;
58-
import java.io.IOException;
59-
import java.net.URISyntaxException;
6057
import java.util.ArrayList;
61-
import java.util.Collection;
6258
import java.util.HashMap;
6359
import java.util.List;
6460
import java.util.Map;
6561
import java.util.concurrent.TimeUnit;
6662

6763
import static com.mongodb.ClusterFixture.getMultiMongosConnectionString;
68-
import static com.mongodb.JsonTestServerVersionChecker.skipTest;
6964
import static com.mongodb.async.client.Fixture.getConnectionString;
7065
import static com.mongodb.async.client.Fixture.getDefaultDatabaseName;
7166
import static com.mongodb.async.client.Fixture.isSharded;
@@ -75,6 +70,7 @@
7570
import static java.util.concurrent.TimeUnit.MILLISECONDS;
7671
import static org.junit.Assert.assertEquals;
7772
import static org.junit.Assert.assertFalse;
73+
import static org.junit.Assert.assertNotEquals;
7874
import static org.junit.Assert.assertNotNull;
7975
import static org.junit.Assert.assertNull;
8076
import static org.junit.Assert.assertTrue;
@@ -84,7 +80,7 @@
8480

8581
// See https://github.com/mongodb/specifications/tree/master/source/transactions/tests
8682
@RunWith(Parameterized.class)
87-
public class TransactionsTest {
83+
public abstract class AbstractUnifiedTest {
8884

8985
private final String filename;
9086
private final String description;
@@ -104,8 +100,8 @@ public class TransactionsTest {
104100

105101
private static final long MIN_HEARTBEAT_FREQUENCY_MS = 50L;
106102

107-
public TransactionsTest(final String filename, final String description, final BsonArray data, final BsonDocument definition,
108-
final boolean skipTest) {
103+
public AbstractUnifiedTest(final String filename, final String description, final BsonArray data, final BsonDocument definition,
104+
final boolean skipTest) {
109105
this.filename = filename;
110106
this.description = description;
111107
this.databaseName = getDefaultDatabaseName();
@@ -353,7 +349,7 @@ private void executeOperations(final BsonArray operations, final boolean throwEx
353349
BsonValue expectedResult = operation.get("result");
354350
String receiver = operation.getString("object").getValue();
355351
final ClientSession clientSession = receiver.startsWith("session") ? sessionsMap.get(receiver)
356-
: (operation.getDocument("arguments").containsKey("session")
352+
: (operation.getDocument("arguments", new BsonDocument()).containsKey("session")
357353
? sessionsMap.get(operation.getDocument("arguments").getString("session").getValue()) : null);
358354
try {
359355
if (operationName.equals("startTransaction")) {
@@ -401,13 +397,31 @@ public void execute() {
401397
} else {
402398
assertFalse(session.hasActiveTransaction());
403399
}
400+
} else if (operationName.equals("endSession")) {
401+
clientSession.close();
404402
} else if (operation.getBoolean("error", BsonBoolean.FALSE).getValue()) {
405403
try {
406404
helper.getOperationResults(operation, clientSession);
407405
fail("Error expected but none thrown");
408406
} catch (Exception e) {
409407
// Expected failure ignore
410408
}
409+
} else if (operationName.equals("assertDifferentLsidOnLastTwoCommands")) {
410+
List<CommandEvent> events = lastTwoCommandEvents();
411+
assertNotEquals(((CommandStartedEvent) events.get(0)).getCommand().getDocument("lsid"),
412+
((CommandStartedEvent) events.get(1)).getCommand().getDocument("lsid"));
413+
} else if (operationName.equals("assertSameLsidOnLastTwoCommands")) {
414+
List<CommandEvent> events = lastTwoCommandEvents();
415+
assertEquals(((CommandStartedEvent) events.get(0)).getCommand().getDocument("lsid"),
416+
((CommandStartedEvent) events.get(1)).getCommand().getDocument("lsid"));
417+
} else if (operationName.equals("assertSessionDirty")) {
418+
assertNotNull(clientSession);
419+
assertNotNull(clientSession.getServerSession());
420+
assertTrue(clientSession.getServerSession().isMarkedDirty());
421+
} else if (operationName.equals("assertSessionNotDirty")) {
422+
assertNotNull(clientSession);
423+
assertNotNull(clientSession.getServerSession());
424+
assertFalse(clientSession.getServerSession().isMarkedDirty());
411425
} else {
412426
BsonDocument actualOutcome = helper.getOperationResults(operation, clientSession);
413427
if (expectedResult != null) {
@@ -426,46 +440,7 @@ public void execute() {
426440
assertFalse(String.format("Expected error code '%s' but none thrown for operation %s",
427441
getErrorCodeNameField(expectedResult), operationName), hasErrorCodeNameField(expectedResult));
428442
} catch (RuntimeException e) {
429-
boolean passedAssertion = false;
430-
if (hasErrorLabelsContainField(expectedResult)) {
431-
if (e instanceof MongoException) {
432-
MongoException mongoException = (MongoException) e;
433-
for (String curErrorLabel : getErrorLabelsContainField(expectedResult)) {
434-
assertTrue(String.format("Expected error label '%s but found labels '%s' for operation %s",
435-
curErrorLabel, mongoException.getErrorLabels(), operationName),
436-
mongoException.hasErrorLabel(curErrorLabel));
437-
}
438-
passedAssertion = true;
439-
}
440-
}
441-
if (hasErrorLabelsOmitField(expectedResult)) {
442-
if (e instanceof MongoException) {
443-
MongoException mongoException = (MongoException) e;
444-
for (String curErrorLabel : getErrorLabelsOmitField(expectedResult)) {
445-
assertFalse(String.format("Expected error label '%s omitted but found labels '%s' for operation %s",
446-
curErrorLabel, mongoException.getErrorLabels(), operationName),
447-
mongoException.hasErrorLabel(curErrorLabel));
448-
}
449-
passedAssertion = true;
450-
}
451-
}
452-
if (hasErrorContainsField(expectedResult)) {
453-
String expectedError = getErrorContainsField(expectedResult);
454-
assertTrue(String.format("Expected '%s' but got '%s' for operation %s", expectedError, e.getMessage(),
455-
operationName), e.getMessage().toLowerCase().contains(expectedError.toLowerCase()));
456-
passedAssertion = true;
457-
}
458-
if (hasErrorCodeNameField(expectedResult)) {
459-
String expectedErrorCodeName = getErrorCodeNameField(expectedResult);
460-
if (e instanceof MongoCommandException) {
461-
assertEquals(expectedErrorCodeName, ((MongoCommandException) e).getErrorCodeName());
462-
passedAssertion = true;
463-
} else if (e instanceof MongoWriteConcernException) {
464-
assertEquals(expectedErrorCodeName, ((MongoWriteConcernException) e).getWriteConcernError().getCodeName());
465-
passedAssertion = true;
466-
}
467-
}
468-
if (!passedAssertion || throwExceptions) {
443+
if (!assertExceptionState(e, expectedResult, operationName) || throwExceptions) {
469444
throw e;
470445
}
471446
}
@@ -477,6 +452,55 @@ public void execute() {
477452
}
478453
}
479454

455+
private boolean assertExceptionState(final RuntimeException e, final BsonValue expectedResult, final String operationName) {
456+
boolean passedAssertion = false;
457+
if (hasErrorLabelsContainField(expectedResult)) {
458+
if (e instanceof MongoException) {
459+
MongoException mongoException = (MongoException) e;
460+
for (String curErrorLabel : getErrorLabelsContainField(expectedResult)) {
461+
assertTrue(String.format("Expected error label '%s but found labels '%s' for operation %s",
462+
curErrorLabel, mongoException.getErrorLabels(), operationName),
463+
mongoException.hasErrorLabel(curErrorLabel));
464+
}
465+
passedAssertion = true;
466+
}
467+
}
468+
if (hasErrorLabelsOmitField(expectedResult)) {
469+
if (e instanceof MongoException) {
470+
MongoException mongoException = (MongoException) e;
471+
for (String curErrorLabel : getErrorLabelsOmitField(expectedResult)) {
472+
assertFalse(String.format("Expected error label '%s omitted but found labels '%s' for operation %s",
473+
curErrorLabel, mongoException.getErrorLabels(), operationName),
474+
mongoException.hasErrorLabel(curErrorLabel));
475+
}
476+
passedAssertion = true;
477+
}
478+
}
479+
if (hasErrorContainsField(expectedResult)) {
480+
String expectedError = getErrorContainsField(expectedResult);
481+
assertTrue(String.format("Expected '%s' but got '%s' for operation %s", expectedError, e.getMessage(),
482+
operationName), e.getMessage().toLowerCase().contains(expectedError.toLowerCase()));
483+
passedAssertion = true;
484+
}
485+
if (hasErrorCodeNameField(expectedResult)) {
486+
String expectedErrorCodeName = getErrorCodeNameField(expectedResult);
487+
if (e instanceof MongoCommandException) {
488+
assertEquals(expectedErrorCodeName, ((MongoCommandException) e).getErrorCodeName());
489+
passedAssertion = true;
490+
} else if (e instanceof MongoWriteConcernException) {
491+
assertEquals(expectedErrorCodeName, ((MongoWriteConcernException) e).getWriteConcernError().getCodeName());
492+
passedAssertion = true;
493+
}
494+
}
495+
return passedAssertion;
496+
}
497+
498+
private List<CommandEvent> lastTwoCommandEvents() {
499+
List<CommandEvent> events = commandListener.getCommandStartedEvents();
500+
assertTrue(events.size() >= 2);
501+
return events.subList(events.size() - 2, events.size());
502+
}
503+
480504
private TransactionOptions createTransactionOptions(final BsonDocument options) {
481505
TransactionOptions.Builder builder = TransactionOptions.builder();
482506
if (options.containsKey("writeConcern")) {
@@ -554,19 +578,6 @@ private ClientSession nonNullClientSession(@Nullable final ClientSession clientS
554578
return clientSession;
555579
}
556580

557-
@Parameterized.Parameters(name = "{0}: {1}")
558-
public static Collection<Object[]> data() throws URISyntaxException, IOException {
559-
List<Object[]> data = new ArrayList<Object[]>();
560-
for (File file : JsonPoweredTestHelper.getTestFiles("/transactions")) {
561-
BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file);
562-
for (BsonValue test : testDocument.getArray("tests")) {
563-
data.add(new Object[]{file.getName(), test.asDocument().getString("description").getValue(),
564-
testDocument.getArray("data"), test.asDocument(), skipTest(testDocument, test.asDocument())});
565-
}
566-
}
567-
return data;
568-
}
569-
570581
private class TargetedFailPoint {
571582
private final BsonDocument failPointDocument;
572583
private final MongoDatabase adminDB;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 org.bson.BsonArray;
20+
import org.bson.BsonDocument;
21+
import org.bson.BsonValue;
22+
import org.junit.runner.RunWith;
23+
import org.junit.runners.Parameterized;
24+
import util.JsonPoweredTestHelper;
25+
26+
import java.io.File;
27+
import java.io.IOException;
28+
import java.net.URISyntaxException;
29+
import java.util.ArrayList;
30+
import java.util.Collection;
31+
import java.util.List;
32+
33+
import static com.mongodb.JsonTestServerVersionChecker.skipTest;
34+
35+
// See https://github.com/mongodb/specifications/tree/master/source/transactions/tests
36+
@RunWith(Parameterized.class)
37+
public class MainTransactionsTest extends AbstractUnifiedTest {
38+
public MainTransactionsTest(final String filename, final String description, final BsonArray data, final BsonDocument definition,
39+
final boolean skipTest) {
40+
super(filename, description, data, definition, skipTest);
41+
}
42+
43+
@Parameterized.Parameters(name = "{0}: {1}")
44+
public static Collection<Object[]> data() throws URISyntaxException, IOException {
45+
List<Object[]> data = new ArrayList<Object[]>();
46+
for (File file : JsonPoweredTestHelper.getTestFiles("/transactions")) {
47+
BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file);
48+
for (BsonValue test : testDocument.getArray("tests")) {
49+
data.add(new Object[]{file.getName(), test.asDocument().getString("description").getValue(),
50+
testDocument.getArray("data"), test.asDocument(), skipTest(testDocument, test.asDocument())});
51+
}
52+
}
53+
return data;
54+
}
55+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 org.bson.BsonArray;
20+
import org.bson.BsonDocument;
21+
import org.bson.BsonValue;
22+
import org.junit.runner.RunWith;
23+
import org.junit.runners.Parameterized;
24+
import util.JsonPoweredTestHelper;
25+
26+
import java.io.File;
27+
import java.io.IOException;
28+
import java.net.URISyntaxException;
29+
import java.util.ArrayList;
30+
import java.util.Collection;
31+
import java.util.List;
32+
33+
import static com.mongodb.JsonTestServerVersionChecker.skipTest;
34+
35+
// See https://github.com/mongodb/specifications/tree/master/source/sessions/tests
36+
@RunWith(Parameterized.class)
37+
public class SessionsTest extends AbstractUnifiedTest {
38+
public SessionsTest(final String filename, final String description, final BsonArray data, final BsonDocument definition,
39+
final boolean skipTest) {
40+
super(filename, description, data, definition, skipTest);
41+
}
42+
43+
@Parameterized.Parameters(name = "{0}: {1}")
44+
public static Collection<Object[]> data() throws URISyntaxException, IOException {
45+
List<Object[]> data = new ArrayList<Object[]>();
46+
for (File file : JsonPoweredTestHelper.getTestFiles("/sessions")) {
47+
BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file);
48+
for (BsonValue test : testDocument.getArray("tests")) {
49+
data.add(new Object[]{file.getName(), test.asDocument().getString("description").getValue(),
50+
testDocument.getArray("data"), test.asDocument(), skipTest(testDocument, test.asDocument())});
51+
}
52+
}
53+
return data;
54+
}
55+
}

driver-core/src/main/com/mongodb/internal/connection/ClusterClockAdvancingSessionContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,14 @@ public void setRecoveryToken(final BsonDocument recoveryToken) {
106106
public void unpinServerAddress() {
107107
wrapped.unpinServerAddress();
108108
}
109+
110+
@Override
111+
public void markSessionDirty() {
112+
wrapped.markSessionDirty();
113+
}
114+
115+
@Override
116+
public boolean isSessionMarkedDirty() {
117+
return wrapped.isSessionMarkedDirty();
118+
}
109119
}

driver-core/src/main/com/mongodb/internal/connection/DefaultServer.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ public void invalidate(final Throwable t) {
158158
}
159159
}
160160

161+
public void invalidate(final Throwable t, final SessionContext sessionContext) {
162+
notNull("sessionContext", sessionContext);
163+
invalidate(t);
164+
if (t instanceof MongoSocketException && sessionContext.hasSession()) {
165+
sessionContext.markSessionDirty();
166+
}
167+
}
168+
161169
@Override
162170
public void close() {
163171
if (!isClosed()) {
@@ -220,7 +228,7 @@ public <T> T execute(final CommandProtocol<T> protocol, final InternalConnection
220228
invalidate();
221229
return (T) e.getResponse();
222230
} catch (MongoException e) {
223-
invalidate(e);
231+
invalidate(e, sessionContext);
224232
throw e;
225233
}
226234
}
@@ -238,7 +246,7 @@ public void onResult(final T result, final Throwable t) {
238246
invalidate();
239247
callback.onResult((T) ((MongoWriteConcernWithResponseException) t).getResponse(), null);
240248
} else {
241-
invalidate(t);
249+
invalidate(t, sessionContext);
242250
callback.onResult(null, t);
243251
}
244252
} else {

driver-core/src/main/com/mongodb/internal/connection/NoOpSessionContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,14 @@ public void setRecoveryToken(final BsonDocument recoveryToken) {
105105
public void unpinServerAddress() {
106106
throw new UnsupportedOperationException();
107107
}
108+
109+
@Override
110+
public void markSessionDirty() {
111+
throw new UnsupportedOperationException();
112+
}
113+
114+
@Override
115+
public boolean isSessionMarkedDirty() {
116+
return false;
117+
}
108118
}

0 commit comments

Comments
 (0)