Skip to content

Commit 793fb14

Browse files
TsuyoshiUshioTsuyoshi UshioCopilot
authored
Preserve activity tags across retries (#263)
Co-authored-by: Tsuyoshi Ushio <tsushi@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 46602d5 commit 793fb14

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
FIXED
11+
12+
- Activity tags, including `durabletask.displayName`, are now preserved across
13+
retry attempts.
14+
1015
## v1.10.0
1116

1217
ADDED

durabletask/worker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2590,14 +2590,17 @@ def _cancel_timer() -> None:
25902590
if not timer_task._retryable_parent._is_sub_orch: # pyright: ignore[reportPrivateUsage]
25912591
cur_task = activity_action.scheduleTask
25922592
instance_id = None
2593+
tags = dict(cur_task.tags)
25932594
else:
25942595
cur_task = activity_action.createSubOrchestration
25952596
instance_id = cur_task.instanceId
2597+
tags = None
25962598
ctx.call_activity_function_helper(
25972599
id=activity_action.id,
25982600
activity_function=cur_task.name,
25992601
input=cur_task.input.value,
26002602
retry_policy=timer_task._retryable_parent._retry_policy, # pyright: ignore[reportPrivateUsage]
2603+
tags=tags,
26012604
is_sub_orch=timer_task._retryable_parent._is_sub_orch, # pyright: ignore[reportPrivateUsage]
26022605
instance_id=instance_id,
26032606
fn_task=timer_task._retryable_parent, # pyright: ignore[reportPrivateUsage]

tests/durabletask/test_orchestration_executor.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,65 @@ def orchestrator(ctx: task.OrchestrationContext, orchestrator_input):
933933
assert actions[-1].id == 7
934934

935935

936+
def test_activity_retry_preserves_tags():
937+
"""Activity tags are preserved on every retry-generated schedule action."""
938+
939+
def dummy_activity(ctx, _):
940+
raise ValueError("Kah-BOOOOM!!!")
941+
942+
tags = {
943+
"durabletask.displayName": "reserve_inventory",
944+
"custom": "value",
945+
}
946+
947+
def orchestrator(ctx: task.OrchestrationContext, orchestrator_input):
948+
return (yield ctx.call_activity(
949+
dummy_activity,
950+
retry_policy=task.RetryPolicy(
951+
first_retry_interval=timedelta(seconds=1),
952+
max_number_of_attempts=3,
953+
),
954+
input=orchestrator_input,
955+
tags=tags,
956+
))
957+
958+
registry = worker._Registry()
959+
name = registry.add_orchestrator(orchestrator)
960+
current_timestamp = datetime.utcnow()
961+
old_events = [
962+
helpers.new_orchestrator_started_event(timestamp=current_timestamp),
963+
helpers.new_execution_started_event(name, TEST_INSTANCE_ID, encoded_input=None),
964+
helpers.new_task_scheduled_event(1, task.get_name(dummy_activity)),
965+
]
966+
967+
for _ in range(2):
968+
failed_events = [
969+
helpers.new_orchestrator_started_event(timestamp=current_timestamp),
970+
helpers.new_task_failed_event(1, ValueError("Kah-BOOOOM!!!")),
971+
]
972+
executor = worker._OrchestrationExecutor(registry, TEST_LOGGER, JsonDataConverter())
973+
result = executor.execute(TEST_INSTANCE_ID, old_events, failed_events)
974+
timer_action = next(action for action in result.actions if action.HasField("createTimer"))
975+
976+
old_events += failed_events
977+
current_timestamp = timer_action.createTimer.fireAt.ToDatetime()
978+
timer_events = [
979+
helpers.new_orchestrator_started_event(current_timestamp),
980+
helpers.new_timer_fired_event(timer_action.id, current_timestamp),
981+
]
982+
executor = worker._OrchestrationExecutor(registry, TEST_LOGGER, JsonDataConverter())
983+
result = executor.execute(TEST_INSTANCE_ID, old_events, timer_events)
984+
retry_actions = [
985+
action.scheduleTask
986+
for action in result.actions
987+
if action.HasField("scheduleTask")
988+
]
989+
990+
assert len(retry_actions) == 1
991+
assert dict(retry_actions[0].tags) == tags
992+
old_events += timer_events
993+
994+
936995
def test_activity_retry_without_max_retry_interval():
937996
"""Tests that retry logic works correctly when max_retry_interval is not set.
938997

0 commit comments

Comments
 (0)