From 7be6f1f00e33d7520f67a6a638d4a3cd604026a0 Mon Sep 17 00:00:00 2001 From: Cloud User Date: Mon, 1 Jun 2026 16:00:58 +0000 Subject: [PATCH 1/8] resyncing specs 06-01-2026 --- .../pool-checkout-custom-maxConnecting-is-enforced.json | 6 ++++-- .../pool-checkout-maxConnecting-is-enforced.json | 6 ++++-- .../pool-checkout-maxConnecting-timeout.json | 6 ++++-- ...ool-checkout-minPoolSize-connection-maxConnecting.json | 6 ++++-- .../pool-checkout-returned-connection-maxConnecting.json | 6 ++++-- .../pool-clear-interrupting-pending-connections.json | 6 ++++-- test/load_balancer/cursors.json | 8 ++++---- test/load_balancer/sdam-error-handling.json | 6 +++--- test/server_selection_logging/operation-id.json | 4 ++-- test/server_selection_logging/replica-set.json | 2 +- test/server_selection_logging/sharded.json | 2 +- test/server_selection_logging/standalone.json | 2 +- .../unified/callback-aborts.json | 8 +++++--- .../unified/callback-commits.json | 8 +++++--- test/transactions/unified/commit.json | 8 ++++++-- test/transactions/unified/retryable-writes.json | 8 ++++++-- 16 files changed, 58 insertions(+), 34 deletions(-) diff --git a/test/connection_monitoring/pool-checkout-custom-maxConnecting-is-enforced.json b/test/connection_monitoring/pool-checkout-custom-maxConnecting-is-enforced.json index 6620f82fd9..9a7c0174c5 100644 --- a/test/connection_monitoring/pool-checkout-custom-maxConnecting-is-enforced.json +++ b/test/connection_monitoring/pool-checkout-custom-maxConnecting-is-enforced.json @@ -17,13 +17,15 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 500 + "blockTimeMS": 500, + "appName": "PoolCheckoutCustomMaxConnectingIsEnforced" } }, "poolOptions": { "maxConnecting": 1, "maxPoolSize": 2, - "waitQueueTimeoutMS": 5000 + "waitQueueTimeoutMS": 5000, + "appName": "PoolCheckoutCustomMaxConnectingIsEnforced" }, "operations": [ { diff --git a/test/connection_monitoring/pool-checkout-maxConnecting-is-enforced.json b/test/connection_monitoring/pool-checkout-maxConnecting-is-enforced.json index 3a63818bfe..418ec7986f 100644 --- a/test/connection_monitoring/pool-checkout-maxConnecting-is-enforced.json +++ b/test/connection_monitoring/pool-checkout-maxConnecting-is-enforced.json @@ -19,12 +19,14 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 800 + "blockTimeMS": 800, + "appName": "PoolCheckoutMaxConnectingIsEnforced" } }, "poolOptions": { "maxPoolSize": 10, - "waitQueueTimeoutMS": 5000 + "waitQueueTimeoutMS": 5000, + "appName": "PoolCheckoutMaxConnectingIsEnforced" }, "operations": [ { diff --git a/test/connection_monitoring/pool-checkout-maxConnecting-timeout.json b/test/connection_monitoring/pool-checkout-maxConnecting-timeout.json index 4d9fda1a68..443104675d 100644 --- a/test/connection_monitoring/pool-checkout-maxConnecting-timeout.json +++ b/test/connection_monitoring/pool-checkout-maxConnecting-timeout.json @@ -19,12 +19,14 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 750 + "blockTimeMS": 750, + "appName": "PoolCheckoutMaxConnectingTimeout" } }, "poolOptions": { "maxPoolSize": 10, - "waitQueueTimeoutMS": 50 + "waitQueueTimeoutMS": 50, + "appName": "PoolCheckoutMaxConnectingTimeout" }, "operations": [ { diff --git a/test/connection_monitoring/pool-checkout-minPoolSize-connection-maxConnecting.json b/test/connection_monitoring/pool-checkout-minPoolSize-connection-maxConnecting.json index 3b0d43e877..b53b002805 100644 --- a/test/connection_monitoring/pool-checkout-minPoolSize-connection-maxConnecting.json +++ b/test/connection_monitoring/pool-checkout-minPoolSize-connection-maxConnecting.json @@ -17,13 +17,15 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 500 + "blockTimeMS": 500, + "appName": "PoolCheckoutMinPoolSizeConnectionMaxConnecting" } }, "poolOptions": { "minPoolSize": 2, "maxPoolSize": 3, - "waitQueueTimeoutMS": 5000 + "waitQueueTimeoutMS": 5000, + "appName": "PoolCheckoutMinPoolSizeConnectionMaxConnecting" }, "operations": [ { diff --git a/test/connection_monitoring/pool-checkout-returned-connection-maxConnecting.json b/test/connection_monitoring/pool-checkout-returned-connection-maxConnecting.json index 10b526e0c3..0bac21ae64 100644 --- a/test/connection_monitoring/pool-checkout-returned-connection-maxConnecting.json +++ b/test/connection_monitoring/pool-checkout-returned-connection-maxConnecting.json @@ -19,13 +19,15 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 750 + "blockTimeMS": 750, + "appName": "PoolCheckoutReturnedConnectionMaxConnecting" } }, "poolOptions": { "maxConnecting": 2, "maxPoolSize": 10, - "waitQueueTimeoutMS": 5000 + "waitQueueTimeoutMS": 5000, + "appName": "PoolCheckoutReturnedConnectionMaxConnecting" }, "operations": [ { diff --git a/test/connection_monitoring/pool-clear-interrupting-pending-connections.json b/test/connection_monitoring/pool-clear-interrupting-pending-connections.json index c1fd746329..a8452d8f84 100644 --- a/test/connection_monitoring/pool-clear-interrupting-pending-connections.json +++ b/test/connection_monitoring/pool-clear-interrupting-pending-connections.json @@ -17,11 +17,13 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 10000 + "blockTimeMS": 10000, + "appName": "PoolClearInterruptingPendingConnections" } }, "poolOptions": { - "minPoolSize": 0 + "minPoolSize": 0, + "appName": "PoolClearInterruptingPendingConnections" }, "operations": [ { diff --git a/test/load_balancer/cursors.json b/test/load_balancer/cursors.json index 27aaddd5b6..b11bf2c6fa 100644 --- a/test/load_balancer/cursors.json +++ b/test/load_balancer/cursors.json @@ -385,7 +385,7 @@ ] }, { - "description": "pinned connections are returned after an network error during getMore", + "description": "pinned connections are not returned after an network error during getMore", "operations": [ { "name": "failPoint", @@ -449,7 +449,7 @@ "object": "testRunner", "arguments": { "client": "client0", - "connections": 0 + "connections": 1 } }, { @@ -677,7 +677,7 @@ ] }, { - "description": "pinned connections are returned to the pool after a non-network error on getMore", + "description": "pinned connections are not returned to the pool after a non-network error on getMore", "operations": [ { "name": "failPoint", @@ -733,7 +733,7 @@ "object": "testRunner", "arguments": { "client": "client0", - "connections": 0 + "connections": 1 } }, { diff --git a/test/load_balancer/sdam-error-handling.json b/test/load_balancer/sdam-error-handling.json index 41bc90be7f..2107afe5b3 100644 --- a/test/load_balancer/sdam-error-handling.json +++ b/test/load_balancer/sdam-error-handling.json @@ -372,6 +372,9 @@ { "connectionCreatedEvent": {} }, + { + "poolClearedEvent": {} + }, { "connectionClosedEvent": { "reason": "error" @@ -381,9 +384,6 @@ "connectionCheckOutFailedEvent": { "reason": "connectionError" } - }, - { - "poolClearedEvent": {} } ] } diff --git a/test/server_selection_logging/operation-id.json b/test/server_selection_logging/operation-id.json index ccc2623166..72ebff60d8 100644 --- a/test/server_selection_logging/operation-id.json +++ b/test/server_selection_logging/operation-id.json @@ -197,7 +197,7 @@ } }, { - "level": "debug", + "level": "info", "component": "serverSelection", "data": { "message": "Waiting for suitable server to become available", @@ -383,7 +383,7 @@ } }, { - "level": "debug", + "level": "info", "component": "serverSelection", "data": { "message": "Waiting for suitable server to become available", diff --git a/test/server_selection_logging/replica-set.json b/test/server_selection_logging/replica-set.json index 830b1ea51a..5eba784bf2 100644 --- a/test/server_selection_logging/replica-set.json +++ b/test/server_selection_logging/replica-set.json @@ -184,7 +184,7 @@ } }, { - "level": "debug", + "level": "info", "component": "serverSelection", "data": { "message": "Waiting for suitable server to become available", diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/sharded.json index 346c050f9e..d42fba9100 100644 --- a/test/server_selection_logging/sharded.json +++ b/test/server_selection_logging/sharded.json @@ -193,7 +193,7 @@ } }, { - "level": "debug", + "level": "info", "component": "serverSelection", "data": { "message": "Waiting for suitable server to become available", diff --git a/test/server_selection_logging/standalone.json b/test/server_selection_logging/standalone.json index fa01ad9911..3b3eddd841 100644 --- a/test/server_selection_logging/standalone.json +++ b/test/server_selection_logging/standalone.json @@ -191,7 +191,7 @@ } }, { - "level": "debug", + "level": "info", "component": "serverSelection", "data": { "message": "Waiting for suitable server to become available", diff --git a/test/transactions-convenient-api/unified/callback-aborts.json b/test/transactions-convenient-api/unified/callback-aborts.json index 206428715c..dc8f7fb5a4 100644 --- a/test/transactions-convenient-api/unified/callback-aborts.json +++ b/test/transactions-convenient-api/unified/callback-aborts.json @@ -308,10 +308,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "autocommit": { - "$$exists": false - }, "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "autocommit": { "$$exists": false }, "startTransaction": { diff --git a/test/transactions-convenient-api/unified/callback-commits.json b/test/transactions-convenient-api/unified/callback-commits.json index 06f791e9ae..edda386418 100644 --- a/test/transactions-convenient-api/unified/callback-commits.json +++ b/test/transactions-convenient-api/unified/callback-commits.json @@ -381,10 +381,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "autocommit": { - "$$exists": false - }, "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "autocommit": { "$$exists": false }, "startTransaction": { diff --git a/test/transactions/unified/commit.json b/test/transactions/unified/commit.json index ab778d8df2..f033906940 100644 --- a/test/transactions/unified/commit.json +++ b/test/transactions/unified/commit.json @@ -1040,7 +1040,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session1" @@ -1196,7 +1198,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session1" diff --git a/test/transactions/unified/retryable-writes.json b/test/transactions/unified/retryable-writes.json index c196e68622..21ef22b8ab 100644 --- a/test/transactions/unified/retryable-writes.json +++ b/test/transactions/unified/retryable-writes.json @@ -217,7 +217,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session0" @@ -306,7 +308,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session0" From 5523c450b5525e998d5c46f891acad828acd9f0f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 05:42:08 -0500 Subject: [PATCH 2/8] try removing patch --- .../spec-patch/permanent/PYTHON-4201.patch | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 .evergreen/spec-patch/permanent/PYTHON-4201.patch diff --git a/.evergreen/spec-patch/permanent/PYTHON-4201.patch b/.evergreen/spec-patch/permanent/PYTHON-4201.patch deleted file mode 100644 index c0b67f202d..0000000000 --- a/.evergreen/spec-patch/permanent/PYTHON-4201.patch +++ /dev/null @@ -1,62 +0,0 @@ -PYMONGO BEHAVES DIFFERENTLY INTENTIONALLY -- WE NEED THIS PATCH -diff --git a/test/server_selection_logging/replica-set.json b/test/server_selection_logging/replica-set.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/replica-set.json -+++ b/test/server_selection_logging/replica-set.json -@@ -184,7 +184,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/standalone.json b/test/server_selection_logging/standalone.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/standalone.json -+++ b/test/server_selection_logging/standalone.json -@@ -191,7 +191,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/sharded.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/sharded.json -+++ b/test/server_selection_logging/sharded.json -@@ -193,7 +193,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/operation-id.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/operation-id.json -+++ b/test/server_selection_logging/operation-id.json -@@ -197,7 +197,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -@@ -383,7 +383,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", From 282a9ce063fedf4af62ad2567ebee517271d7d29 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 07:44:03 -0500 Subject: [PATCH 3/8] Log server selection WAITING message at INFO level The server selection spec was updated to expect the "Waiting for suitable server to become available" log message at INFO level. PyMongo was still emitting it at DEBUG, causing test_server_selection_logging failures. Removes the need for the now-deleted PYTHON-4201.patch. --- pymongo/asynchronous/topology.py | 3 ++- pymongo/synchronous/topology.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index 01e346bfa8..8ccc4b38b9 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -54,6 +54,7 @@ _SDAM_LOGGER, _SERVER_SELECTION_LOGGER, _debug_log, + _info_log, _SDAMStatusMessage, _ServerSelectionStatusMessage, ) @@ -361,7 +362,7 @@ async def _select_servers_loop( ) if not logged_waiting: - _debug_log( + _info_log( _SERVER_SELECTION_LOGGER, message=_ServerSelectionStatusMessage.WAITING, selector=selector, diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index ec1615f0c6..fe9d993317 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -50,6 +50,7 @@ _SDAM_LOGGER, _SERVER_SELECTION_LOGGER, _debug_log, + _info_log, _SDAMStatusMessage, _ServerSelectionStatusMessage, ) @@ -361,7 +362,7 @@ def _select_servers_loop( ) if not logged_waiting: - _debug_log( + _info_log( _SERVER_SELECTION_LOGGER, message=_ServerSelectionStatusMessage.WAITING, selector=selector, From 47cd71c1da70ae22c7ee7319b1d1de08be13911e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 12:08:07 -0500 Subject: [PATCH 4/8] Implement load balancer connection pinning and event ordering fixes Two spec changes from the 2026-06-01 resync: 1. cursors.json: pinned connections must remain checked out after getMore errors in load balancer mode. Don't call close() on OperationFailure or ConnectionFailure when _sock_mgr is set; just mark _killed=True and let the application release the connection via cursor.close(). 2. sdam-error-handling.json: poolClearedEvent must precede connectionClosedEvent when auth fails. Call handler.handle() before close_conn() in Pool.connect() so SDAM error handling (and the resulting pool reset) fires before the connection is closed. --- pymongo/asynchronous/command_cursor.py | 9 +++++++-- pymongo/asynchronous/pool.py | 6 ++++++ pymongo/synchronous/command_cursor.py | 9 +++++++-- pymongo/synchronous/pool.py | 6 ++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pymongo/asynchronous/command_cursor.py b/pymongo/asynchronous/command_cursor.py index 5a59c67a15..52e3465935 100644 --- a/pymongo/asynchronous/command_cursor.py +++ b/pymongo/asynchronous/command_cursor.py @@ -172,6 +172,10 @@ async def _send_message(self, operation: _GetMore) -> None: self._killed = True if exc.timeout: self._die_no_lock() + elif self._sock_mgr: + # In load balancer mode the pinned connection must stay checked + # out until the cursor is explicitly closed by the application. + self._killed = True else: # Return the session and pinned connection, if necessary. await self.close() @@ -179,8 +183,9 @@ async def _send_message(self, operation: _GetMore) -> None: except ConnectionFailure: # Don't send killCursors because the cursor is already closed. self._killed = True - # Return the session and pinned connection, if necessary. - await self.close() + if not self._sock_mgr: + # Return the session and pinned connection, if necessary. + await self.close() raise except Exception: await self.close() diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index a5d5b28990..3f256bc7db 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -1086,6 +1086,12 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A self.active_contexts.discard(conn.cancel_context) if not completed_hello: self._handle_connection_error(e) + if completed_hello and handler: + # Hello succeeded so service_id is known. Run SDAM error + # handling (which clears the pool) before closing the + # connection so that poolClearedEvent precedes + # connectionClosedEvent as required by the CMAP spec. + await handler.handle(type(e), e) await conn.close_conn(ConnectionClosedReason.ERROR) raise diff --git a/pymongo/synchronous/command_cursor.py b/pymongo/synchronous/command_cursor.py index 34f60c6540..3295ca9d43 100644 --- a/pymongo/synchronous/command_cursor.py +++ b/pymongo/synchronous/command_cursor.py @@ -172,6 +172,10 @@ def _send_message(self, operation: _GetMore) -> None: self._killed = True if exc.timeout: self._die_no_lock() + elif self._sock_mgr: + # In load balancer mode the pinned connection must stay checked + # out until the cursor is explicitly closed by the application. + self._killed = True else: # Return the session and pinned connection, if necessary. self.close() @@ -179,8 +183,9 @@ def _send_message(self, operation: _GetMore) -> None: except ConnectionFailure: # Don't send killCursors because the cursor is already closed. self._killed = True - # Return the session and pinned connection, if necessary. - self.close() + if not self._sock_mgr: + # Return the session and pinned connection, if necessary. + self.close() raise except Exception: self.close() diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 25f2d08fe7..87d84524af 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -1082,6 +1082,12 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect self.active_contexts.discard(conn.cancel_context) if not completed_hello: self._handle_connection_error(e) + if completed_hello and handler: + # Hello succeeded so service_id is known. Run SDAM error + # handling (which clears the pool) before closing the + # connection so that poolClearedEvent precedes + # connectionClosedEvent as required by the CMAP spec. + handler.handle(type(e), e) conn.close_conn(ConnectionClosedReason.ERROR) raise From 19c3c16b930f5fa34472ebfbaeae74c1637bc43d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 14:10:35 -0500 Subject: [PATCH 5/8] Fix LB cursor pinning and poolClearedEvent ordering (take 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two root-cause fixes: 1. AsyncCursor._send_message() also needed the pinned-connection guard — the test uses NonLazyCursor wrapping AsyncCursor, not AsyncCommandCursor. Apply the same _sock_mgr check as the previous commit. 2. Pool.connect(): _raise_connection_failure() was emitting connectionClosedEvent for not-ready connections before handler.handle() could clear the pool. Fix by always deferring the event in _raise_connection_failure() (use close_conn(None)) and emitting it explicitly in connect()'s except block, after pool clearing. --- pymongo/asynchronous/cursor.py | 7 ++++++- pymongo/asynchronous/pool.py | 37 +++++++++++++++++++++++++--------- pymongo/synchronous/cursor.py | 7 ++++++- pymongo/synchronous/pool.py | 37 +++++++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index a60c082ade..c10ae9aa24 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -993,6 +993,10 @@ async def _send_message(self, operation: Union[_Query, _GetMore]) -> None: self._killed = True if exc.timeout: self._die_no_lock() + elif self._sock_mgr: + # In load balancer mode the pinned connection must stay checked + # out until the cursor is explicitly closed by the application. + self._killed = True else: await self.close() # If this is a tailable cursor the error is likely @@ -1007,7 +1011,8 @@ async def _send_message(self, operation: Union[_Query, _GetMore]) -> None: raise except ConnectionFailure: self._killed = True - await self.close() + if not self._sock_mgr: + await self.close() raise # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. except BaseException: diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index 3f256bc7db..d8311195ae 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -623,12 +623,11 @@ async def _raise_connection_failure(self, error: BaseException) -> NoReturn: # KeyboardInterrupt from the start, rather than as an initial # socket.error, so we catch that, close the socket, and reraise it. # - # The connection closed event will be emitted later in checkin. - if self.ready: - reason = None - else: - reason = ConnectionClosedReason.ERROR - await self.close_conn(reason) + # The connection closed event will be emitted later in checkin (for + # ready connections) or by pool.connect()'s except block (for + # not-ready connections). Always use reason=None here so that the + # event can be deferred past any pool clearing that needs to happen. + await self.close_conn(None) # SSLError from PyOpenSSL inherits directly from Exception. if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) @@ -1088,11 +1087,31 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A self._handle_connection_error(e) if completed_hello and handler: # Hello succeeded so service_id is known. Run SDAM error - # handling (which clears the pool) before closing the - # connection so that poolClearedEvent precedes + # handling (which clears the pool) before emitting # connectionClosedEvent as required by the CMAP spec. await handler.handle(type(e), e) - await conn.close_conn(ConnectionClosedReason.ERROR) + if conn.closed: + # _raise_connection_failure already closed the TCP socket + # without emitting connectionClosedEvent (deferred). Emit it + # now so it follows any poolClearedEvent from above. + if conn.enabled_for_cmap: + assert conn.listeners is not None + conn.listeners.publish_connection_closed( + conn.address, conn.id, ConnectionClosedReason.ERROR + ) + if conn.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): + _debug_log( + _CONNECTION_LOGGER, + message=_ConnectionStatusMessage.CONN_CLOSED, + clientId=self._client_id, + serverHost=self.address[0], + serverPort=self.address[1], + driverConnectionId=conn.id, + reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), + error=ConnectionClosedReason.ERROR, + ) + else: + await conn.close_conn(ConnectionClosedReason.ERROR) raise if handler: diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index 5a721d8e06..ae2402f0f0 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -991,6 +991,10 @@ def _send_message(self, operation: Union[_Query, _GetMore]) -> None: self._killed = True if exc.timeout: self._die_no_lock() + elif self._sock_mgr: + # In load balancer mode the pinned connection must stay checked + # out until the cursor is explicitly closed by the application. + self._killed = True else: self.close() # If this is a tailable cursor the error is likely @@ -1005,7 +1009,8 @@ def _send_message(self, operation: Union[_Query, _GetMore]) -> None: raise except ConnectionFailure: self._killed = True - self.close() + if not self._sock_mgr: + self.close() raise # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. except BaseException: diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 87d84524af..96770fbcb9 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -621,12 +621,11 @@ def _raise_connection_failure(self, error: BaseException) -> NoReturn: # KeyboardInterrupt from the start, rather than as an initial # socket.error, so we catch that, close the socket, and reraise it. # - # The connection closed event will be emitted later in checkin. - if self.ready: - reason = None - else: - reason = ConnectionClosedReason.ERROR - self.close_conn(reason) + # The connection closed event will be emitted later in checkin (for + # ready connections) or by pool.connect()'s except block (for + # not-ready connections). Always use reason=None here so that the + # event can be deferred past any pool clearing that needs to happen. + self.close_conn(None) # SSLError from PyOpenSSL inherits directly from Exception. if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) @@ -1084,11 +1083,31 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect self._handle_connection_error(e) if completed_hello and handler: # Hello succeeded so service_id is known. Run SDAM error - # handling (which clears the pool) before closing the - # connection so that poolClearedEvent precedes + # handling (which clears the pool) before emitting # connectionClosedEvent as required by the CMAP spec. handler.handle(type(e), e) - conn.close_conn(ConnectionClosedReason.ERROR) + if conn.closed: + # _raise_connection_failure already closed the TCP socket + # without emitting connectionClosedEvent (deferred). Emit it + # now so it follows any poolClearedEvent from above. + if conn.enabled_for_cmap: + assert conn.listeners is not None + conn.listeners.publish_connection_closed( + conn.address, conn.id, ConnectionClosedReason.ERROR + ) + if conn.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): + _debug_log( + _CONNECTION_LOGGER, + message=_ConnectionStatusMessage.CONN_CLOSED, + clientId=self._client_id, + serverHost=self.address[0], + serverPort=self.address[1], + driverConnectionId=conn.id, + reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), + error=ConnectionClosedReason.ERROR, + ) + else: + conn.close_conn(ConnectionClosedReason.ERROR) raise if handler: From 347cc46f23472301c559e6ab07e0959c23e3c876 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 14:21:19 -0500 Subject: [PATCH 6/8] Fix: OperationFailure in LB mode must not suppress killCursors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For non-cursor-closed OperationFailures during getMore in load balancer mode, the cursor server-side is still alive. Don't set _killed=True — leave it False so cursor.close() sends killCursors via the pinned connection before releasing it. Only ConnectionFailure (where the server already closed the cursor) should set _killed=True. --- pymongo/asynchronous/command_cursor.py | 7 +++++-- pymongo/asynchronous/cursor.py | 7 +++++-- pymongo/synchronous/command_cursor.py | 7 +++++-- pymongo/synchronous/cursor.py | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pymongo/asynchronous/command_cursor.py b/pymongo/asynchronous/command_cursor.py index 52e3465935..f67c9ad3ec 100644 --- a/pymongo/asynchronous/command_cursor.py +++ b/pymongo/asynchronous/command_cursor.py @@ -174,8 +174,11 @@ async def _send_message(self, operation: _GetMore) -> None: self._die_no_lock() elif self._sock_mgr: # In load balancer mode the pinned connection must stay checked - # out until the cursor is explicitly closed by the application. - self._killed = True + # out until cursor.close() is called. _killed is already True + # for cursor-closed errors (set above); for other errors leave + # it False so cursor.close() will send killCursors via the + # pinned connection before releasing it. + pass else: # Return the session and pinned connection, if necessary. await self.close() diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index c10ae9aa24..310d66964a 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -995,8 +995,11 @@ async def _send_message(self, operation: Union[_Query, _GetMore]) -> None: self._die_no_lock() elif self._sock_mgr: # In load balancer mode the pinned connection must stay checked - # out until the cursor is explicitly closed by the application. - self._killed = True + # out until cursor.close() is called. _killed is already True + # for cursor-closed errors (set above); for other errors leave + # it False so cursor.close() will send killCursors via the + # pinned connection before releasing it. + pass else: await self.close() # If this is a tailable cursor the error is likely diff --git a/pymongo/synchronous/command_cursor.py b/pymongo/synchronous/command_cursor.py index 3295ca9d43..61b16d60c7 100644 --- a/pymongo/synchronous/command_cursor.py +++ b/pymongo/synchronous/command_cursor.py @@ -174,8 +174,11 @@ def _send_message(self, operation: _GetMore) -> None: self._die_no_lock() elif self._sock_mgr: # In load balancer mode the pinned connection must stay checked - # out until the cursor is explicitly closed by the application. - self._killed = True + # out until cursor.close() is called. _killed is already True + # for cursor-closed errors (set above); for other errors leave + # it False so cursor.close() will send killCursors via the + # pinned connection before releasing it. + pass else: # Return the session and pinned connection, if necessary. self.close() diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index ae2402f0f0..92a9d6739a 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -993,8 +993,11 @@ def _send_message(self, operation: Union[_Query, _GetMore]) -> None: self._die_no_lock() elif self._sock_mgr: # In load balancer mode the pinned connection must stay checked - # out until the cursor is explicitly closed by the application. - self._killed = True + # out until cursor.close() is called. _killed is already True + # for cursor-closed errors (set above); for other errors leave + # it False so cursor.close() will send killCursors via the + # pinned connection before releasing it. + pass else: self.close() # If this is a tailable cursor the error is likely From 687131bf8c86b7aa44fc768202a964a119a751c5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 15:15:10 -0500 Subject: [PATCH 7/8] Fix LB event ordering regression and add transactions spec patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pool fix: make the poolClearedEvent-before-connectionClosedEvent ordering conditional on load_balanced so non-LB auth failures keep their original event order (connectionClosedEvent → connectionCheckOutFailedEvent). Spec patch PYTHON-5791: retryable-writes, commit, and callback spec tests now expect readConcern.afterClusterTime in retried write commands, which PyMongo does not yet send. Add a patch to revert to the old assertions. --- .evergreen/spec-patch/PYTHON-5791.patch | 97 +++++++++++++++++++++++++ pymongo/asynchronous/pool.py | 28 ++++--- pymongo/synchronous/pool.py | 28 ++++--- 3 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 .evergreen/spec-patch/PYTHON-5791.patch diff --git a/.evergreen/spec-patch/PYTHON-5791.patch b/.evergreen/spec-patch/PYTHON-5791.patch new file mode 100644 index 0000000000..145ddadff7 --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5791.patch @@ -0,0 +1,97 @@ +PYMONGO DOES NOT YET SEND readConcern.afterClusterTime IN RETRYABLE WRITE RETRIES +diff --git a/test/transactions-convenient-api/unified/callback-aborts.json b/test/transactions-convenient-api/unified/callback-aborts.json +index dc8f7fb5..20642871 100644 +--- a/test/transactions-convenient-api/unified/callback-aborts.json ++++ b/test/transactions-convenient-api/unified/callback-aborts.json +@@ -308,14 +308,12 @@ + "lsid": { + "$$sessionLsid": "session0" + }, +- "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } +- }, + "autocommit": { + "$$exists": false + }, ++ "readConcern": { ++ "$$exists": false ++ }, + "startTransaction": { + "$$exists": false + }, +diff --git a/test/transactions-convenient-api/unified/callback-commits.json b/test/transactions-convenient-api/unified/callback-commits.json +index edda3864..06f791e9 100644 +--- a/test/transactions-convenient-api/unified/callback-commits.json ++++ b/test/transactions-convenient-api/unified/callback-commits.json +@@ -381,14 +381,12 @@ + "lsid": { + "$$sessionLsid": "session0" + }, +- "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } +- }, + "autocommit": { + "$$exists": false + }, ++ "readConcern": { ++ "$$exists": false ++ }, + "startTransaction": { + "$$exists": false + }, +diff --git a/test/transactions/unified/commit.json b/test/transactions/unified/commit.json +index f0339069..ab778d8d 100644 +--- a/test/transactions/unified/commit.json ++++ b/test/transactions/unified/commit.json +@@ -1040,9 +1040,7 @@ + ], + "ordered": true, + "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } ++ "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" +@@ -1198,9 +1196,7 @@ + ], + "ordered": true, + "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } ++ "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" +diff --git a/test/transactions/unified/retryable-writes.json b/test/transactions/unified/retryable-writes.json +index 21ef22b8..c196e686 100644 +--- a/test/transactions/unified/retryable-writes.json ++++ b/test/transactions/unified/retryable-writes.json +@@ -217,9 +217,7 @@ + ], + "ordered": true, + "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } ++ "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" +@@ -308,9 +306,7 @@ + ], + "ordered": true, + "readConcern": { +- "afterClusterTime": { +- "$$exists": true +- } ++ "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index d8311195ae..268b935e5d 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -623,11 +623,15 @@ async def _raise_connection_failure(self, error: BaseException) -> NoReturn: # KeyboardInterrupt from the start, rather than as an initial # socket.error, so we catch that, close the socket, and reraise it. # - # The connection closed event will be emitted later in checkin (for - # ready connections) or by pool.connect()'s except block (for - # not-ready connections). Always use reason=None here so that the - # event can be deferred past any pool clearing that needs to happen. - await self.close_conn(None) + # For ready connections the closed event is emitted later in checkin. + # For not-ready LB connections, defer the event so that pool.connect()'s + # except block can emit poolClearedEvent first (CMAP spec requirement). + # For not-ready non-LB connections, emit the event immediately as before. + if self.ready or not self.opts.load_balanced: + reason = None if self.ready else ConnectionClosedReason.ERROR + else: + reason = None # deferred to pool.connect()'s except block + await self.close_conn(reason) # SSLError from PyOpenSSL inherits directly from Exception. if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) @@ -1085,15 +1089,14 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A self.active_contexts.discard(conn.cancel_context) if not completed_hello: self._handle_connection_error(e) - if completed_hello and handler: - # Hello succeeded so service_id is known. Run SDAM error - # handling (which clears the pool) before emitting + if completed_hello and handler and self.opts.load_balanced: + # LB only: hello succeeded so service_id is known. Run SDAM + # error handling (which clears the pool) before emitting # connectionClosedEvent as required by the CMAP spec. await handler.handle(type(e), e) - if conn.closed: - # _raise_connection_failure already closed the TCP socket - # without emitting connectionClosedEvent (deferred). Emit it - # now so it follows any poolClearedEvent from above. + if self.opts.load_balanced and conn.closed: + # LB: _raise_connection_failure deferred the connectionClosedEvent. + # Emit it now, after any poolClearedEvent emitted above. if conn.enabled_for_cmap: assert conn.listeners is not None conn.listeners.publish_connection_closed( @@ -1111,6 +1114,7 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A error=ConnectionClosedReason.ERROR, ) else: + # Non-LB: close_conn emits event if not already closed. await conn.close_conn(ConnectionClosedReason.ERROR) raise diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 96770fbcb9..afc53eef70 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -621,11 +621,15 @@ def _raise_connection_failure(self, error: BaseException) -> NoReturn: # KeyboardInterrupt from the start, rather than as an initial # socket.error, so we catch that, close the socket, and reraise it. # - # The connection closed event will be emitted later in checkin (for - # ready connections) or by pool.connect()'s except block (for - # not-ready connections). Always use reason=None here so that the - # event can be deferred past any pool clearing that needs to happen. - self.close_conn(None) + # For ready connections the closed event is emitted later in checkin. + # For not-ready LB connections, defer the event so that pool.connect()'s + # except block can emit poolClearedEvent first (CMAP spec requirement). + # For not-ready non-LB connections, emit the event immediately as before. + if self.ready or not self.opts.load_balanced: + reason = None if self.ready else ConnectionClosedReason.ERROR + else: + reason = None # deferred to pool.connect()'s except block + self.close_conn(reason) # SSLError from PyOpenSSL inherits directly from Exception. if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) @@ -1081,15 +1085,14 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect self.active_contexts.discard(conn.cancel_context) if not completed_hello: self._handle_connection_error(e) - if completed_hello and handler: - # Hello succeeded so service_id is known. Run SDAM error - # handling (which clears the pool) before emitting + if completed_hello and handler and self.opts.load_balanced: + # LB only: hello succeeded so service_id is known. Run SDAM + # error handling (which clears the pool) before emitting # connectionClosedEvent as required by the CMAP spec. handler.handle(type(e), e) - if conn.closed: - # _raise_connection_failure already closed the TCP socket - # without emitting connectionClosedEvent (deferred). Emit it - # now so it follows any poolClearedEvent from above. + if self.opts.load_balanced and conn.closed: + # LB: _raise_connection_failure deferred the connectionClosedEvent. + # Emit it now, after any poolClearedEvent emitted above. if conn.enabled_for_cmap: assert conn.listeners is not None conn.listeners.publish_connection_closed( @@ -1107,6 +1110,7 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect error=ConnectionClosedReason.ERROR, ) else: + # Non-LB: close_conn emits event if not already closed. conn.close_conn(ConnectionClosedReason.ERROR) raise From 18b5efd543e5e43831d850f3c506d8088bce1fe4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 2 Jun 2026 18:05:28 -0500 Subject: [PATCH 8/8] Fix PYTHON-5791.patch direction and revert spec files directly The spec patches in .evergreen/spec-patch/ are only applied during spec resyncs, not regular test runs. The spec file changes must also be reverted directly on the branch. Fix the patch file to use the forward diff direction (what was added in the spec) so git apply -R works correctly in future resyncs. --- .evergreen/spec-patch/PYTHON-5791.patch | 89 +++++++++---------- .../unified/callback-aborts.json | 8 +- .../unified/callback-commits.json | 8 +- test/transactions/unified/commit.json | 8 +- .../unified/retryable-writes.json | 8 +- 5 files changed, 53 insertions(+), 68 deletions(-) diff --git a/.evergreen/spec-patch/PYTHON-5791.patch b/.evergreen/spec-patch/PYTHON-5791.patch index 145ddadff7..1a078712c9 100644 --- a/.evergreen/spec-patch/PYTHON-5791.patch +++ b/.evergreen/spec-patch/PYTHON-5791.patch @@ -1,97 +1,94 @@ PYMONGO DOES NOT YET SEND readConcern.afterClusterTime IN RETRYABLE WRITE RETRIES + diff --git a/test/transactions-convenient-api/unified/callback-aborts.json b/test/transactions-convenient-api/unified/callback-aborts.json -index dc8f7fb5..20642871 100644 +index 20642871..dc8f7fb5 100644 --- a/test/transactions-convenient-api/unified/callback-aborts.json +++ b/test/transactions-convenient-api/unified/callback-aborts.json -@@ -308,14 +308,12 @@ +@@ -308,10 +308,12 @@ "lsid": { "$$sessionLsid": "session0" }, -- "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } +- "autocommit": { +- "$$exists": false - }, - "autocommit": { - "$$exists": false - }, -+ "readConcern": { -+ "$$exists": false + "readConcern": { ++ "afterClusterTime": { ++ "$$exists": true ++ } + }, - "startTransaction": { ++ "autocommit": { "$$exists": false }, + "startTransaction": { diff --git a/test/transactions-convenient-api/unified/callback-commits.json b/test/transactions-convenient-api/unified/callback-commits.json -index edda3864..06f791e9 100644 +index 06f791e9..edda3864 100644 --- a/test/transactions-convenient-api/unified/callback-commits.json +++ b/test/transactions-convenient-api/unified/callback-commits.json -@@ -381,14 +381,12 @@ +@@ -381,10 +381,12 @@ "lsid": { "$$sessionLsid": "session0" }, -- "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } +- "autocommit": { +- "$$exists": false - }, - "autocommit": { - "$$exists": false - }, -+ "readConcern": { -+ "$$exists": false + "readConcern": { ++ "afterClusterTime": { ++ "$$exists": true ++ } + }, - "startTransaction": { ++ "autocommit": { "$$exists": false }, + "startTransaction": { diff --git a/test/transactions/unified/commit.json b/test/transactions/unified/commit.json -index f0339069..ab778d8d 100644 +index ab778d8d..f0339069 100644 --- a/test/transactions/unified/commit.json +++ b/test/transactions/unified/commit.json -@@ -1040,9 +1040,7 @@ +@@ -1040,7 +1040,9 @@ ], "ordered": true, "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } -+ "$$exists": false +- "$$exists": false ++ "afterClusterTime": { ++ "$$exists": true ++ } }, "lsid": { "$$sessionLsid": "session1" -@@ -1198,9 +1196,7 @@ +@@ -1196,7 +1198,9 @@ ], "ordered": true, "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } -+ "$$exists": false +- "$$exists": false ++ "afterClusterTime": { ++ "$$exists": true ++ } }, "lsid": { "$$sessionLsid": "session1" diff --git a/test/transactions/unified/retryable-writes.json b/test/transactions/unified/retryable-writes.json -index 21ef22b8..c196e686 100644 +index c196e686..21ef22b8 100644 --- a/test/transactions/unified/retryable-writes.json +++ b/test/transactions/unified/retryable-writes.json -@@ -217,9 +217,7 @@ +@@ -217,7 +217,9 @@ ], "ordered": true, "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } -+ "$$exists": false +- "$$exists": false ++ "afterClusterTime": { ++ "$$exists": true ++ } }, "lsid": { "$$sessionLsid": "session0" -@@ -308,9 +306,7 @@ +@@ -306,7 +308,9 @@ ], "ordered": true, "readConcern": { -- "afterClusterTime": { -- "$$exists": true -- } -+ "$$exists": false +- "$$exists": false ++ "afterClusterTime": { ++ "$$exists": true ++ } }, "lsid": { "$$sessionLsid": "session0" diff --git a/test/transactions-convenient-api/unified/callback-aborts.json b/test/transactions-convenient-api/unified/callback-aborts.json index dc8f7fb5a4..206428715c 100644 --- a/test/transactions-convenient-api/unified/callback-aborts.json +++ b/test/transactions-convenient-api/unified/callback-aborts.json @@ -308,14 +308,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "readConcern": { - "afterClusterTime": { - "$$exists": true - } - }, "autocommit": { "$$exists": false }, + "readConcern": { + "$$exists": false + }, "startTransaction": { "$$exists": false }, diff --git a/test/transactions-convenient-api/unified/callback-commits.json b/test/transactions-convenient-api/unified/callback-commits.json index edda386418..06f791e9ae 100644 --- a/test/transactions-convenient-api/unified/callback-commits.json +++ b/test/transactions-convenient-api/unified/callback-commits.json @@ -381,14 +381,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "readConcern": { - "afterClusterTime": { - "$$exists": true - } - }, "autocommit": { "$$exists": false }, + "readConcern": { + "$$exists": false + }, "startTransaction": { "$$exists": false }, diff --git a/test/transactions/unified/commit.json b/test/transactions/unified/commit.json index f033906940..ab778d8df2 100644 --- a/test/transactions/unified/commit.json +++ b/test/transactions/unified/commit.json @@ -1040,9 +1040,7 @@ ], "ordered": true, "readConcern": { - "afterClusterTime": { - "$$exists": true - } + "$$exists": false }, "lsid": { "$$sessionLsid": "session1" @@ -1198,9 +1196,7 @@ ], "ordered": true, "readConcern": { - "afterClusterTime": { - "$$exists": true - } + "$$exists": false }, "lsid": { "$$sessionLsid": "session1" diff --git a/test/transactions/unified/retryable-writes.json b/test/transactions/unified/retryable-writes.json index 21ef22b8ab..c196e68622 100644 --- a/test/transactions/unified/retryable-writes.json +++ b/test/transactions/unified/retryable-writes.json @@ -217,9 +217,7 @@ ], "ordered": true, "readConcern": { - "afterClusterTime": { - "$$exists": true - } + "$$exists": false }, "lsid": { "$$sessionLsid": "session0" @@ -308,9 +306,7 @@ ], "ordered": true, "readConcern": { - "afterClusterTime": { - "$$exists": true - } + "$$exists": false }, "lsid": { "$$sessionLsid": "session0"