Skip to content

Commit d9d790b

Browse files
cocolordTRAE CLI
andcommitted
fix(runtime): retry transient startup exits
Let retry-safe Effect runtime requests spend their existing second attempt when a managed runtime exits before readiness. Preserve immediate typed startup diagnostics for non-retry-safe calls and bound startup attempts to two. Signed-off-by: lusendong.6789 <lusendong.6789@bytedance.com> Co-authored-by: TRAE CLI <noreply@bytedance.com>
1 parent ff83a4d commit d9d790b

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

‎loopx/control_plane/effect_runtime.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,14 @@ def effect_runtime_request(
608608
for attempt in range(2 if retry_safe else 1):
609609
info = _read_info(info_path, fingerprint=fingerprint)
610610
if info is None:
611-
info = _start_runtime(fingerprint=fingerprint, info_path=info_path)
611+
try:
612+
info = _start_runtime(fingerprint=fingerprint, info_path=info_path)
613+
except EffectRuntimeStartupError as exc:
614+
if attempt == 0 and retry_safe:
615+
last_error = exc
616+
info_path.unlink(missing_ok=True)
617+
continue
618+
raise
612619
try:
613620
return _request_with_info(
614621
info,

‎tests/control_plane/test_effect_runtime_integration.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,72 @@ def popen(*_args, **kwargs):
491491
assert launch["close_fds"] is True
492492

493493

494+
@pytest.mark.parametrize(
495+
("retry_safe", "successful_attempt", "expected_attempts"),
496+
[
497+
(False, None, 1),
498+
(True, None, 2),
499+
(True, 2, 2),
500+
],
501+
)
502+
def test_request_startup_retry_boundary(
503+
tmp_path: Path,
504+
monkeypatch,
505+
retry_safe: bool,
506+
successful_attempt: int | None,
507+
expected_attempts: int,
508+
) -> None:
509+
runtime_dir = tmp_path / "runtime"
510+
monkeypatch.setattr(effect_runtime, "_runtime_dir", lambda: runtime_dir)
511+
attempts = {"count": 0}
512+
ready_info = {
513+
"schema_version": effect_runtime.EFFECT_RUNTIME_INFO_SCHEMA_VERSION,
514+
"fingerprint": effect_runtime._runtime_fingerprint(),
515+
"pid": os.getpid(),
516+
"host": "127.0.0.1",
517+
"port": 1,
518+
"token": "fixture-token",
519+
}
520+
521+
def start_runtime(*, fingerprint: str, info_path: Path):
522+
attempts["count"] += 1
523+
assert fingerprint == ready_info["fingerprint"]
524+
assert info_path.parent == runtime_dir
525+
if attempts["count"] == successful_attempt:
526+
return ready_info
527+
raise effect_runtime.EffectRuntimeStartupError(
528+
"runtime exited before ready",
529+
diagnostic_code="runtime_exited_before_ready",
530+
)
531+
532+
monkeypatch.setattr(effect_runtime, "_start_runtime", start_runtime)
533+
monkeypatch.setattr(
534+
effect_runtime,
535+
"_request_with_info",
536+
lambda info, **_kwargs: {"result": {"pid": info["pid"]}},
537+
)
538+
539+
if successful_attempt is not None:
540+
assert effect_runtime.effect_runtime_result(
541+
"runtime.ping",
542+
{},
543+
retry_safe=retry_safe,
544+
) == {"pid": os.getpid()}
545+
else:
546+
with pytest.raises(
547+
effect_runtime.EffectRuntimeStartupError,
548+
match="runtime exited before ready",
549+
) as exc_info:
550+
effect_runtime.effect_runtime_result(
551+
"turn_journal.write",
552+
{},
553+
retry_safe=retry_safe,
554+
)
555+
assert exc_info.value.diagnostic_code == "runtime_exited_before_ready"
556+
557+
assert attempts["count"] == expected_attempts
558+
559+
494560
def test_managed_runtime_releases_memory_after_idle_timeout(
495561
tmp_path: Path,
496562
monkeypatch,

0 commit comments

Comments
 (0)