11from collections import defaultdict
22from azure .durable_functions .models .actions .SignalEntityAction import SignalEntityAction
33from azure .durable_functions .models .actions .CallEntityAction import CallEntityAction
4- from azure .durable_functions .models .Task import TaskBase , TimerTask
4+ from azure .durable_functions .models .Task import LongTimerTask , TaskBase , TimerTask
55from azure .durable_functions .models .actions .CallHttpAction import CallHttpAction
66from azure .durable_functions .models .DurableHttpRequest import DurableHttpRequest
77from azure .durable_functions .models .actions .CallSubOrchestratorWithRetryAction import \
2626from uuid import UUID , uuid5 , NAMESPACE_URL , NAMESPACE_OID
2727from datetime import timezone
2828
29+ from azure .durable_functions .models .utils .json_utils import parse_datetime_attrib_timespan
30+
2931from .RetryOptions import RetryOptions
3032from .FunctionContext import FunctionContext
3133from .history import HistoryEvent , HistoryEventType
@@ -48,11 +50,19 @@ class DurableOrchestrationContext:
4850 # parameter names are as defined by JSON schema and do not conform to PEP8 naming conventions
4951 def __init__ (self ,
5052 history : List [Dict [Any , Any ]], instanceId : str , isReplaying : bool ,
51- parentInstanceId : str , input : Any = None , upperSchemaVersion : int = 0 , ** kwargs ):
53+ parentInstanceId : str , input : Any = None , upperSchemaVersion : int = 0 ,
54+ maximumShortTimerDuration = None , longRunningTimerIntervalDuration = None ,
55+ upperSchemaVersionNew = None , ** kwargs ):
5256 self ._histories : List [HistoryEvent ] = [HistoryEvent (** he ) for he in history ]
5357 self ._instance_id : str = instanceId
5458 self ._is_replaying : bool = isReplaying
5559 self ._parent_instance_id : str = parentInstanceId
60+ self ._maximum_short_timer_duration : datetime .timedelta
61+ if maximumShortTimerDuration is not None :
62+ self ._maximum_short_timer_duration = parse_datetime_attrib_timespan (maximumShortTimerDuration )
63+ self ._long_running_timer_interval_duration : datetime .timedelta
64+ if longRunningTimerIntervalDuration is not None :
65+ self ._long_running_timer_interval_duration = parse_datetime_attrib_timespan (longRunningTimerIntervalDuration )
5666 self ._custom_status : Any = None
5767 self ._new_uuid_counter : int = 0
5868 self ._sub_orchestrator_counter : int = 0
@@ -66,6 +76,8 @@ def __init__(self,
6676 self ._function_context : FunctionContext = FunctionContext (** kwargs )
6777 self ._sequence_number = 0
6878 self ._replay_schema = ReplaySchema (upperSchemaVersion )
79+ if upperSchemaVersionNew is not None and upperSchemaVersionNew > self ._replay_schema .value :
80+ self ._replay_schema = ReplaySchema (upperSchemaVersionNew )
6981
7082 self ._action_payload_v1 : List [List [Action ]] = []
7183 self ._action_payload_v2 : List [Action ] = []
@@ -471,6 +483,37 @@ def parent_instance_id(self) -> str:
471483 """
472484 return self ._parent_instance_id
473485
486+ @property
487+ def maximum_short_timer_duration (self ) -> datetime .timedelta :
488+ """Get the maximum duration for a short timer
489+
490+ The maximum length of a "short timer" is defined by the storage backend.
491+ Some storage backends have a maximum future date for scheduled tasks, and
492+ so for timers longer than this duration, we must simulate a long timer by
493+ waiting in chunks.
494+
495+ Returns
496+ -------
497+ str
498+ Maximum allowable duration for a short timer in Durable
499+ """
500+ return self ._maximum_short_timer_duration
501+
502+ @property
503+ def long_running_timer_interval_duration (self ) -> datetime .timedelta :
504+ """Get the interval for long timers.
505+
506+ When a timer is scheduled for a duration longer than the maximum short timer
507+ duration, the timer is set to run in chunks of time. The long running timer
508+ interval duration defines how long these chunks of time should be.
509+
510+ Returns
511+ -------
512+ str
513+ Duration for intervals of a long-running timer
514+ """
515+ return self ._long_running_timer_interval_duration
516+
474517 @property
475518 def current_utc_datetime (self ) -> datetime .datetime :
476519 """Get the current date/time.
@@ -532,10 +575,10 @@ def _record_fire_and_forget_action(self, action: Action):
532575 The action to append
533576 """
534577 new_action : Union [List [Action ], Action ]
535- if self ._replay_schema is ReplaySchema .V2 :
536- new_action = action
537- else :
578+ if self ._replay_schema is ReplaySchema .V1 :
538579 new_action = [action ]
580+ else :
581+ new_action = action
539582 self ._add_to_actions (new_action )
540583 self ._sequence_number += 1
541584
@@ -580,6 +623,20 @@ def create_timer(self, fire_at: datetime.datetime) -> TaskBase:
580623 TaskBase
581624 A Durable Timer Task that schedules the timer to wake up the activity
582625 """
626+ if self ._replay_schema .value >= ReplaySchema .V3 .value :
627+ if not self .maximum_short_timer_duration or not self .long_running_timer_interval_duration :
628+ raise Exception (
629+ "A framework-internal error was detected: replay schema version >= V3 is being used, " +
630+ "but one or more of the properties `maximumShortTimerDuration` and `longRunningTimerIntervalDuration` are not defined. " +
631+ "This is likely an issue with the Durable Functions Extension. " +
632+ "Please report this bug here: https://github.com/Azure/azure-functions-durable-js/issues\n " +
633+ f"maximumShortTimerDuration: { self .maximum_short_timer_duration } \n " +
634+ f"longRunningTimerIntervalDuration: { self .long_running_timer_interval_duration } "
635+ )
636+ if fire_at > self .current_utc_datetime + self .maximum_short_timer_duration :
637+ action = CreateTimerAction (fire_at )
638+ return LongTimerTask (None , action , self , None , self .maximum_short_timer_duration , self .long_running_timer_interval_duration )
639+
583640 action = CreateTimerAction (fire_at )
584641 task = self ._generate_task (action , task_constructor = TimerTask )
585642 return task
@@ -656,7 +713,7 @@ def _add_to_actions(self, action_repr: Union[List[Action], Action]):
656713
657714 if self ._replay_schema is ReplaySchema .V1 and isinstance (action_repr , list ):
658715 self ._action_payload_v1 .append (action_repr )
659- elif self ._replay_schema is ReplaySchema .V2 and isinstance (action_repr , Action ):
716+ elif self ._replay_schema . value >= ReplaySchema .V2 . value and isinstance (action_repr , Action ):
660717 self ._action_payload_v2 .append (action_repr )
661718 else :
662719 raise Exception (f"DF-internal exception: ActionRepr of signature { type (action_repr )} "
0 commit comments