Skip to content

Commit 535e458

Browse files
committed
Address latest PR review comments from berndverst
1 parent 853f169 commit 535e458

5 files changed

Lines changed: 28 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ ADDED
3232
to 3 times with exponential backoff (15s/30s/60s), retries failed batches
3333
up to 3 times, caps in-flight exports via `max_parallel_exports`
3434
(default 32), continues-as-new every 5 page cycles to bound orchestrator
35-
history, and re-fetches entity state at the top of every page loop so
35+
history while preserving cumulative totals across continue-as-new segments,
36+
and re-fetches entity state at the top of every page loop so
3637
external delete or mark-failed signals stop the orchestrator cleanly.
38+
Empty-page BATCH checkpoints no longer reset the persisted resume cursor,
39+
and duplicate `mark_failed` signals are now idempotent no-ops when a job
40+
is already failed to reduce transition-noise logs.
3741
`delete_job` actively tears the job down: it clears the entity state,
3842
terminates the driving orchestrator, waits briefly for it to settle, and
3943
purges its orchestration history so a re-created job with the same ID

durabletask/extensions/history_export/entity.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ def mark_failed(
305305
assert_valid_transition(
306306
self.OP_MARK_FAILED, state.status, ExportJobStatus.FAILED, job_id=job_id,
307307
)
308+
if state.status is ExportJobStatus.FAILED:
309+
# Idempotent no-op: if the job is already FAILED, do not
310+
# mutate persisted state (especially ``last_error``) and
311+
# avoid noisy invalid-transition logs from duplicate
312+
# best-effort mark_failed signals.
313+
return state.to_dict()
308314
reason = ""
309315
if payload is not None:
310316
reason = str(payload.get("reason", ""))

durabletask/extensions/history_export/orchestrator.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,22 @@ def export_job_orchestrator(
145145
config = ExportJobConfiguration.from_dict(config_mapping)
146146
initial_checkpoint = input.get("checkpoint") or {"last_instance_key": None}
147147
processed_cycles = int(input.get("processed_cycles", 0))
148+
input_totals_raw = input.get("totals")
149+
input_totals: Mapping[str, Any] = (
150+
cast("Mapping[str, Any]", input_totals_raw)
151+
if isinstance(input_totals_raw, Mapping)
152+
else {}
153+
)
148154

149155
entity_id = task.EntityInstanceId(ENTITY_NAME, job_id)
150156
runtime_status_names = [s.name for s in config.filter.effective_runtime_status()]
151157
continuation_token: str | None = initial_checkpoint.get("last_instance_key")
152158

153-
totals: dict[str, int] = {"scanned": 0, "exported": 0, "failed": 0}
159+
totals: dict[str, int] = {
160+
"scanned": int(input_totals.get("scanned", 0)),
161+
"exported": int(input_totals.get("exported", 0)),
162+
"failed": int(input_totals.get("failed", 0)),
163+
}
154164

155165
try:
156166
while True:
@@ -160,6 +170,7 @@ def export_job_orchestrator(
160170
"job_id": job_id,
161171
"config": dict(config_mapping),
162172
"checkpoint": {"last_instance_key": continuation_token},
173+
"totals": dict(totals),
163174
"processed_cycles": 0,
164175
})
165176
return None
@@ -227,7 +238,6 @@ def export_job_orchestrator(
227238
"scanned_delta": 0,
228239
"exported_delta": 0,
229240
"failed_delta": 0,
230-
"last_instance_key": None,
231241
},
232242
)
233243
break

durabletask/extensions/history_export/transitions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
("mark_completed", ExportJobStatus.ACTIVE): frozenset({ExportJobStatus.COMPLETED}),
4747

4848
("mark_failed", ExportJobStatus.ACTIVE): frozenset({ExportJobStatus.FAILED}),
49+
("mark_failed", ExportJobStatus.FAILED): frozenset({ExportJobStatus.FAILED}),
4950
}
5051

5152

tests/durabletask/extensions/history_export/test_transitions_and_exceptions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,13 @@ def test_mark_completed_requires_active(self) -> None:
5757
"mark_completed", ExportJobStatus.FAILED, ExportJobStatus.COMPLETED,
5858
)
5959

60-
def test_mark_failed_allowed_from_active(self) -> None:
60+
def test_mark_failed_allowed_from_active_or_failed(self) -> None:
6161
assert is_valid_transition(
6262
"mark_failed", ExportJobStatus.ACTIVE, ExportJobStatus.FAILED,
6363
)
64+
assert is_valid_transition(
65+
"mark_failed", ExportJobStatus.FAILED, ExportJobStatus.FAILED,
66+
)
6467

6568
def test_unknown_operation_rejected(self) -> None:
6669
assert not is_valid_transition(

0 commit comments

Comments
 (0)