Skip to content

Commit cf7a1aa

Browse files
authored
PYTHON-5504 Prototype exponential backoff in with_transaction (#2492)
1 parent e4b7eb5 commit cf7a1aa

File tree

2 files changed

+19
-0
lines changed

2 files changed

+19
-0
lines changed

pymongo/asynchronous/client_session.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@
135135

136136
from __future__ import annotations
137137

138+
import asyncio
138139
import collections
140+
import random
139141
import time
140142
import uuid
141143
from collections.abc import Mapping as _Mapping
@@ -471,6 +473,8 @@ def _max_time_expired_error(exc: PyMongoError) -> bool:
471473
# This limit is non-configurable and was chosen to be twice the 60 second
472474
# default value of MongoDB's `transactionLifetimeLimitSeconds` parameter.
473475
_WITH_TRANSACTION_RETRY_TIME_LIMIT = 120
476+
_BACKOFF_MAX = 1
477+
_BACKOFF_INITIAL = 0.050 # 50ms initial backoff
474478

475479

476480
def _within_time_limit(start_time: float) -> bool:
@@ -700,7 +704,13 @@ async def callback(session, custom_arg, custom_kwarg=None):
700704
https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/transactions-convenient-api.md#handling-errors-inside-the-callback
701705
"""
702706
start_time = time.monotonic()
707+
retry = 0
703708
while True:
709+
if retry: # Implement exponential backoff on retry.
710+
jitter = random.random() # noqa: S311
711+
backoff = jitter * min(_BACKOFF_INITIAL * (2**retry), _BACKOFF_MAX)
712+
await asyncio.sleep(backoff)
713+
retry += 1
704714
await self.start_transaction(
705715
read_concern, write_concern, read_preference, max_commit_time_ms
706716
)

pymongo/synchronous/client_session.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
from __future__ import annotations
137137

138138
import collections
139+
import random
139140
import time
140141
import uuid
141142
from collections.abc import Mapping as _Mapping
@@ -470,6 +471,8 @@ def _max_time_expired_error(exc: PyMongoError) -> bool:
470471
# This limit is non-configurable and was chosen to be twice the 60 second
471472
# default value of MongoDB's `transactionLifetimeLimitSeconds` parameter.
472473
_WITH_TRANSACTION_RETRY_TIME_LIMIT = 120
474+
_BACKOFF_MAX = 1
475+
_BACKOFF_INITIAL = 0.050 # 50ms initial backoff
473476

474477

475478
def _within_time_limit(start_time: float) -> bool:
@@ -699,7 +702,13 @@ def callback(session, custom_arg, custom_kwarg=None):
699702
https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/transactions-convenient-api.md#handling-errors-inside-the-callback
700703
"""
701704
start_time = time.monotonic()
705+
retry = 0
702706
while True:
707+
if retry: # Implement exponential backoff on retry.
708+
jitter = random.random() # noqa: S311
709+
backoff = jitter * min(_BACKOFF_INITIAL * (2**retry), _BACKOFF_MAX)
710+
time.sleep(backoff)
711+
retry += 1
703712
self.start_transaction(read_concern, write_concern, read_preference, max_commit_time_ms)
704713
try:
705714
ret = callback(self)

0 commit comments

Comments
 (0)