Skip to content

CSHARP-6257: Fix WithTransaction not retrying TransientTransactionError when MongoClientSettings is created with its constructor - #2144

Open
flibustier7seas wants to merge 1 commit into
mongodb:mainfrom
flibustier7seas:fix/withtransaction-infinite-timeout
Open

flibustier7seas wants to merge 1 commit into
mongodb:mainfrom
flibustier7seas:fix/withtransaction-infinite-timeout

Conversation

@flibustier7seas

Copy link
Copy Markdown
Contributor

Summary

Fixes https://jira.mongodb.org/browse/CSHARP-6257

The MongoClientSettings constructor sets the internal Timeout to Timeout.InfiniteTimeSpan, while MongoClientSettings.FromConnectionString / FromUrl leave it null. Since CSHARP-5712 (3.7.0), TransactionExecutor.IsTimedOut compares the elapsed time with that value as if it were a finite deadline of −1 ms. As a result, WithTransaction[Async] gives up after the first TransientTransactionError, for example a write conflict. Since CSHARP-5869 (3.8.0) that error is also wrapped in TimeoutException. The same code retried on 3.5.2.

The infinite constructor default, introduced in 3.5.0 by CSHARP-3549, has a wider effect as well: a client built with the constructor runs every operation in CSOT mode even though the application never set timeoutMS, because IsRootContextTimeoutConfigured() is true for every operation.

The driver's own integration and spec tests did not catch this. They build clients through FromConnectionString / FromUrl (DriverTestConfiguration), so they always ran with Timeout == null and never exercised the constructor default.

Fix

  • In the MongoClientSettings constructor, Timeout now defaults to null (unset), the same as MongoUrlBuilder and FromUrl. A client built with the constructor now behaves as the CSOT spec requires when timeoutMS is not set.
  • TransactionExecutor.IsTimedOut now treats an infinite timeout as "no deadline", the same way OperationContext.RemainingTimeout already does. This covers an infinite timeout set explicitly, for example through TransactionOptions.

Note the behaviour change for applications that build MongoClientSettings with the constructor. Their retryable reads and writes go back to a single retry, and per-operation options such as FindOptions.MaxTime are sent as maxTimeMS again. Both are the existing behaviour that the spec requires when timeoutMS is not set.

Behaviour by version

Case Spec 3.5.2 3.7.0–3.12.0 This PR
timeoutMS not set, settings created with new MongoClientSettings() withTransaction retries for up to 120 seconds, then rethrows the last error (Convenient Transactions API) ✗ Timeout is InfiniteTimeSpan, so retries are unbounded ✗ Gives up after the first attempt with TimeoutException wrapping the error (3.7.0: rethrows the error, still after one attempt) ✓ Timeout is null: retries for up to 120 seconds, then rethrows the last error
timeoutMS not set, settings created with FromConnectionString / FromUrl Same as above ✓ Retries for up to 120 seconds, then rethrows the last error ✓ Same, with backoff between attempts ✓ Unchanged
timeoutMS finite The timeout applies to the entire withTransaction call ✓ Retries until the timeout expires, then rethrows the last error ✓ Stops when the next attempt would exceed the timeout and throws TimeoutException wrapping the last error (3.7.0: rethrows the last error) ✓ Unchanged
timeoutMS infinite, set explicitly (internal API only until CSOT GA) No deadline: "an explicit value of 0 means infinite", and TimeSpan special values keep their meaning (timeoutMS) ✓ Retries are unbounded ✗ Gives up after the first attempt with TimeoutException wrapping the error (3.7.0: rethrows the error) ✓ Retries are unbounded
Other operations, timeoutMS not set, settings created with new MongoClientSettings() "The existing timeout behavior is unchanged" (timeoutMS) ✗ CSOT mode: retryable reads and writes retry without an attempt limit, and FindOptions.MaxTime is not sent as maxTimeMS ✗ Same ✓ Existing behaviour: one retry, and FindOptions.MaxTime is sent as maxTimeMS

Testing

  • Added ClientSessionHandleTests.WithTransaction_callback_with_a_TransientTransactionError_and_infinite_timeout_should_be_retried, which covers the sync and async paths. The mocked clock moves past 120 seconds before the retry, so the test also fails if an infinite timeout falls back to the 120-second limit. It fails on main with TimeoutException.
  • MongoClientSettingsTests.TestDefaults now asserts that Timeout is null. Together with the existing _settings.Timeout.HasValue check in MongoClient.StartSession, this means a client built with the constructor no longer passes a timeout to its sessions' default transaction options.

…or when MongoClientSettings is created with its constructor
@flibustier7seas
flibustier7seas requested a review from a team as a code owner October 9, 2026 14:53
@sanych-sun
sanych-sun requested review from sanych-sun and removed request for ajcvickers October 9, 2026 16:15
@sanych-sun

Copy link
Copy Markdown
Member

Thank you for your contribution! I'll make sure the changes will be included into the next release.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants