Skip to content

Commit c392288

Browse files
authored
Allow orchestrators to try-catch entity timeouts (#328)
1 parent 41b7d88 commit c392288

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

azure/durable_functions/models/entities/ResponseMessage.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Dict, Any
2+
import json
23

34

45
class ResponseMessage:
@@ -17,6 +18,13 @@ def __init__(self, result: str, is_exception: bool = False):
1718
result: str
1819
The result provided by the entity
1920
"""
21+
# The time-out case seems to be handled by the Functions-Host, so
22+
# its result is not doubly-serialized. In this branch, we compensate
23+
# for this by re-serializing the payload.
24+
if result.strip().startswith("Timeout value of"):
25+
is_exception = True
26+
result = json.dumps(result)
27+
2028
self.result = result
2129
self.is_exception = is_exception
2230
# TODO: JS has an additional exceptionType field, but does not use it

tests/orchestrator/test_entity.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,11 @@ def add_signal_entity_action(state: OrchestratorState, id_: df.EntityId, op: str
192192
state.actions.append([action])
193193

194194
def add_call_entity_completed_events(
195-
context_builder: ContextBuilder, op: str, instance_id=str, input_=None, event_id=0, is_error=False):
195+
context_builder: ContextBuilder, op: str, instance_id=str, input_=None, event_id=0, is_error=False, literal_input=False):
196196
context_builder.add_event_sent_event(instance_id, event_id)
197197
context_builder.add_orchestrator_completed_event()
198198
context_builder.add_orchestrator_started_event()
199-
context_builder.add_event_raised_event(name="0000", id_=0, input_=input_, is_entity=True, is_error=is_error)
199+
context_builder.add_event_raised_event(name="0000", id_=0, input_=input_, is_entity=True, is_error=is_error, literal_input=literal_input)
200200

201201
def test_call_entity_sent():
202202
context_builder = ContextBuilder('test_simple_function')
@@ -289,4 +289,30 @@ def test_call_entity_catch_exception():
289289
expected_state._is_done = True
290290
expected = expected_state.to_json()
291291

292+
assert_orchestration_state_equals(expected, result)
293+
294+
def test_timeout_entity_catch_exception():
295+
entityId = df.EntityId("Counter", "myCounter")
296+
context_builder = ContextBuilder('catch timeout exceptions')
297+
add_call_entity_completed_events(
298+
context_builder,
299+
"add",
300+
df.EntityId.get_scheduler_id(entityId),
301+
input_="Timeout value of 00:02:00 was exceeded by function: Functions.SlowEntity.",
302+
event_id=0,
303+
is_error=False,
304+
literal_input=True
305+
)
306+
307+
result = get_orchestration_state_result(
308+
context_builder, generator_function_catch_entity_exception)
309+
310+
expected_state = base_expected_state(
311+
"Exception thrown"
312+
)
313+
314+
add_call_entity_action(expected_state, entityId, "add", 3)
315+
expected_state._is_done = True
316+
expected = expected_state.to_json()
317+
292318
assert_orchestration_state_equals(expected, result)

tests/test_utils/ContextBuilder.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,17 @@ def add_execution_started_event(
125125
event.Input = input_
126126
self.history_events.append(event)
127127

128-
def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False):
128+
def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False, literal_input=False):
129129
event = self.get_base_event(HistoryEventType.EVENT_RAISED, id_=id_, timestamp=timestamp)
130130
event.Name = name
131131
if is_entity:
132132
if is_error:
133133
event.Input = json.dumps({ "result": json.dumps(input_), "exceptionType": "True" })
134134
else:
135-
event.Input = json.dumps({ "result": json.dumps(input_) })
135+
if literal_input:
136+
event.Input = json.dumps({ "result": input_ })
137+
else:
138+
event.Input = json.dumps({ "result": json.dumps(input_) })
136139
else:
137140
event.Input = input_
138141
# event.timestamp = timestamp

0 commit comments

Comments
 (0)