Skip to content

Commit 8b7daff

Browse files
committed
add test
1 parent 4f2594b commit 8b7daff

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

pymongo/asynchronous/client_session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ def __init__(
518518
# Is this an implicitly created session?
519519
self._implicit = implicit
520520
self._transaction = _Transaction(None, client)
521+
self._transaction_retry_backoffs: list[float] = []
521522

522523
async def end_session(self) -> None:
523524
"""Finish this session. If a transaction has started, abort it.
@@ -705,10 +706,12 @@ async def callback(session, custom_arg, custom_kwarg=None):
705706
"""
706707
start_time = time.monotonic()
707708
retry = 0
709+
self._transaction_retry_backoffs = []
708710
while True:
709711
if retry: # Implement exponential backoff on retry.
710712
jitter = random.random() # noqa: S311
711713
backoff = jitter * min(_BACKOFF_INITIAL * (1.25**retry), _BACKOFF_MAX)
714+
self._transaction_retry_backoffs.append(backoff)
712715
await asyncio.sleep(backoff)
713716
retry += 1
714717
await self.start_transaction(

pymongo/synchronous/client_session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ def __init__(
516516
# Is this an implicitly created session?
517517
self._implicit = implicit
518518
self._transaction = _Transaction(None, client)
519+
self._transaction_retry_backoffs: list[float] = []
519520

520521
def end_session(self) -> None:
521522
"""Finish this session. If a transaction has started, abort it.
@@ -703,10 +704,12 @@ def callback(session, custom_arg, custom_kwarg=None):
703704
"""
704705
start_time = time.monotonic()
705706
retry = 0
707+
self._transaction_retry_backoffs = []
706708
while True:
707709
if retry: # Implement exponential backoff on retry.
708710
jitter = random.random() # noqa: S311
709711
backoff = jitter * min(_BACKOFF_INITIAL * (1.25**retry), _BACKOFF_MAX)
712+
self._transaction_retry_backoffs.append(backoff)
710713
time.sleep(backoff)
711714
retry += 1
712715
self.start_transaction(read_concern, write_concern, read_preference, max_commit_time_ms)

test/asynchronous/test_transactions.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from __future__ import annotations
1717

1818
import sys
19+
import time
1920
from io import BytesIO
2021
from test.asynchronous.utils_spec_runner import AsyncSpecRunner
2122

@@ -36,7 +37,11 @@
3637
from bson.raw_bson import RawBSONDocument
3738
from pymongo import WriteConcern, _csot
3839
from pymongo.asynchronous import client_session
39-
from pymongo.asynchronous.client_session import TransactionOptions
40+
from pymongo.asynchronous.client_session import (
41+
_BACKOFF_MAX,
42+
TransactionOptions,
43+
_set_backoff_initial,
44+
)
4045
from pymongo.asynchronous.command_cursor import AsyncCommandCursor
4146
from pymongo.asynchronous.cursor import AsyncCursor
4247
from pymongo.asynchronous.helpers import anext
@@ -602,6 +607,42 @@ async def callback(session):
602607
await s.with_transaction(callback)
603608
self.assertFalse(s.in_transaction)
604609

610+
@async_client_context.require_test_commands
611+
@async_client_context.require_transactions
612+
async def test_transaction_backoff(self):
613+
client = async_client_context.client
614+
coll = client[self.db.name].test
615+
# set fail point to trigger transaction failure and trigger backoff
616+
await self.set_fail_point(
617+
{
618+
"configureFailPoint": "failCommand",
619+
"mode": {"times": 3},
620+
"data": {
621+
"failCommands": ["commitTransaction"],
622+
"errorCode": 24,
623+
},
624+
}
625+
)
626+
self.addAsyncCleanup(
627+
self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"}
628+
)
629+
630+
start = time.monotonic()
631+
632+
async def callback(session):
633+
await coll.insert_one({}, session=session)
634+
635+
total_backoff = 0
636+
async with self.client.start_session() as s:
637+
await s.with_transaction(callback)
638+
self.assertEqual(len(s._transaction_retry_backoffs), 3)
639+
for backoff in s._transaction_retry_backoffs:
640+
self.assertGreater(backoff, 0)
641+
total_backoff += backoff
642+
643+
end = time.monotonic()
644+
self.assertGreaterEqual(end - start, total_backoff)
645+
605646

606647
class TestOptionsInsideTransactionProse(AsyncTransactionsBase):
607648
@async_client_context.require_transactions

test/test_transactions.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from __future__ import annotations
1717

1818
import sys
19+
import time
1920
from io import BytesIO
2021
from test.utils_spec_runner import SpecRunner
2122

@@ -47,7 +48,11 @@
4748
from pymongo.read_concern import ReadConcern
4849
from pymongo.read_preferences import ReadPreference
4950
from pymongo.synchronous import client_session
50-
from pymongo.synchronous.client_session import TransactionOptions
51+
from pymongo.synchronous.client_session import (
52+
_BACKOFF_MAX,
53+
TransactionOptions,
54+
_set_backoff_initial,
55+
)
5156
from pymongo.synchronous.command_cursor import CommandCursor
5257
from pymongo.synchronous.cursor import Cursor
5358
from pymongo.synchronous.helpers import next
@@ -590,6 +595,40 @@ def callback(session):
590595
s.with_transaction(callback)
591596
self.assertFalse(s.in_transaction)
592597

598+
@client_context.require_test_commands
599+
@client_context.require_transactions
600+
def test_transaction_backoff(self):
601+
client = client_context.client
602+
coll = client[self.db.name].test
603+
# set fail point to trigger transaction failure and trigger backoff
604+
self.set_fail_point(
605+
{
606+
"configureFailPoint": "failCommand",
607+
"mode": {"times": 3},
608+
"data": {
609+
"failCommands": ["commitTransaction"],
610+
"errorCode": 24,
611+
},
612+
}
613+
)
614+
self.addCleanup(self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"})
615+
616+
start = time.monotonic()
617+
618+
def callback(session):
619+
coll.insert_one({}, session=session)
620+
621+
total_backoff = 0
622+
with self.client.start_session() as s:
623+
s.with_transaction(callback)
624+
self.assertEqual(len(s._transaction_retry_backoffs), 3)
625+
for backoff in s._transaction_retry_backoffs:
626+
self.assertGreater(backoff, 0)
627+
total_backoff += backoff
628+
629+
end = time.monotonic()
630+
self.assertGreaterEqual(end - start, total_backoff)
631+
593632

594633
class TestOptionsInsideTransactionProse(TransactionsBase):
595634
@client_context.require_transactions

0 commit comments

Comments
 (0)