Skip to content

Commit 5be24a6

Browse files
committed
fix(tangle-cli): run retry handoff after recovered submit
Assisted-By: devx/0c001c3a-79d6-4fd4-9ba8-4012e253d1e2
1 parent 8c8ff7c commit 5be24a6

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,8 @@ def _run_body_factory(
17321732
attempt=attempt,
17331733
context=context,
17341734
)
1735+
if attempt > 1:
1736+
self.hooks.after_retry_submit(context)
17351737
else:
17361738
try:
17371739
response = self.submit_prepared_body(

tests/test_pipeline_runs_cli.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,69 @@ def pipeline_runs_list(self, **kwargs: Any) -> dict[str, Any]:
18021802
assert len(client.list_calls) == 4
18031803

18041804

1805+
1806+
def test_pipeline_runs_recovered_retry_runs_after_retry_submit_hook(tmp_path: Path, monkeypatch) -> None:
1807+
monkeypatch.setattr("tangle_cli.pipeline_run_manager.time.sleep", lambda _seconds: None)
1808+
pipeline_path = tmp_path / "pipeline.yaml"
1809+
events: list[tuple[str, int, str | None]] = []
1810+
1811+
class DynamicTimeHooks(PipelineRunnerHooks):
1812+
def __init__(self) -> None:
1813+
super().__init__()
1814+
self.prepare_run_arguments_calls = 0
1815+
1816+
def read_pipeline_yaml(self, pipeline_path):
1817+
return {
1818+
"name": "template-source",
1819+
"inputs": [{"name": "exec_time", "type": "String"}],
1820+
"metadata": {"annotations": {"run-name-template": "run-${arguments.exec_time}"}},
1821+
"implementation": {"graph": {"tasks": {}}},
1822+
}
1823+
1824+
def prepare_run_arguments(self, pipeline_spec, run_args):
1825+
del pipeline_spec
1826+
self.prepare_run_arguments_calls += 1
1827+
updated = dict(run_args or {})
1828+
updated["exec_time"] = f"time-{self.prepare_run_arguments_calls}"
1829+
return updated
1830+
1831+
def before_retry(self, context, error, *, next_attempt):
1832+
del error
1833+
events.append(("before_retry", next_attempt, context.run_id))
1834+
1835+
def after_retry_submit(self, context):
1836+
events.append(("after_retry_submit", context.attempt, context.run_id))
1837+
1838+
class RecoverOnRetryClient(FakeClient):
1839+
def pipeline_runs_create(self, body: Any = None) -> dict[str, Any]:
1840+
self.created.append(copy.deepcopy(body))
1841+
raise TimeoutError("submit timed out")
1842+
1843+
def pipeline_runs_list(self, **kwargs: Any) -> dict[str, Any]:
1844+
self.list_calls.append(kwargs)
1845+
if len(self.list_calls) <= 2:
1846+
return {"pipeline_runs": [], "next_page_token": None}
1847+
return {
1848+
"pipeline_runs": [
1849+
{"id": "run-created", "root_execution_id": "exec-created", "pipeline_name": "run-time-1"}
1850+
],
1851+
"next_page_token": None,
1852+
}
1853+
1854+
hooks = DynamicTimeHooks()
1855+
client = RecoverOnRetryClient()
1856+
manager = PipelineRunner(client=client, hooks=hooks)
1857+
1858+
result = manager.run_pipeline(pipeline_path, hydrate=False, max_attempts=2)
1859+
1860+
assert result["response"]["id"] == "run-created"
1861+
assert result["context"].attempt == 2
1862+
assert result["context"].metadata["recovered_after_submit_error"] is True
1863+
assert hooks.prepare_run_arguments_calls == 1
1864+
assert len(client.created) == 1
1865+
assert events == [("before_retry", 2, None), ("after_retry_submit", 2, "run-created")]
1866+
1867+
18051868
def test_pipeline_runs_run_pipeline_spec_uses_in_memory_spec_lifecycle() -> None:
18061869
events = []
18071870
spec = {

0 commit comments

Comments
 (0)