JAVA-6055 Implement prose backpressure retryable writes tests#1929
JAVA-6055 Implement prose backpressure retryable writes tests#1929stIncMale wants to merge 2 commits intomongodb:backpressurefrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds/updates prose tests to cover the retryable writes “backpressure retryable writes” scenarios from the spec updates, including refactoring test infrastructure to configure fail points in reaction to command events.
Changes:
- Introduce
ConfigureFailPointCommandListenerto enable a failpoint once upon matching aCommandEvent, and automatically disable it via try-with-resources. - Refactor existing retryable reads/writes prose tests to use the new helper and updated spec link references.
- Add new “error propagation after encountering multiple errors” retryable writes prose test cases (with Case 1 temporarily disabled behind TODO-BACKPRESSURE).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| driver-sync/src/test/functional/com/mongodb/internal/event/ConfigureFailPointCommandListener.java | New test utility to configure/cleanup failpoints triggered by command events. |
| driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java | Refactor existing prose tests; add new multi-error propagation cases. |
| driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java | Update prose-test references and align helper usage/signatures. |
| driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java | Mirror sync prose test updates via sync-adapter delegation; add new cases. |
| driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java | Update prose-test references and align helper usage/signatures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Show resolved
Hide resolved
...-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java
Outdated
Show resolved
Hide resolved
53e526f to
44c8119
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
44c8119 to
973ee55
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...r-sync/src/test/functional/com/mongodb/internal/event/ConfigureFailPointCommandListener.java
Outdated
Show resolved
Hide resolved
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Outdated
Show resolved
Hide resolved
973ee55 to
758b89e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Outdated
Show resolved
Hide resolved
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Outdated
Show resolved
Hide resolved
758b89e to
d270460
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java
Show resolved
Hide resolved
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Outdated
Show resolved
Hide resolved
driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java
Outdated
Show resolved
Hide resolved
The relevant spec changes: - https://github.com/mongodb/specifications/blame/ba14b6bdc1dc695aa9cc20ccf9378592da1b2329/source/retryable-writes/tests/README.md#L265-L418 - See also https://jira.mongodb.org/browse/DRIVERS-3432 for the required fixes for "Test 3 Case 3" JAVA-6055
d270460 to
72d70e4
Compare
nhachicha
left a comment
There was a problem hiding this comment.
Good idea extracting ConfigureFailPointCommandListener as a reusable utility 👍
some minor comments from me & Claude review
| fail("The listener was closed before (in the happens-before order) it attempted to configure the fail point"); | ||
| } else { | ||
| assertTrue(failPointFuture.isDone()); | ||
| assertNotNull(failPointFuture.get()).close(); |
There was a problem hiding this comment.
[minor]
If failPointFuture is completed exceptionally (via completeExceptionally), then failPointFuture.get() in close() will throw, so the normal FailPoint.close() is skipped and the fail point may remain enabled.
This is, however, low-risk since there may be nothing to clean up if FailPoint.enable() fails ...
There was a problem hiding this comment.
If
failPointFutureis completed exceptionally (viacompleteExceptionally), thenfailPointFuture.get()inclose()will throw, so the normalFailPoint.close()is skipped
No, the execution you are proposing is forbidden, and the conclusion "so the normal FailPoint.close() is skipped" is incorrect, assuming that you imply that a reference to a FailPoint was returned (I have to assume this, as otherwise there is no reference which we could call close through):
- Due to the
synchronizedblocks, there is a guarantee that either the wholeonEventis ordered before (in the happens-before order)close, orcloseis ordered beforeonEvent1. failPointFuture.get()is not part of any execution, wherecloseis ordered beforeonEvent.- In all executions where
onEventis ordered beforeclose,failPointFuture.get()throws if and only iffailPointFuture.completeExceptionally(e)was executed as part ofonEvent. When the latter is the case, no fail point were enabled2, and, therefore, no fail point needs to be closed.
1 Strictly speaking, that's not the right way to say what I am saying, but I don't want to make things more complex, because that will change nothing.
2 Actually, FailPoint.enable throwing does not guarantee that the configureFailPoint command failed to configure the fail point. That's a bug in FailPoint.enable, I fixed it in main: #1931.
There was a problem hiding this comment.
If, however, you were talking exactly about the bug in FailPoin.enable (I introduced it in the past when added this class):
there may be nothing to clean up if
FailPoint.enable()fails
then no code added/modified in the current PR is relevant, and any usage of FailPoint is problematic because of that bug.
If that's the case, then it should be fine when #1931 is merged.
| @Override | ||
| public void close() throws InterruptedException, ExecutionException { | ||
| synchronized (lock) { | ||
| if (failPointFuture.cancel(true)) { |
There was a problem hiding this comment.
[TIL]
mayInterruptIfRunning is no-op
mayInterruptIfRunning - this value has no effect in this implementation because interrupts are not used to control processing.
There was a problem hiding this comment.
Thank you, I know. But we have to pass something.
| + " data: {\n" | ||
| + " failCommands: [\"" + operationName + "\"],\n" | ||
| + " failCommands: [\"" + expectedCommandName + "\"],\n" | ||
| + " errorCode: 6,\n" |
There was a problem hiding this comment.
[claude-review]
Trailing comma in generated JSON when write=false.
The old code avoided this by placing errorCode last
The same pattern appears in retriesOnSameMongosWhenAnotherNotAvailable
| Predicate<CommandEvent> configureFailPointEventMatcher = event -> { | ||
| if (event instanceof CommandFailedEvent) { | ||
| CommandFailedEvent commandFailedEvent = (CommandFailedEvent) event; | ||
| MongoException cause = assertInstanceOf(MongoException.class, commandFailedEvent.getThrowable()); |
There was a problem hiding this comment.
[claude-review]
The predicates use assertInstanceOf(MongoException.class, ...) which throws AssertionFailedError if the type doesn't match. A Predicate.test() should return false, not throw. The exception is caught by onEvent() and routed to completeExceptionally, so it works, but the semantics are surprising. Consider using a plain instanceof check instead.
| Predicate<CommandEvent> configureFailPointEventMatcher = event -> { | ||
| if (event instanceof CommandFailedEvent) { | ||
| CommandFailedEvent commandFailedEvent = (CommandFailedEvent) event; | ||
| MongoException cause = assertInstanceOf(MongoException.class, commandFailedEvent.getThrowable()); |
There was a problem hiding this comment.
| import static com.mongodb.assertions.Assertions.assertTrue; | ||
| import static com.mongodb.assertions.Assertions.fail; | ||
|
|
||
| public final class ConfigureFailPointCommandListener implements CommandListener, AutoCloseable { |
There was a problem hiding this comment.
Worth annotating with @ThreadSafe ?
| + " failCommands: ['" + commandName + "'],\n" | ||
| + " errorCode: 91,\n" | ||
| + " blockConnection: true,\n" | ||
| + " blockTimeMS: 1000,\n" |
There was a problem hiding this comment.
Trailing comma issue when write == false. I wonder if we should keep the append approach of building the BsonDocument
similar to https://github.com/mongodb/mongo-java-driver/pull/1929/changes#r3005045523
| + " data: {\n" | ||
| + " failCommands: [\"" + operationName + "\"],\n" | ||
| + " failCommands: [\"" + expectedCommandName + "\"],\n" | ||
| + " errorCode: 6,\n" |
There was a problem hiding this comment.
| + "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "']\n" | ||
| + " }\n" | ||
| + "}\n"); | ||
| Predicate<CommandEvent> configureFailPointEventMatcher = event -> event instanceof CommandFailedEvent; |
There was a problem hiding this comment.
[minor]
Both the initial and the listener fail points use error code 91, for consistency and defensiveness, checking code == 91 wouldn't hurt — if something unexpected fails first (e.g., a different command), the test would configure the second fail point at the wrong time and produce a confusing failure.
The relevant spec changes:
I recommend reviewing the first two commits one by one to simplify reviewing: the first commit does the refactoring, including the necessary refactoring (the introduction of
ConfigureFailPointCommandListener), the second commit adds the new tests.I used the
TODO-BACKPRESSURE Valentincomments as described in #1918.JAVA-6055