22
33from __future__ import annotations
44
5- from enum import StrEnum
5+ import math
66import random
77import re
8- import sys
98from dataclasses import dataclass , field
9+ from enum import StrEnum
1010from typing import TYPE_CHECKING
1111
1212if TYPE_CHECKING :
1313 from collections .abc import Callable
14- from aws_durable_execution_sdk_python .types import JitterStrategy
1514
1615Numeric = int | float
1716
1817# region Jitter
1918
19+
2020class JitterStrategy (StrEnum ):
2121 """
2222 Jitter strategies are used to introduce noise when attempting to retry
2323 an invoke. We introduce noise to prevent a thundering-herd effect where
2424 a group of accesses (e.g. invokes) happen at once.
2525
26- Jitter is meant to be used to spread operations across time.
26+ Jitter is meant to be used to spread operations across time.
2727
2828 members:
2929 :NONE: No jitter; use the exact calculated delay
3030 :FULL: Full jitter; random delay between 0 and calculated delay
3131 :HALF: Half jitter; random delay between 0.5x and 1.0x of the calculated delay
3232 """
33+
3334 NONE = "NONE"
3435 FULL = "FULL"
3536 HALF = "HALF"
3637
37- def compute_jitter (self , delay ) -> int :
38+ def compute_jitter (self , delay ) -> float :
3839 match self :
3940 case JitterStrategy .NONE :
4041 return 0
41- case JitterStrategy .FULL :
42- return random .random () * delay # noqa: S311
4342 case JitterStrategy .HALF :
44- return (random .random () * 0.5 + 0.5 ) # noqa: S311
43+ return random .random () * 0.5 + 0.5 # noqa: S311
44+ case _: # default is FULL
45+ return random .random () * delay # noqa: S311
46+
4547
4648# endregion Jitter
4749
@@ -66,7 +68,7 @@ def no_retry(cls) -> RetryDecision:
6668
6769@dataclass
6870class RetryStrategyConfig :
69- max_attempts : int = sys . maxsize # "infinite", practically
71+ max_attempts : int = 3 # "infinite", practically
7072 initial_delay_seconds : int = 5
7173 max_delay_seconds : int = 300 # 5 minutes
7274 backoff_rate : Numeric = 2.0
@@ -109,8 +111,9 @@ def retry_strategy(error: Exception, attempts_made: int) -> RetryDecision:
109111 config .initial_delay_seconds * (config .backoff_rate ** (attempts_made - 1 )),
110112 config .max_delay_seconds ,
111113 )
112- jitter = config .jitter_strategy .compute_jitter (delay )
113- final_delay = max (1 , delay + jitter )
114+ delay_with_jitter = delay + config .jitter_strategy .compute_jitter (delay )
115+ delay_with_jitter = math .ceil (delay_with_jitter )
116+ final_delay = max (1 , delay_with_jitter )
114117
115118 return RetryDecision .retry (round (final_delay ))
116119
@@ -123,17 +126,18 @@ class RetryPresets:
123126 @classmethod
124127 def none (cls ) -> Callable [[Exception , int ], RetryDecision ]:
125128 """No retries."""
126- return create_retry_strategy (RetryStrategyConfig (max_attempts = 0 ))
129+ return create_retry_strategy (RetryStrategyConfig (max_attempts = 1 ))
127130
128131 @classmethod
129132 def default (cls ) -> Callable [[Exception , int ], RetryDecision ]:
130133 """Default retries, will be used automatically if retryConfig is missing"""
131134 return create_retry_strategy (
132135 RetryStrategyConfig (
133- max_attempts = sys . maxsize ,
136+ max_attempts = 6 ,
134137 initial_delay_seconds = 5 ,
135138 max_delay_seconds = 60 ,
136139 backoff_rate = 2 ,
140+ jitter_strategy = JitterStrategy .FULL ,
137141 )
138142 )
139143
@@ -142,9 +146,7 @@ def transient(cls) -> Callable[[Exception, int], RetryDecision]:
142146 """Quick retries for transient errors"""
143147 return create_retry_strategy (
144148 RetryStrategyConfig (
145- max_attempts = 3 ,
146- backoff_rate = 2 ,
147- jitter_seconds = 0.5 ,
149+ max_attempts = 3 , backoff_rate = 2 , jitter_strategy = JitterStrategy .HALF
148150 )
149151 )
150152
@@ -169,5 +171,6 @@ def critical(cls) -> Callable[[Exception, int], RetryDecision]:
169171 initial_delay_seconds = 1 ,
170172 max_delay_seconds = 60 ,
171173 backoff_rate = 1.5 ,
174+ jitter_strategy = JitterStrategy .NONE ,
172175 )
173176 )
0 commit comments