@@ -2010,6 +2010,8 @@ async def _retry_internal(
20102010 read_pref : Optional [_ServerMode ] = None ,
20112011 retryable : bool = False ,
20122012 operation_id : Optional [int ] = None ,
2013+ is_run_command : bool = False ,
2014+ is_aggregate_write : bool = False ,
20132015 ) -> T :
20142016 """Internal retryable helper for all client transactions.
20152017
@@ -2021,6 +2023,8 @@ async def _retry_internal(
20212023 :param address: Server Address, defaults to None
20222024 :param read_pref: Topology of read operation, defaults to None
20232025 :param retryable: If the operation should be retried once, defaults to None
2026+ :param is_run_command: If this is a runCommand operation, defaults to False
2027+ :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False.
20242028
20252029 :return: Output of the calling func()
20262030 """
@@ -2035,6 +2039,8 @@ async def _retry_internal(
20352039 address = address ,
20362040 retryable = retryable ,
20372041 operation_id = operation_id ,
2042+ is_run_command = is_run_command ,
2043+ is_aggregate_write = is_aggregate_write ,
20382044 ).run ()
20392045
20402046 async def _retryable_read (
@@ -2046,6 +2052,8 @@ async def _retryable_read(
20462052 address : Optional [_Address ] = None ,
20472053 retryable : bool = True ,
20482054 operation_id : Optional [int ] = None ,
2055+ is_run_command : bool = False ,
2056+ is_aggregate_write : bool = False ,
20492057 ) -> T :
20502058 """Execute an operation with consecutive retries if possible
20512059
@@ -2061,6 +2069,8 @@ async def _retryable_read(
20612069 :param address: Optional address when sending a message, defaults to None
20622070 :param retryable: if we should attempt retries
20632071 (may not always be supported even if supplied), defaults to False
2072+ :param is_run_command: If this is a runCommand operation, defaults to False.
2073+ :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False.
20642074 """
20652075
20662076 # Ensure that the client supports retrying on reads and there is no session in
@@ -2079,6 +2089,8 @@ async def _retryable_read(
20792089 read_pref = read_pref ,
20802090 retryable = retryable ,
20812091 operation_id = operation_id ,
2092+ is_run_command = is_run_command ,
2093+ is_aggregate_write = is_aggregate_write ,
20822094 )
20832095
20842096 async def _retryable_write (
@@ -2748,6 +2760,8 @@ def __init__(
27482760 address : Optional [_Address ] = None ,
27492761 retryable : bool = False ,
27502762 operation_id : Optional [int ] = None ,
2763+ is_run_command : bool = False ,
2764+ is_aggregate_write : bool = False ,
27512765 ):
27522766 self ._last_error : Optional [Exception ] = None
27532767 self ._retrying = False
@@ -2770,6 +2784,8 @@ def __init__(
27702784 self ._operation = operation
27712785 self ._operation_id = operation_id
27722786 self ._attempt_number = 0
2787+ self ._is_run_command = is_run_command
2788+ self ._is_aggregate_write = is_aggregate_write
27732789
27742790 async def run (self ) -> T :
27752791 """Runs the supplied func() and attempts a retry
@@ -2810,18 +2826,30 @@ async def run(self) -> T:
28102826 always_retryable = False
28112827 overloaded = False
28122828 exc_to_check = exc
2829+
2830+ if self ._is_run_command and not (
2831+ self ._client .options .retry_reads and self ._client .options .retry_writes
2832+ ):
2833+ raise
2834+ if self ._is_aggregate_write and not self ._client .options .retry_writes :
2835+ raise
2836+
28132837 # Execute specialized catch on read
28142838 if self ._is_read :
28152839 if isinstance (exc , (ConnectionFailure , OperationFailure )):
28162840 # ConnectionFailures do not supply a code property
28172841 exc_code = getattr (exc , "code" , None )
28182842 overloaded = exc .has_error_label ("SystemOverloadedError" )
28192843 always_retryable = exc .has_error_label ("RetryableError" ) and overloaded
2820- if not always_retryable and (
2821- self ._is_not_eligible_for_retry ()
2822- or (
2823- isinstance (exc , OperationFailure )
2824- and exc_code not in helpers_shared ._RETRYABLE_ERROR_CODES
2844+ if (
2845+ not self ._client .options .retry_reads
2846+ or not always_retryable
2847+ and (
2848+ self ._is_not_eligible_for_retry ()
2849+ or (
2850+ isinstance (exc , OperationFailure )
2851+ and exc_code not in helpers_shared ._RETRYABLE_ERROR_CODES
2852+ )
28252853 )
28262854 ):
28272855 raise
@@ -2852,7 +2880,12 @@ async def run(self) -> T:
28522880 retryable_write_label = exc_to_check .has_error_label ("RetryableWriteError" )
28532881 overloaded = exc_to_check .has_error_label ("SystemOverloadedError" )
28542882 always_retryable = exc_to_check .has_error_label ("RetryableError" ) and overloaded
2855- if not self ._retryable and not always_retryable :
2883+
2884+ # Always retry abortTransaction and commitTransaction up to once
2885+ if self ._operation not in ["abortTransaction" , "commitTransaction" ] and (
2886+ not self ._client .options .retry_writes
2887+ or not (self ._retryable or always_retryable )
2888+ ):
28562889 raise
28572890 if retryable_write_label or always_retryable :
28582891 assert self ._session
0 commit comments