Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.reactivestreams.client;

import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;

/**
* <a href="https://github.com/mongodb/specifications/blob/master/source/client-backpressure/tests/README.md#prose-tests">
* Prose Tests</a>.
Comment thread
stIncMale marked this conversation as resolved.
*/
final class BackpressureProseTest extends com.mongodb.client.BackpressureProseTest {
@Override
protected MongoClient createClient(final MongoClientSettings mongoClientSettings) {
return new SyncMongoClient(mongoClientSettings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.client;

import com.mongodb.MongoClientSettings;
import com.mongodb.MongoServerException;
import com.mongodb.internal.connection.TestCommandListener;
import com.mongodb.internal.time.StartTime;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.Document;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.time.Duration;

import static com.mongodb.ClusterFixture.serverVersionAtLeast;
import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL;
import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL;
import static com.mongodb.client.Fixture.getDefaultDatabaseName;
import static com.mongodb.client.Fixture.getMongoClientSettings;
import static com.mongodb.client.Fixture.getPrimary;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

/**
* <a href="https://github.com/mongodb/specifications/blob/master/source/client-backpressure/tests/README.md#prose-tests">
* Prose Tests</a>.
Comment thread
stIncMale marked this conversation as resolved.
*/
public class BackpressureProseTest {
protected MongoClient createClient(final MongoClientSettings mongoClientSettings) {
return MongoClients.create(mongoClientSettings);
}

/**
* <a href="https://github.com/mongodb/specifications/blob/master/source/client-backpressure/tests/README.md#test-1-operation-retry-uses-exponential-backoff">
* Test 1: Operation Retry Uses Exponential Backoff</a>.
*/
@Test
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141 if PR 1899 is merged")
void operationRetryUsesExponentialBackoff() throws InterruptedException {
assumeTrue(serverVersionAtLeast(4, 4));
BsonDocument configureFailPoint = BsonDocument.parse(
"{\n"
+ " configureFailPoint: 'failCommand',\n"
+ " mode: 'alwaysOn',\n"
+ " data: {\n"
+ " failCommands: ['insert'],\n"
+ " errorCode: 2,\n"
+ " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n"
+ " }\n"
+ "}\n");
try (MongoClient client = createClient(getMongoClientSettings());
FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) {
Comment thread
stIncMale marked this conversation as resolved.
MongoCollection<Document> collection = dropAndGetCollection("operationRetryUsesExponentialBackoff", client);
long noBackoffTimeMillis = measureFailedInsertDuration(collection, false).toMillis();
long withBackoffTimeMillis = measureFailedInsertDuration(collection, true).toMillis();
long expectedMaxVarianceMillis = 300;
long maxTotalBackoffMillis = 300;
long actualAbsDiffMillis = Math.abs(withBackoffTimeMillis - (noBackoffTimeMillis + maxTotalBackoffMillis));
assertTrue(actualAbsDiffMillis < expectedMaxVarianceMillis,
format("Expected actualAbsDiffMillis < %d ms, but was %d ms (|%d ms - (%d ms + %d ms)|)",
expectedMaxVarianceMillis, actualAbsDiffMillis, withBackoffTimeMillis, noBackoffTimeMillis, maxTotalBackoffMillis));
}
}

private static Duration measureFailedInsertDuration(final MongoCollection<Document> collection, final boolean retryBackoff) {
if (!retryBackoff) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My codereview skill picked this up:

🟡 Timing assertion fragility (Test 1, L83-88) — Still stands. The measureFailedInsertDuration only configures the jitter supplier for the retryBackoff == false case (commented-out line 87: setTestJitterSupplier(() -> 0)). The retryBackoff == true path has no corresponding jitter override, meaning it will use random jitter when enabled, making the 300ms variance window non-deterministic.

Per the spec, the backoff-enabled measurement should also use a controlled jitter value (close to 1). This will need an else branch when the TODO is uncommented.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Done in eb753df.

The crazy part is that I was sure the following step

iv. Configure the random number generator used for jitter to always return a number as close as possible to 1.

does not exist...

This is a 100% valid concern from the standpoint of "implement the damn test the way it is specified". However, given that jitter is guaranteed to be <= 1 when it is random, and that the 300 ms maxTotalBackoffMillis is computed for the upper jitter bound of 1, specifying jitter supplier 1 changes absolutely nothing for the test.

// TODO-BACKPRESSURE Valentin uncomment below when https://github.com/mongodb/mongo-java-driver/pull/1899 is merged
// ExponentialBackoff.setTestJitterSupplier(() -> 0);
}
try {
StartTime startTime = StartTime.now();
assertThrows(MongoServerException.class, () -> collection.insertOne(Document.parse("{a: 1}")));
return startTime.elapsed();
} finally {
// TODO-BACKPRESSURE Valentin uncomment below when https://github.com/mongodb/mongo-java-driver/pull/1899 is merged
// ExponentialBackoff.clearTestJitterSupplier();
}
}

/**
* <a href="https://github.com/mongodb/specifications/blob/master/source/client-backpressure/tests/README.md#test-3-overload-errors-are-retried-a-maximum-of-max_retries-times">
* Test 3: Overload Errors are Retried a Maximum of {@code MAX_RETRIES} times</a>.
*/
@Test
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141")
void overloadErrorsAreRetriedAtMostMaxRetriesTimes() throws InterruptedException {
overloadErrorsAreRetriedLimitedNumberOfTimes(null);
}

/**
* <a href="https://github.com/mongodb/specifications/blob/master/source/client-backpressure/tests/README.md#test-4-overload-errors-are-retried-a-maximum-of-maxadaptiveretries-times-when-configured">
* Test 4: Overload Errors are Retried a Maximum of {@code maxAdaptiveRetries} times when configured</a>.
*/
@Test
Comment thread
stIncMale marked this conversation as resolved.
@Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141")
void overloadErrorsAreRetriedAtMostMaxAdaptiveRetriesTimesWhenConfigured() throws InterruptedException {
overloadErrorsAreRetriedLimitedNumberOfTimes(1);
}

private void overloadErrorsAreRetriedLimitedNumberOfTimes(@Nullable final Integer maxAdaptiveRetries)
throws InterruptedException {
assumeTrue(serverVersionAtLeast(4, 4));
TestCommandListener commandListener = new TestCommandListener();
BsonDocument configureFailPoint = BsonDocument.parse(
"{\n"
+ " configureFailPoint: 'failCommand',\n"
+ " mode: 'alwaysOn',\n"
+ " data: {\n"
+ " failCommands: ['find'],\n"
+ " errorCode: 462,\n"
+ " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n"
+ " }\n"
+ "}\n");
try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings())
.maxAdaptiveRetries(maxAdaptiveRetries)
.addCommandListener(commandListener)
.build());
FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) {
Comment thread
stIncMale marked this conversation as resolved.
MongoCollection<Document> collection = dropAndGetCollection("overloadErrorsAreRetriedLimitedNumberOfTimes", client);
commandListener.reset();
MongoServerException exception = assertThrows(MongoServerException.class, () -> collection.find().first());
assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL));
assertTrue(exception.hasErrorLabel(RETRYABLE_ERROR_LABEL));
// TODO-BACKPRESSURE Valentin replace 2 with `MAX_RETRIES` when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141
int expectedAttempts = (maxAdaptiveRetries == null ? 2 : maxAdaptiveRetries) + 1;
assertEquals(expectedAttempts, commandListener.getCommandStartedEvents().size());
Comment thread
stIncMale marked this conversation as resolved.
}
}

private static MongoCollection<Document> dropAndGetCollection(final String name, final MongoClient client) {
MongoCollection<Document> result = client.getDatabase(getDefaultDatabaseName()).getCollection(name);
result.drop();
return result;
}
}