Conversation
|
|
||
| timeOffset = 0; | ||
| const originalNow = performance.now.bind(performance); | ||
| sinon.stub(performance, 'now').callsFake(() => originalNow() + timeOffset); |
There was a problem hiding this comment.
This is how spec suggests to test, similar implementations in:
- python: PatchSessionTimeout(0)
- java: Duration.ofSeconds(120)
There was a problem hiding this comment.
Pull request overview
This PR updates the driver’s withTransaction retry-timeout behavior to align with the DRIVERS-3391 spec change: when retrying runs out the overall deadline (CSOT timeoutMS or the legacy 120s cap), it throws a MongoOperationTimeoutError that wraps the last transient error as cause (and propagates error labels onto the timeout error).
Changes:
- Update
ClientSession.withTransactionto throw aMongoOperationTimeoutErroron timeout instead of surfacing the raw transient error. - Add a
makeWithTransactionTimeoutErrorhelper that wraps the last error ascauseand copies error labels. - Extend spec/integration tests to cover the new timeout error behavior (and add
commandFailedEventobservations in the CSOT unified spec test).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/sessions.ts |
Implements timeout wrapping behavior and adds helper to build labeled MongoOperationTimeoutError. |
test/spec/client-side-operations-timeout/convenient-transactions.yml |
Adds commandFailedEvent observation and a new unified test for timeout surfacing after transient retries. |
test/spec/client-side-operations-timeout/convenient-transactions.json |
JSON equivalent updates for the unified spec test additions. |
test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts |
Adds prose integration tests that simulate exceeding the legacy 120s retry window and assert a timeout error is returned. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 3. Assert that the error is a timeout error wrapping the TransientTransactionError. | ||
| expect(result).to.be.instanceOf(MongoOperationTimeoutError); | ||
| expect((result as MongoOperationTimeoutError).cause).to.be.an('error'); | ||
| } |
|
|
||
| // 3. Assert that the error is a timeout error wrapping the commit error. | ||
| expect(result).to.be.instanceOf(MongoOperationTimeoutError); | ||
| expect((result as MongoOperationTimeoutError).cause).to.be.an('error'); |
| // 3. Assert that the error is a timeout error wrapping the TransientTransactionError. | ||
| expect(result).to.be.instanceOf(MongoOperationTimeoutError); | ||
| expect((result as MongoOperationTimeoutError).cause).to.be.an('error'); |
There was a problem hiding this comment.
Pull request overview
This PR updates the driver’s withTransaction retry-timeout behavior to match the updated transactions convenient API spec (DRIVERS-3391 / NODE-7430): when retry attempts run past the allowed deadline (CSOT timeoutMS or legacy 120s), the driver should throw a MongoOperationTimeoutError that wraps the last retryable/transient error as the cause, rather than throwing the transient error directly.
Changes:
- Update
ClientSession.withTransactionto throw aMongoOperationTimeoutError(with labels copied from the cause) when retry deadlines are exceeded, and fix legacy 120s deadline logic when CSOT is enabled. - Add/extend unified spec tests for CSOT convenient transactions to cover transient retry exhaustion behavior.
- Add integration prose tests that stub time to validate the legacy 120-second retry timeout behavior for callback + commit retry paths.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/sessions.ts |
Implements timeout-wrapping behavior for withTransaction retry deadline exhaustion and refines deadline checks for CSOT vs legacy timeout paths. |
test/spec/client-side-operations-timeout/convenient-transactions.yml |
Adds command failed event observation and a new unified spec test covering transient retry exhaustion surfacing as a timeout. |
test/spec/client-side-operations-timeout/convenient-transactions.json |
JSON equivalent updates for the unified spec additions/changes. |
test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts |
Adds integration prose tests that validate legacy 120s retry timeout behavior using a stubbed clock. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sessions.ts
Outdated
| const hasTimedOut = | ||
| !this.timeoutContext?.csotEnabled() && processTimeMS() - startTime >= MAX_TIMEOUT; | ||
|
|
||
| if (!hasTimedOut) { | ||
| /* | ||
| * Note: a maxTimeMS error will have the MaxTimeMSExpired | ||
| * code (50) and can be reported as a top-level error or | ||
| * inside writeConcernError, ex. | ||
| * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' } | ||
| * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } } | ||
| */ | ||
| if ( | ||
| !isMaxTimeMSExpiredError(commitError) && | ||
| commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) | ||
| ) { | ||
| // 10.i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not | ||
| // MaxTimeMSExpired and the elapsed time of `withTransaction` is less than 120 seconds, jump back to step eight. | ||
| continue retryCommit; | ||
| } | ||
|
|
||
| if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { | ||
| // 10.ii If the commitTransaction error includes a "TransientTransactionError" label | ||
| // and the elapsed time of withTransaction is less than 120 seconds, jump back to step two. | ||
| lastError = commitError; | ||
|
|
||
| continue retryTransaction; | ||
| } | ||
| (this.timeoutContext?.csotEnabled() && this.timeoutContext.remainingTimeMS <= 0) || | ||
| (!this.timeoutContext?.csotEnabled() && processTimeMS() - startTime >= MAX_TIMEOUT); | ||
|
|
||
| if (hasTimedOut) { | ||
| throw makeWithTransactionTimeoutError(commitError); | ||
| } |
| mode: alwaysOn | ||
| data: | ||
| failCommands: ["insert"] | ||
| blockConnection: true | ||
| blockTimeMS: 25 | ||
| errorCode: 24 | ||
| errorLabels: ["TransientTransactionError"] | ||
|
|
| - commandStartedEvent: | ||
| commandName: abortTransaction | ||
| - commandFailedEvent: | ||
| commandName: abortTransaction |
| "mode": "alwaysOn", | ||
| "data": { | ||
| "failCommands": [ | ||
| "insert" |
| { | ||
| "commandStartedEvent": { | ||
| "commandName": "abortTransaction" | ||
| } | ||
| }, | ||
| { | ||
| "commandFailedEvent": { | ||
| "commandName": "abortTransaction" | ||
| } |
Description
Summary of Changes
Align
withTransactiontimeout behavior with the spec change from DRIVERS-3391: when retry attempts exhaust the timeout (CSOTtimeoutMSor legacy 120s), throw aMongoOperationTimeoutErrorwrapping the last transient error ascause, instead of throwing the raw transient error directly.Notes for Reviewers
The
makeWithTransactionTimeoutErrorhelper mirrors the spec'smakeTimeoutErrorpseudocode function. It creates aMongoOperationTimeoutErrorand copies error labels from the cause, so callers can checkhasErrorLabel('TransientTransactionError')on the timeout error itself.The 120s cap fix is a one-line change (adding
!csotEnabled()guard to the legacy branch ofwillExceedTransactionDeadline), but it's included here since it's in the same code path.What is the motivation for this change?
NODE-7430 / DRIVERS-3391
Release Highlight
Release notes highlight
Double check the following
npm run check:lint)type(NODE-xxxx)[!]: descriptionfeat(NODE-1234)!: rewriting everything in coffeescript