-
Notifications
You must be signed in to change notification settings - Fork 64
Enable replay V2 #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable replay V2 #291
Changes from all commits
1a1bf52
c04b14d
0b4d70b
6ae6302
b566c0a
e881b1d
767254c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from enum import Enum | ||
|
||
|
||
class ReplaySchema(Enum): | ||
"""Enum representing the ReplaySchemas supported by this SDK version.""" | ||
|
||
V1 = 0 | ||
V2 = 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from typing import Dict, Union | ||
|
||
from .Action import Action | ||
from ..utils.json_utils import add_attrib | ||
from typing import List | ||
from abc import abstractmethod | ||
|
||
|
||
class CompoundAction(Action): | ||
"""Defines the structure of the WhenAll Action object. | ||
|
||
Provides the information needed by the durable extension to be able to invoke WhenAll tasks. | ||
""" | ||
|
||
def __init__(self, compoundTasks: List[Action]): | ||
self.compound_actions = list(map(lambda x: x.to_json(), compoundTasks)) | ||
|
||
@property | ||
@abstractmethod | ||
def action_type(self) -> int: | ||
"""Get this object's action type as an integer.""" | ||
... | ||
|
||
def to_json(self) -> Dict[str, Union[str, int]]: | ||
"""Convert object into a json dictionary. | ||
|
||
Returns | ||
------- | ||
Dict[str, Union[str, int]] | ||
The instance of the class converted into a json dictionary | ||
""" | ||
json_dict: Dict[str, Union[str, int]] = {} | ||
add_attrib(json_dict, self, 'action_type', 'actionType') | ||
add_attrib(json_dict, self, 'compound_actions', 'compoundActions') | ||
return json_dict |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from .ActionType import ActionType | ||
from azure.durable_functions.models.actions.CompoundAction import CompoundAction | ||
|
||
|
||
class WhenAllAction(CompoundAction): | ||
"""Defines the structure of the WhenAll Action object. | ||
|
||
Provides the information needed by the durable extension to be able to invoke WhenAll tasks. | ||
""" | ||
|
||
@property | ||
def action_type(self) -> int: | ||
"""Get the type of action this class represents.""" | ||
return ActionType.WHEN_ALL |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from azure.durable_functions.models.actions.CompoundAction import CompoundAction | ||
from .ActionType import ActionType | ||
|
||
|
||
class WhenAnyAction(CompoundAction): | ||
"""Defines the structure of the WhenAll Action object. | ||
|
||
Provides the information needed by the durable extension to be able to invoke WhenAll tasks. | ||
""" | ||
|
||
@property | ||
def action_type(self) -> int: | ||
"""Get the type of action this class represents.""" | ||
return ActionType.WHEN_ANY |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
""" | ||
from typing import Callable, Iterator, Any, Generator | ||
|
||
from azure.durable_functions.models.ReplaySchema import ReplaySchema | ||
|
||
from .models import ( | ||
DurableOrchestrationContext, | ||
Task, | ||
|
@@ -55,6 +57,7 @@ def handle(self, context: DurableOrchestrationContext): | |
# `fn_output` is the return value instead of a generator | ||
if not isinstance(fn_output, Iterator): | ||
orchestration_state = OrchestratorState( | ||
replay_schema=self.durable_context._replay_schema, | ||
is_done=True, | ||
output=fn_output, | ||
actions=self.durable_context.actions, | ||
|
@@ -75,6 +78,7 @@ def handle(self, context: DurableOrchestrationContext): | |
# `will_continue_as_new` essentially "tracks" | ||
# whether or not the orchestration is done. | ||
orchestration_state = OrchestratorState( | ||
replay_schema=self.durable_context._replay_schema, | ||
is_done=self.durable_context.will_continue_as_new, | ||
output=None, | ||
actions=self.durable_context.actions, | ||
|
@@ -95,13 +99,15 @@ def handle(self, context: DurableOrchestrationContext): | |
|
||
except StopIteration as sie: | ||
orchestration_state = OrchestratorState( | ||
replay_schema=self.durable_context._replay_schema, | ||
is_done=True, | ||
output=sie.value, | ||
actions=self.durable_context.actions, | ||
custom_status=self.durable_context.custom_status) | ||
except Exception as e: | ||
exception_str = str(e) | ||
orchestration_state = OrchestratorState( | ||
replay_schema=self.durable_context._replay_schema, | ||
is_done=False, | ||
output=None, # Should have no output, after generation range | ||
actions=self.durable_context.actions, | ||
|
@@ -135,12 +141,17 @@ def _add_to_actions(self, generation_state): | |
if self.durable_context.will_continue_as_new: | ||
return | ||
if not generation_state._is_yielded: | ||
if (isinstance(generation_state, Task) | ||
and hasattr(generation_state, "action")): | ||
self.durable_context.actions.append([generation_state.action]) | ||
elif (isinstance(generation_state, TaskSet) | ||
and hasattr(generation_state, "actions")): | ||
self.durable_context.actions.append(generation_state.actions) | ||
if isinstance(generation_state, Task): | ||
if self.durable_context._replay_schema == ReplaySchema.V1: | ||
self.durable_context.actions.append([generation_state.action]) | ||
else: | ||
self.durable_context.actions[0].append(generation_state.action) | ||
ConnorMcMahon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
elif isinstance(generation_state, TaskSet): | ||
if self.durable_context._replay_schema == ReplaySchema.V1: | ||
self.durable_context.actions.append(generation_state.actions) | ||
else: | ||
self.durable_context.actions[0].append(generation_state.actions) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we even generate TaskSet even more in V2? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do! It's just its corresponding I did originally think of removing the |
||
generation_state._is_yielded = True | ||
|
||
def _update_timestamp(self): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from azure.durable_functions.models.actions.WhenAllAction import WhenAllAction | ||
from azure.durable_functions.models.ReplaySchema import ReplaySchema | ||
from datetime import datetime | ||
from typing import List, Optional, Any | ||
|
||
|
@@ -6,7 +8,7 @@ | |
from ..models.actions import Action | ||
|
||
|
||
def task_all(tasks: List[Task]): | ||
def task_all(tasks: List[Task], replay_schema: ReplaySchema): | ||
"""Determine the state of scheduling the activities for execution with retry options. | ||
|
||
Parameters | ||
|
@@ -33,7 +35,10 @@ def task_all(tasks: List[Task]): | |
for task in tasks: | ||
# Add actions and results | ||
if isinstance(task, TaskSet): | ||
actions.extend(task.actions) | ||
if replay_schema == ReplaySchema.V1: | ||
actions.extend(task.actions) | ||
else: | ||
actions.append(task.actions) | ||
else: | ||
# We know it's an atomic Task | ||
actions.append(task.action) | ||
|
@@ -62,6 +67,9 @@ def task_all(tasks: List[Task]): | |
results = [] | ||
end_time = None | ||
|
||
if replay_schema == ReplaySchema.V2: | ||
actions = WhenAllAction(actions) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're asking why the single action The reason is that, in the case of In general, a lot of this PR is designed to modify as little of the existing logic as possible, as I worried that too big of a refactor would risk introducing bugs. That being said, in this case we're talking about a specific variable name, so I don't mind just replacing the name with something like "action_payload" which works for V1 and V2. |
||
|
||
# Construct TaskSet | ||
taskset = TaskSet( | ||
is_completed=is_completed, | ||
|
Uh oh!
There was an error while loading. Please reload this page.