Skip to content

PYTHON-5668 - Merge backpressure branch into mainline#2729

Open
NoahStapp wants to merge 327 commits intomasterfrom
backpressure
Open

PYTHON-5668 - Merge backpressure branch into mainline#2729
NoahStapp wants to merge 327 commits intomasterfrom
backpressure

Conversation

@NoahStapp
Copy link
Contributor

@NoahStapp NoahStapp commented Mar 12, 2026

PYTHON-5668

Changes in this PR

Merge backpressure branch into master.

Test Plan

Checklist

Checklist for Author

  • Did you update the changelog (if necessary)?
  • Is there test coverage?
  • Is any followup work tracked in a JIRA ticket? If so, add link(s).

Checklist for Reviewer

  • Does the title of the PR reference a JIRA Ticket?
  • Do you fully understand the implementation? (Would you be comfortable explaining how this code works to someone else?)
  • Is all relevant documentation (README or docstring) updated?

blink1073 and others added 17 commits November 19, 2025 15:31
PYTHON-5505 Prototype system overload retry loop for all operations (#2497)

All commands that fail with the "Retryable" error label will be retried up to 3 times.
When the error includes the "SystemOverloaded" error label we apply exponential
backoff with jitter before attempting a retry.

PYTHON-5506 Prototype adaptive token bucket retry (#2501)

Add adaptive token bucket based retry policy.
Successfully completed commands deposit 0.1 token.
Failed retry attempts consume 1 token.
A retry is only permitted if there is an available token.
Token bucket starts full with the maximum 1000 tokens.

PYTHON-5505 Use proper RetryableError and SystemOverloadedError labels
…tion rate limiter triggers (#2509)

Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com>
Co-authored-by: Noah Stapp <noah.stapp@mongodb.com>
Co-authored-by: Shane Harvey <shnhrv@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

PYTHON-5629 Increase max overload retries from 3 to 5 and initial delay from 50ms to 100ms (#2599)

PYTHON-5517 Simplify pool backpressure behavior (#2611)

synchro

update network_layer

update pool shared

update pool shared

update run-tests
Co-authored-by: Shane Harvey <shnhrv@gmail.com>
Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
Co-authored-by: Noah Stapp <noah.stapp@mongodb.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…loop for server overloaded errors (#2635)

Co-authored-by: Kevin Albertson <kevin.albertson@mongodb.com>
Co-authored-by: Casey Clements <caseyclements@users.noreply.github.com>
Co-authored-by: Sergey Zelenov <mail@zelenov.su>
@NoahStapp NoahStapp marked this pull request as ready for review March 12, 2026 19:41
@NoahStapp NoahStapp requested a review from a team as a code owner March 12, 2026 19:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Merges the backpressure work into mainline by introducing adaptive retry behavior (token bucket + exponential backoff) for server overload conditions, updating handshake metadata, and adding/adjusting spec + integration tests for these behaviors.

Changes:

  • Add adaptive retry policy (token bucket + backoff) and apply it to retry loops when SystemOverloadedError is encountered.
  • Include backpressure: true in handshake commands and adjust SDAM/pool behavior to avoid clearing pools / changing server descriptions on overload-labeled connection failures.
  • Add new unified/spec tests and prose/integration tests covering backpressure retries across reads, writes, transactions, and getMore.

Reviewed changes

Copilot reviewed 50 out of 52 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tools/synchro.py Ensures new backpressure test file is included in synchro conversions; improves async sleep translation regex.
test/uri_options/client-backpressure-options.json Adds URI parsing coverage for adaptiveRetries.
test/transactions/unified/backpressure-retryable-writes.json Adds unified transaction tests validating overload-triggered retry behavior for writes.
test/transactions/unified/backpressure-retryable-reads.json Adds unified transaction tests validating overload-triggered retry behavior for reads.
test/transactions/unified/backpressure-retryable-commit.json Adds unified transaction tests validating overload-triggered retry behavior for commit.
test/transactions/unified/backpressure-retryable-abort.json Adds unified transaction tests validating overload-triggered retry behavior for abort.
test/test_transactions.py Updates/extends transaction convenient API tests for timeout/backoff-related behavior.
test/test_retryable_writes.py Adds tests for error propagation across multiple retryable write errors / labels.
test/test_pooling.py Adds integration test asserting pool backpressure does not disrupt existing connections.
test/test_discovery_and_monitoring.py Adds pool backpressure test asserting checkout failures occur without pool-cleared events; adjusts overload labeling in a specific handshake failure path.
test/test_client_metadata.py Asserts handshake documents include backpressure: true.
test/test_client_backpressure.py Adds backpressure prose + unified-format tests for retries/backoff/token-bucket behavior.
test/test_client.py Adds basic client option parsing tests for adaptive_retries / adaptiveRetries.
test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json Adds unified SDAM test around minPoolSize population error behavior under backpressure scenarios.
test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json Adds unified SDAM test applying backpressure semantics on connection-establishment network timeouts (single).
test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json Adds unified SDAM test applying backpressure semantics on connection-establishment network timeouts (replicaset).
test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json Adds unified SDAM test applying backpressure semantics on connection-establishment network errors (single).
test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json Adds unified SDAM test applying backpressure semantics on connection-establishment network errors (replicaset).
test/discovery_and_monitoring/errors/error_handling_handshake.json Updates expected SDAM outcome for handshake error handling (topology/server type/generation changes).
test/connection_monitoring/pool-create-min-size-error.json Adjusts failpoint mode to alwaysOn for minPoolSize population error test.
test/client-backpressure/getMore-retried.json Adds unified-format tests ensuring getMore retries under overload errors.
test/client-backpressure/backpressure-connection-checkin.json Adds unified-format test ensuring connections are checked back in across overload retry attempts.
test/asynchronous/test_transactions.py Async equivalents of updated/extended convenient transaction API timeout/backoff tests.
test/asynchronous/test_retryable_writes.py Async equivalents for multi-error propagation retryable writes tests.
test/asynchronous/test_pooling.py Async equivalent of pool backpressure “preserve existing connections” test.
test/asynchronous/test_discovery_and_monitoring.py Async equivalent of pool backpressure test and overload label adjustment.
test/asynchronous/test_client_metadata.py Async equivalent of handshake backpressure: true assertion.
test/asynchronous/test_client_backpressure.py Async equivalents of backpressure prose + unified-format tests.
test/asynchronous/test_client.py Async equivalent of adaptive_retries option parsing test.
pymongo/synchronous/topology.py Avoids SDAM server description/pool reset behavior for overload-labeled connection failures.
pymongo/synchronous/pool.py Adds handshake backpressure: true and synthesizes overload labels for certain connection establishment failures.
pymongo/synchronous/mongo_client.py Introduces retry policy usage in retry loop; extends retry internals with runCommand/aggregate-write flags; updates command/drop paths to reuse retry machinery.
pymongo/synchronous/helpers.py Adds token bucket + retry policy helpers for adaptive retries and backoff calculations.
pymongo/synchronous/database.py Routes Database.command and cursor commands through retryable read machinery (incl. runCommand flag).
pymongo/synchronous/collection.py Routes various write helper methods through retryable write machinery; marks aggregate-writes for retry gating.
pymongo/synchronous/client_session.py Adds transaction retry backoff and CSOT-aware timeout error conversion in convenient API.
pymongo/synchronous/client_bulk.py Treats overload-labeled errors as retryable in client bulk write execution.
pymongo/monitoring.py Adds a new connection closed reason for “pool backoff”.
pymongo/logger.py Adds logging message/reason mapping for pool backoff.
pymongo/common.py Introduces ADAPTIVE_RETRIES default and registers adaptiveRetries / adaptive_retries options.
pymongo/client_options.py Plumbs adaptive_retries option into ClientOptions with a new property.
pymongo/asynchronous/topology.py Async equivalent of overload-labeled connection failure SDAM behavior change.
pymongo/asynchronous/pool.py Async equivalent of handshake backpressure: true and overload label synthesis in connection establishment.
pymongo/asynchronous/mongo_client.py Async equivalent of retry policy integration and runCommand/aggregate-write gating.
pymongo/asynchronous/helpers.py Async equivalent of token bucket + retry policy helpers.
pymongo/asynchronous/database.py Async equivalent of routing Database.command/cursor commands through retryable read machinery.
pymongo/asynchronous/collection.py Async equivalent of routing various write helper methods through retryable write machinery; aggregate-write marking.
pymongo/asynchronous/client_session.py Async equivalent of convenient transaction API backoff + timeout conversion.
pymongo/asynchronous/client_bulk.py Async equivalent of treating overload-labeled errors as retryable in client bulk write execution.
.evergreen/resync-specs.sh Adds spec resync support for the client-backpressure spec tests.

Comment on lines +530 to +541
# Mock a session establishment overload.
mock_connection_fail = {
"configureFailPoint": "failCommand",
"mode": {"times": 1},
"data": {
"closeConnection": True,
},
}

async with self.fail_point(mock_connection_fail):
await coll.find_one({})

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This failCommand configuration only sets closeConnection: True but omits failCommands (and does not scope by appName), which is inconsistent with other failCommand usages and may make configureFailPoint fail or apply too broadly. Include an explicit failCommands list (e.g., find) and scope it to the client via appName so the test is deterministic.

Copilot uses AI. Check for mistakes.
def adaptive_retries(self) -> bool:
"""The configured adaptiveRetries option.

.. versionadded:: 4.XX
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The adaptive_retries option docstring uses a placeholder version (".. versionadded:: 4.XX"). This should be replaced with the actual version number used elsewhere in the PR (e.g., the MongoClient docstring mentions 4.17) to avoid publishing incomplete docs.

Suggested change
.. versionadded:: 4.XX
.. versionadded:: 4.17

Copilot uses AI. Check for mistakes.
Comment on lines +2836 to +2839
if self._is_run_command and not (
self._client.options.retry_reads and self._client.options.retry_writes
):
raise
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The overload-retry gating for is_run_command currently requires both retry_reads and retry_writes to be enabled. Since Database.command() is executed through the read retry path, disabling retryWrites would unexpectedly disable overload/backpressure retries for read-only commands issued via Database.command(). Consider gating based on the operation type (read vs write), e.g. require only retry_reads here for read-path runCommand operations.

Suggested change
if self._is_run_command and not (
self._client.options.retry_reads and self._client.options.retry_writes
):
raise
if self._is_run_command:
if self._is_read and not self._client.options.retry_reads:
raise
if not self._is_read and not self._client.options.retry_writes:
raise

Copilot uses AI. Check for mistakes.
Comment on lines +2846 to +2849
if self._is_run_command and not (
self._client.options.retry_reads and self._client.options.retry_writes
):
raise
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The overload-retry gating for is_run_command currently requires both retry_reads and retry_writes to be enabled. Since Database.command() is executed through the read retry path, disabling retryWrites would unexpectedly disable overload/backpressure retries for read-only commands issued via Database.command(). Consider gating based on the operation type (read vs write), e.g. require only retry_reads here for read-path runCommand operations.

Suggested change
if self._is_run_command and not (
self._client.options.retry_reads and self._client.options.retry_writes
):
raise
if self._is_run_command:
if self._is_read and not self._client.options.retry_reads:
raise
if not self._is_read and not self._client.options.retry_writes:
raise

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +113
@client_context.require_failCommand_appName
def test_retry_overload_error_insert_one(self):
self.db.t.insert_one({"x": 1})

# Ensure command is retried on overload error.
fail_many = mock_overload_error.copy()
fail_many["mode"] = {"times": _MAX_RETRIES}
with self.fail_point(fail_many):
self.db.t.find_one()

# Ensure command stops retrying after _MAX_RETRIES.
fail_too_many = mock_overload_error.copy()
fail_too_many["mode"] = {"times": _MAX_RETRIES + 1}
with self.fail_point(fail_too_many):
with self.assertRaises(PyMongoError) as error:
self.db.t.find_one()

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

test_retry_overload_error_insert_one is supposed to exercise insert-one retry behavior, but it currently calls find_one() for both the success and failure paths. This makes the test duplicate test_retry_overload_error_find and leaves insert_one overload retry behavior untested. Update the test to run insert_one(...) under the fail point (and in the MAX_RETRIES+1 case) so it actually validates insert retries.

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +112
await self.db.t.find_one()

# Ensure command stops retrying after _MAX_RETRIES.
fail_too_many = mock_overload_error.copy()
fail_too_many["mode"] = {"times": _MAX_RETRIES + 1}
async with self.fail_point(fail_too_many):
with self.assertRaises(PyMongoError) as error:
await self.db.t.find_one()
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

test_retry_overload_error_insert_one is intended to validate retries for insert_one, but it currently calls find_one() in both the success and failure paths, duplicating the previous find test and not covering insert retries. Update the test to call insert_one(...) under the fail point (including the _MAX_RETRIES + 1 case).

Suggested change
await self.db.t.find_one()
# Ensure command stops retrying after _MAX_RETRIES.
fail_too_many = mock_overload_error.copy()
fail_too_many["mode"] = {"times": _MAX_RETRIES + 1}
async with self.fail_point(fail_too_many):
with self.assertRaises(PyMongoError) as error:
await self.db.t.find_one()
await self.db.t.insert_one({"x": 2})
# Ensure command stops retrying after _MAX_RETRIES.
fail_too_many = mock_overload_error.copy()
fail_too_many["mode"] = {"times": _MAX_RETRIES + 1}
async with self.fail_point(fail_too_many):
with self.assertRaises(PyMongoError) as error:
await self.db.t.insert_one({"x": 3})

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a legitmate issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, good job 🤖 !

Comment on lines +528 to +538
# Mock a session establishment overload.
mock_connection_fail = {
"configureFailPoint": "failCommand",
"mode": {"times": 1},
"data": {
"closeConnection": True,
},
}

with self.fail_point(mock_connection_fail):
coll.find_one({})
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The failCommand configuration used here only sets closeConnection: True but does not specify failCommands (and does not scope by appName), which is inconsistent with other failCommand usages in this repo and may cause the configureFailPoint command to fail or apply too broadly. Include an explicit failCommands list (e.g., find) and scope it to the client via appName to keep the test deterministic.

Copilot uses AI. Check for mistakes.
@codecov-commenter
Copy link

codecov-commenter commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 92.46753% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.68%. Comparing base (80c3ff2) to head (a7fc68f).

Files with missing lines Patch % Lines
pymongo/asynchronous/collection.py 66.66% 6 Missing ⚠️
pymongo/synchronous/collection.py 66.66% 6 Missing ⚠️
pymongo/asynchronous/client_session.py 91.66% 2 Missing and 1 partial ⚠️
pymongo/asynchronous/mongo_client.py 94.33% 1 Missing and 2 partials ⚠️
pymongo/asynchronous/pool.py 85.71% 2 Missing and 1 partial ⚠️
pymongo/synchronous/client_session.py 91.42% 2 Missing and 1 partial ⚠️
pymongo/synchronous/mongo_client.py 94.33% 1 Missing and 2 partials ⚠️
pymongo/synchronous/pool.py 90.47% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2729      +/-   ##
==========================================
+ Coverage   87.54%   87.68%   +0.13%     
==========================================
  Files         141      141              
  Lines       24152    24455     +303     
  Branches     4126     4178      +52     
==========================================
+ Hits        21145    21443     +298     
- Misses       2116     2120       +4     
- Partials      891      892       +1     
Flag Coverage Δ
auth-aws-rhel8-test-auth-aws-rapid-web-identity-python3.14-cov 35.04% <27.79%> (-0.04%) ⬇️
auth-aws-win64-test-auth-aws-rapid-web-identity-python3.14-cov ?
auth-enterprise-macos-test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov 43.80% <37.92%> (+<0.01%) ⬆️
auth-enterprise-rhel8-test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov 43.80% <37.92%> (+0.04%) ⬆️
auth-enterprise-win64-test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov 43.85% <37.92%> (+0.11%) ⬆️
auth-oidc-local-ubuntu-22-test-auth-oidc-default 48.64% <42.33%> (-0.07%) ⬇️
compression-snappy-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.12% <44.67%> (-0.08%) ⬇️
compression-snappy-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.43% <61.55%> (+4.30%) ⬆️
compression-snappy-rhel8-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.55% <53.24%> (+0.01%) ⬆️
compression-snappy-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.78% <44.67%> (-0.07%) ⬇️
compression-zlib-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.13% <44.67%> (-0.08%) ⬇️
compression-zlib-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.43% <61.55%> (+4.30%) ⬆️
compression-zlib-rhel8-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.55% <53.24%> (+0.01%) ⬆️
compression-zlib-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.78% <44.67%> (-0.08%) ⬇️
compression-zstd-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.13% <44.67%> (-0.08%) ⬇️
compression-zstd-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.43% <61.55%> (+4.30%) ⬆️
compression-zstd-rhel8-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.55% <53.24%> (+0.01%) ⬆️
compression-zstd-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.77% <44.67%> (-0.08%) ⬇️
compression-zstd-ubuntu-22-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.76% <44.67%> (-0.07%) ⬇️
coverage-report-coverage-report 87.51% <92.46%> (+5.59%) ⬆️
disable-test-commands-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.13% <44.67%> (-0.08%) ⬇️
disable-test-commands-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.43% <61.55%> (+4.30%) ⬆️
disable-test-commands-rhel8-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.55% <53.24%> (+0.01%) ⬆️
disable-test-commands-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.78% <44.67%> (-0.07%) ⬇️
encryption-crypt_shared-macos-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.76% <43.89%> (-0.05%) ⬇️
encryption-crypt_shared-macos-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.61% <45.45%> (-0.12%) ⬇️
encryption-crypt_shared-macos-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.44% <45.45%> (-0.09%) ⬇️
encryption-crypt_shared-rhel8-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.74% <43.89%> (-0.07%) ⬇️
encryption-crypt_shared-rhel8-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.50% <44.41%> (-0.11%) ⬇️
encryption-crypt_shared-rhel8-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.41% <45.45%> (-0.12%) ⬇️
encryption-crypt_shared-win64-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.62% <43.89%> (-0.08%) ⬇️
encryption-crypt_shared-win64-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.61% <44.93%> (-0.18%) ⬇️
encryption-crypt_shared-win64-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.50% <45.45%> (-0.09%) ⬇️
encryption-macos-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.74% <43.89%> (-0.07%) ⬇️
encryption-macos-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.60% <45.45%> (-0.10%) ⬇️
encryption-macos-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.45% <45.45%> (-0.08%) ⬇️
encryption-pyopenssl-rhel8-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 53.42% <43.89%> (-0.07%) ⬇️
encryption-pyopenssl-rhel8-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 55.18% <44.41%> (-0.10%) ⬇️
encryption-pyopenssl-rhel8-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 55.10% <45.45%> (-0.12%) ⬇️
encryption-rhel8-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.75% <43.89%> (-0.07%) ⬇️
encryption-rhel8-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.49% <44.41%> (-0.11%) ⬇️
encryption-rhel8-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.45% <45.45%> (-0.08%) ⬇️
encryption-win64-test-non-standard-latest-python3.13-noauth-nossl-standalone-cov 52.62% <43.89%> (-0.08%) ⬇️
encryption-win64-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 54.60% <44.93%> (-0.04%) ⬇️
encryption-win64-test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov 54.48% <45.45%> (-0.14%) ⬇️
load-balancer-test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov 48.43% <54.80%> (+0.11%) ⬆️
mongodb-latest-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 56.96% <53.24%> (+0.02%) ⬆️
mongodb-latest-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 55.16% <44.67%> (-0.08%) ⬇️
mongodb-latest-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 59.12% <53.50%> (+<0.01%) ⬆️
mongodb-latest-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 59.25% <55.58%> (-0.02%) ⬇️
mongodb-latest-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 61.33% <61.55%> (+4.27%) ⬆️
mongodb-rapid-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 56.96% <53.24%> (+0.01%) ⬆️
mongodb-rapid-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 55.14% <44.67%> (-0.09%) ⬇️
mongodb-rapid-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 59.12% <53.50%> (+<0.01%) ⬆️
mongodb-rapid-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 59.23% <54.54%> (-0.03%) ⬇️
mongodb-rapid-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 61.33% <61.55%> (+4.26%) ⬆️
mongodb-v4.2-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 54.55% <45.71%> (-0.09%) ⬇️
mongodb-v4.2-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 53.05% <40.00%> (-0.14%) ⬇️
mongodb-v4.2-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 56.71% <45.71%> (-0.11%) ⬇️
mongodb-v4.2-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 56.83% <45.97%> (-0.11%) ⬇️
mongodb-v4.2-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 54.71% <45.97%> (-0.08%) ⬇️
mongodb-v4.4-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 55.05% <52.20%> (+0.01%) ⬆️
mongodb-v4.4-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 53.38% <43.89%> (-0.09%) ⬇️
mongodb-v4.4-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 57.19% <52.20%> (-0.03%) ⬇️
mongodb-v4.4-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 57.29% <52.46%> (-0.02%) ⬇️
mongodb-v4.4-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 55.10% <52.46%> (+0.01%) ⬆️
mongodb-v5.0-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 55.24% <52.20%> (+0.02%) ⬆️
mongodb-v5.0-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 53.55% <43.89%> (-0.09%) ⬇️
mongodb-v5.0-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 57.39% <52.20%> (-0.03%) ⬇️
mongodb-v5.0-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 57.52% <52.46%> (-0.02%) ⬇️
mongodb-v5.0-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 55.34% <52.46%> (+0.01%) ⬆️
mongodb-v6.0-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 55.28% <53.24%> (+0.04%) ⬆️
mongodb-v6.0-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 53.55% <43.89%> (-0.08%) ⬇️
mongodb-v6.0-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 57.41% <52.20%> (-0.03%) ⬇️
mongodb-v6.0-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 57.57% <53.50%> (-0.02%) ⬇️
mongodb-v6.0-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 59.68% <60.51%> (+4.31%) ⬆️
mongodb-v7.0-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 55.28% <52.20%> (+0.01%) ⬆️
mongodb-v7.0-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 53.54% <43.89%> (-0.09%) ⬇️
mongodb-v7.0-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 57.46% <52.46%> (+0.02%) ⬆️
mongodb-v7.0-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 57.57% <53.50%> (-0.02%) ⬇️
mongodb-v7.0-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 59.64% <60.51%> (+4.28%) ⬆️
mongodb-v8.0-test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov 56.95% <53.24%> (+0.02%) ⬆️
mongodb-v8.0-test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov 55.15% <44.67%> (-0.08%) ⬇️
mongodb-v8.0-test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov 59.12% <53.50%> (+<0.01%) ⬆️
mongodb-v8.0-test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov 59.23% <54.54%> (-0.03%) ⬇️
mongodb-v8.0-test-server-version-python3.11-async-noauth-nossl-replica-set-cov 61.33% <61.55%> (+4.27%) ⬆️
no-c-ext-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 56.32% <44.67%> (-0.10%) ⬇️
no-c-ext-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 62.63% <61.55%> (+4.29%) ⬆️
no-c-ext-rhel8-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 57.76% <53.24%> (+<0.01%) ⬆️
no-c-ext-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 55.98% <44.67%> (-0.09%) ⬇️
ocsp-rhel8-test-ocsp-ecdsa-valid-cert-server-staples-latest-python3.14-cov 34.24% <26.23%> (?)
ocsp-rhel8-test-ocsp-rsa-valid-cert-server-staples-latest-python3.14-cov 34.24% <26.23%> (?)
pyopenssl-macos-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.68% <61.55%> (+4.64%) ⬆️
pyopenssl-rhel8-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.68% <61.55%> (+4.64%) ⬆️
pyopenssl-win64-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.62% <61.55%> (+4.65%) ⬆️
stable-api-accept-v2-rhel8-auth-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.12% <44.67%> (-0.09%) ⬇️
stable-api-accept-v2-rhel8-auth-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.78% <44.67%> (-0.07%) ⬇️
stable-api-require-v1-rhel8-auth-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.11% <44.67%> (-0.08%) ⬇️
stable-api-require-v1-rhel8-auth-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.43% <53.24%> (+0.02%) ⬆️
stable-api-require-v1-rhel8-auth-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.77% <44.67%> (-0.07%) ⬇️
storage-inmemory-rhel8-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.12% <44.67%> (-0.08%) ⬇️
storage-inmemory-rhel8-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.78% <44.67%> (-0.07%) ⬇️
test-macos-arm64-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.10% <44.67%> (-0.10%) ⬇️
test-macos-arm64-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.41% <61.55%> (+4.30%) ⬆️
test-macos-arm64-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.54% <53.24%> (+<0.01%) ⬆️
test-macos-arm64-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.75% <44.67%> (-0.06%) ⬇️
test-macos-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 55.10% <44.67%> (-0.09%) ⬇️
test-macos-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.41% <61.55%> (+4.29%) ⬆️
test-macos-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.57% <54.28%> (+0.04%) ⬆️
test-macos-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.76% <44.67%> (-0.05%) ⬇️
test-numpy-macos-arm64-test-numpy-python3.14-python3.14-cov 32.65% <24.93%> (+0.39%) ⬆️
test-numpy-macos-test-numpy-python3.14-python3.14-cov 32.65% <24.93%> (+0.39%) ⬆️
test-numpy-rhel8-test-numpy-python3.14-python3.14-cov 32.65% <24.93%> (+0.39%) ⬆️
test-numpy-win32-test-numpy-python3.14-python3.14-cov 32.63% <24.93%> (+0.39%) ⬆️
test-numpy-win64-test-numpy-python3.14-python3.14-cov 32.63% <24.93%> (+0.41%) ⬆️
test-win32-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov 54.98% <44.67%> (-0.07%) ⬇️
test-win32-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.39% <61.55%> (+4.33%) ⬆️
test-win32-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.49% <53.24%> (+0.02%) ⬆️
test-win32-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov 54.62% <44.67%> (-0.07%) ⬇️
test-win64-test-standard-latest-python3.11-async-noauth-nossl-standalone-cov ?
test-win64-test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov 61.39% <61.55%> (+4.32%) ⬆️
test-win64-test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov 56.49% <53.24%> (+0.02%) ⬆️
test-win64-test-standard-latest-python3.14-async-noauth-nossl-standalone-cov ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@ShaneHarvey ShaneHarvey left a comment

Choose a reason for hiding this comment

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

@NoahStapp anything you'd like to call out in the merge?

and "errorLabels" in error.details
and isinstance(error.details["errorLabels"], list)
and "RetryableError" in error.details["errorLabels"]
and "SystemOverloadedError" in error.details["errorLabels"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't "RetryableError" enough to say it's retryable? "SystemOverloadedError" only indicates that we backoff before the retry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch: the spec doesn't include this as a requirement, and the tests pass with the "SystemOverloadedError" check removed.

Comment on lines +105 to +112
await self.db.t.find_one()

# Ensure command stops retrying after _MAX_RETRIES.
fail_too_many = mock_overload_error.copy()
fail_too_many["mode"] = {"times": _MAX_RETRIES + 1}
async with self.fail_point(fail_too_many):
with self.assertRaises(PyMongoError) as error:
await self.db.t.find_one()
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a legitmate issue.

@NoahStapp
Copy link
Contributor Author

NoahStapp commented Mar 13, 2026

@NoahStapp anything you'd like to call out in the merge?

The main thing I'd like eyes on is a sanity check that none of the non-test changes appear obviously incorrect. I'm doing a pass today to ensure that all of the Python backpressure work is present in this PR.

@NoahStapp NoahStapp requested a review from sleepyStick March 13, 2026 14:26
# If we need to apply backpressure to the first command,
# we will need to revert back to starting state.
if self._session is not None and self._session.in_transaction:
self._session._transaction.has_completed_command = True
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a subtle behavior change here. For example:

coll.insert_one({"_id": 1})
with client.start_session() as s, s.start_transaction():
    try:
        coll.insert_one({"_id": 1}, session=s)
    except <DuplicateKeyError>:
        pass
    # An overload error on this find will incorrectly(?) cause the transaction to be restarted. 
    coll.find_one(session=s)

Is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We only restart the transaction if transaction.has_completed_command is False. The first insert_one within the transaction block should set transaction.has_completed_command to True and make the overload error find_one retry itself only. Unless you're saying there's a bug in that logic?

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't an exception being raised on the first operation cause the has_completed_command=True line to be skipped?

"""The pool was closed, making the connection no longer valid."""

POOL_BACKOFF = "poolBackoff"
"""The pool is in backoff mode."""
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be removed right?

_VERBOSE_CONNECTION_ERROR_REASONS = {
ConnectionClosedReason.POOL_CLOSED: "Connection pool was closed",
ConnectionCheckOutFailedReason.POOL_CLOSED: "Connection pool was closed",
ConnectionClosedReason.POOL_BACKOFF: "Connection pool is in backoff",
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be removed right?

"servermonitoringmode", common.SERVER_MONITORING_MODE
)
self.__adaptive_retries = (
options.get("adaptive_retries", common.ADAPTIVE_RETRIES)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we accept both underscore and camelcase here? The norm for spec uri options is to only accept camelCase, like retryWrites, serverMonitoringMode, etc...

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.

6 participants