@@ -158,7 +158,7 @@ def call_sub_orchestrator(
158158 """
159159 pass
160160
161- # TOOD : Add a timeout parameter, which allows the task to be canceled if the event is
161+ # TODO : Add a timeout parameter, which allows the task to be canceled if the event is
162162 # not received within the specified timeout. This requires support for task cancellation.
163163 @abstractmethod
164164 def wait_for_external_event (self , name : str ) -> Task :
@@ -434,7 +434,22 @@ def compute_next_delay(self) -> Optional[timedelta]:
434434 else :
435435 backoff_coefficient = self ._retry_policy .backoff_coefficient
436436
437- if datetime .utcnow () < retry_expiration :
437+ # Compute a deterministic "logical now" based on start time and accumulated delays,
438+ # rather than wall-clock time, to avoid non-determinism during replay.
439+ total_elapsed_seconds = 0.0
440+ for i in range (1 , self ._attempt_count ):
441+ attempt_delay = (
442+ math .pow (backoff_coefficient , i - 1 )
443+ * self ._retry_policy .first_retry_interval .total_seconds ()
444+ )
445+ if self ._retry_policy .max_retry_interval is not None :
446+ attempt_delay = min (
447+ attempt_delay ,
448+ self ._retry_policy .max_retry_interval .total_seconds (),
449+ )
450+ total_elapsed_seconds += attempt_delay
451+ logical_now = self ._start_time + timedelta (seconds = total_elapsed_seconds )
452+ if logical_now < retry_expiration :
438453 next_delay_f = (
439454 math .pow (backoff_coefficient , self ._attempt_count - 1 )
440455 * self ._retry_policy .first_retry_interval .total_seconds ()
0 commit comments