Skip to content

Commit 73b0b19

Browse files
authored
PYTHON-1894: Client-side errors in in-progress transactions must not change transaction state (mongodb#2857)
1 parent 0f5596e commit 73b0b19

17 files changed

Lines changed: 48 additions & 25 deletions

.evergreen/remove-unimplemented-tests.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/bin/bash
22
PYMONGO=$(dirname "$(cd "$(dirname "$0")" || exit; pwd)")
33

4-
rm $PYMONGO/test/transactions/legacy/errors-client.json # PYTHON-1894
54
rm $PYMONGO/test/connection_monitoring/wait-queue-fairness.json # PYTHON-1873
65
rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-application-error.json # PYTHON-4918
76
rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-checkout-error.json # PYTHON-4918

pymongo/asynchronous/bulk.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
from bson.objectid import ObjectId
3737
from bson.raw_bson import RawBSONDocument
3838
from pymongo import _csot, common
39-
from pymongo.asynchronous.client_session import AsyncClientSession, _validate_session_write_concern
39+
from pymongo.asynchronous.client_session import (
40+
AsyncClientSession,
41+
_validate_session_write_concern,
42+
)
4043
from pymongo.asynchronous.helpers import _handle_reauth
4144
from pymongo.bulk_shared import (
4245
_COMMANDS,
@@ -271,6 +274,8 @@ async def write_command(
271274
if bwc.publish:
272275
bwc._start(cmd, request_id, docs)
273276
try:
277+
if bwc.session is not None and bwc.session._starting_transaction:
278+
bwc.session._transaction.set_in_progress()
274279
reply = await bwc.conn.write_command(request_id, msg, bwc.codec) # type: ignore[misc]
275280
duration = datetime.datetime.now() - bwc.start_time
276281
if _COMMAND_LOGGER.isEnabledFor(logging.DEBUG):

pymongo/asynchronous/client_bulk.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
from bson.objectid import ObjectId
3636
from bson.raw_bson import RawBSONDocument
3737
from pymongo import _csot, common
38-
from pymongo.asynchronous.client_session import AsyncClientSession, _validate_session_write_concern
38+
from pymongo.asynchronous.client_session import (
39+
AsyncClientSession,
40+
_validate_session_write_concern,
41+
)
3942
from pymongo.asynchronous.collection import AsyncCollection
4043
from pymongo.asynchronous.command_cursor import AsyncCommandCursor
4144
from pymongo.asynchronous.database import AsyncDatabase
@@ -258,6 +261,8 @@ async def write_command(
258261
if bwc.publish:
259262
bwc._start(cmd, request_id, op_docs, ns_docs)
260263
try:
264+
if bwc.session is not None and bwc.session._starting_transaction:
265+
bwc.session._transaction.set_in_progress()
261266
reply = await bwc.conn.write_command(request_id, msg, bwc.codec) # type: ignore[misc, arg-type]
262267
duration = datetime.datetime.now() - bwc.start_time
263268
if _COMMAND_LOGGER.isEnabledFor(logging.DEBUG):

pymongo/asynchronous/client_session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,10 @@ def starting(self) -> bool:
443443
def set_starting(self) -> None:
444444
self.state = _TxnState.STARTING
445445

446+
def set_in_progress(self) -> None:
447+
if self.state == _TxnState.STARTING:
448+
self.state = _TxnState.IN_PROGRESS
449+
446450
@property
447451
def pinned_conn(self) -> Optional[AsyncConnection]:
448452
if self.active() and self.conn_mgr:
@@ -1135,7 +1139,6 @@ def _apply_to(
11351139

11361140
if self._transaction.state == _TxnState.STARTING:
11371141
# First command begins a new transaction.
1138-
self._transaction.state = _TxnState.IN_PROGRESS
11391142
command["startTransaction"] = True
11401143

11411144
assert self._transaction.opts

pymongo/asynchronous/mongo_client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,8 +2870,8 @@ async def run(self) -> T:
28702870
self._last_error = exc
28712871
self._attempt_number += 1
28722872

2873-
# Revert back to starting state if we're in a transaction but haven't completed the first
2874-
# command.
2873+
# Revert back to starting state only if the first
2874+
# transactional command was never completed.
28752875
if (
28762876
overloaded
28772877
and self._session is not None
@@ -2921,8 +2921,8 @@ async def run(self) -> T:
29212921
self._last_error = exc
29222922
if self._last_error is None:
29232923
self._last_error = exc
2924-
# Revert back to starting state if we're in a transaction but haven't completed the first
2925-
# command.
2924+
# Revert back to starting state only if the first
2925+
# transactional command was never completed.
29262926
if overloaded and self._session is not None and self._session.in_transaction:
29272927
transaction = self._session._transaction
29282928
if not transaction.has_completed_command:

pymongo/asynchronous/pool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ async def command(
395395
unacknowledged = bool(write_concern and not write_concern.acknowledged)
396396
self._raise_if_not_writable(unacknowledged)
397397
try:
398+
if session is not None and session._starting_transaction:
399+
session._transaction.set_in_progress()
398400
return await command(
399401
self,
400402
dbname,

pymongo/asynchronous/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ async def run_operation(
204204
if more_to_come:
205205
reply = await conn.receive_message(None)
206206
else:
207+
if operation.session is not None and operation.session._starting_transaction:
208+
operation.session._transaction.set_in_progress()
207209
await conn.send_message(data, max_doc_size)
208210
reply = await conn.receive_message(request_id)
209211

pymongo/synchronous/bulk.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@
6767
_randint,
6868
)
6969
from pymongo.read_preferences import ReadPreference
70-
from pymongo.synchronous.client_session import ClientSession, _validate_session_write_concern
70+
from pymongo.synchronous.client_session import (
71+
ClientSession,
72+
_validate_session_write_concern,
73+
)
7174
from pymongo.synchronous.helpers import _handle_reauth
7275
from pymongo.write_concern import WriteConcern
7376

@@ -271,6 +274,8 @@ def write_command(
271274
if bwc.publish:
272275
bwc._start(cmd, request_id, docs)
273276
try:
277+
if bwc.session is not None and bwc.session._starting_transaction:
278+
bwc.session._transaction.set_in_progress()
274279
reply = bwc.conn.write_command(request_id, msg, bwc.codec) # type: ignore[misc]
275280
duration = datetime.datetime.now() - bwc.start_time
276281
if _COMMAND_LOGGER.isEnabledFor(logging.DEBUG):

pymongo/synchronous/client_bulk.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
from bson.objectid import ObjectId
3636
from bson.raw_bson import RawBSONDocument
3737
from pymongo import _csot, common
38-
from pymongo.synchronous.client_session import ClientSession, _validate_session_write_concern
38+
from pymongo.synchronous.client_session import (
39+
ClientSession,
40+
_validate_session_write_concern,
41+
)
3942
from pymongo.synchronous.collection import Collection
4043
from pymongo.synchronous.command_cursor import CommandCursor
4144
from pymongo.synchronous.database import Database
@@ -258,6 +261,8 @@ def write_command(
258261
if bwc.publish:
259262
bwc._start(cmd, request_id, op_docs, ns_docs)
260263
try:
264+
if bwc.session is not None and bwc.session._starting_transaction:
265+
bwc.session._transaction.set_in_progress()
261266
reply = bwc.conn.write_command(request_id, msg, bwc.codec) # type: ignore[misc, arg-type]
262267
duration = datetime.datetime.now() - bwc.start_time
263268
if _COMMAND_LOGGER.isEnabledFor(logging.DEBUG):

pymongo/synchronous/client_session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ def starting(self) -> bool:
441441
def set_starting(self) -> None:
442442
self.state = _TxnState.STARTING
443443

444+
def set_in_progress(self) -> None:
445+
if self.state == _TxnState.STARTING:
446+
self.state = _TxnState.IN_PROGRESS
447+
444448
@property
445449
def pinned_conn(self) -> Optional[Connection]:
446450
if self.active() and self.conn_mgr:
@@ -1131,7 +1135,6 @@ def _apply_to(
11311135

11321136
if self._transaction.state == _TxnState.STARTING:
11331137
# First command begins a new transaction.
1134-
self._transaction.state = _TxnState.IN_PROGRESS
11351138
command["startTransaction"] = True
11361139

11371140
assert self._transaction.opts

0 commit comments

Comments
 (0)