From 754f6921260c9be3edc2e553008b13e37e5cc0dd Mon Sep 17 00:00:00 2001 From: shangzh0 <97216392+shangzh0@users.noreply.github.com> Date: Sun, 1 Nov 2026 01:59:50 +0800 Subject: [PATCH] perf(runtime): avoid renewing healthy leases at every proof check Signed-off-by: shangzh0 <97216392+shangzh0@users.noreply.github.com> --- docs/reference/local-delegation.md | 8 ++++ .../turn_driver/leased_host_process.ts | 29 ++++++----- .../control_plane/test_leased_host_process.py | 48 +++++++++++++++++++ 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/docs/reference/local-delegation.md b/docs/reference/local-delegation.md index 0b2ce2e6c8..cbb2b8ca8d 100644 --- a/docs/reference/local-delegation.md +++ b/docs/reference/local-delegation.md @@ -479,6 +479,11 @@ five minutes total. Execution and resume keep their original one-shot CLI path, including the existing leased Host supervisor's current-execution readback, renewal and nested-process cleanup. Lease-bearing commands never enter the preview worker; preview reuse grants no lease or execution authority. +The leased supervisor keeps current-execution checks at `min(30 s, remaining/2)`. +It renews only when the remaining lifetime is at most half the requested TTL, +then requires an advanced version and current readback. Healthy long-lived +leases therefore avoid a write at each proof check. Owner/key/epoch checks, +the last proved expiry, exact-intent retry and final proof still apply. This is an internal transport change, not a new capability setting, execution grant or UI source of truth. A one-shot CLI inspection still pays its original cold startup; a warm-service measurement is not evidence of a faster cold CLI. @@ -508,6 +513,9 @@ best-effort 进程树边界。 总寿命 5 分钟;执行和恢复沿用一次性 CLI,包括既有租约 Host 的当前执行读回、 续期和嵌套进程清理。带租约的命令不进入预检 worker,预检复用不授予租约或执行 权限。这不增加配置、授权或 UI 权威。 +租约监督器仍按 `min(30 秒, 剩余寿命/2)` 检查当前执行,只在剩余寿命不超过 +请求 TTL 的一半时续租,并要求版本前进及当前读回。因此长租约的普通证明检查 +不再每次写入;owner/key/epoch、最后已证明的到期时间、原意图重试及末次证明不变。 一次性 CLI 仍承担原来的冷启动,热服务的加速不能冒充冷 CLI 的加速。 首次验收读取或预检后的末次复读遇到权限存储不可用时,均返回同一类型化不可用 diff --git a/loopx/control_plane/turn_driver/leased_host_process.ts b/loopx/control_plane/turn_driver/leased_host_process.ts index ad778c4fe7..e0371a9c0b 100644 --- a/loopx/control_plane/turn_driver/leased_host_process.ts +++ b/loopx/control_plane/turn_driver/leased_host_process.ts @@ -107,20 +107,27 @@ export async function runLeasedHostProcess(request: HostProcessRequest, context: if (finished) return; renewalTimer = setTimeout(() => { pending = (async () => { - const argv = [...context.renew_argv, "--expected-version", String(leaseVersion(lease)), - "--ttl-seconds", String(context.ttl_seconds)]; try { - let renewed: unknown; - try { renewed = await cli(argv); } - catch (error) { - if (!(error instanceof LeaseTransportUnavailable) || controller.signal.aborted) throw error; - // One bounded same-intent recovery for a lost response. Never - // adopt a newer version by editing the rejected renewal request. - renewed = await cli(argv); + // Keep the existing revocation/proof cadence, but do not rewrite a + // long-lived lease on every observation. Renew in its latter half; + // this leaves the same retry/deadline supervision for short leases. + const deadline = parseIsoTimestamp(String(lease.expires_at)); + const renew = deadline === null || deadline.valueOf() - Date.now() <= context.ttl_seconds * 500; + if (renew) { + const argv = [...context.renew_argv, "--expected-version", String(leaseVersion(lease)), + "--ttl-seconds", String(context.ttl_seconds)]; + let renewed: unknown; + try { renewed = await cli(argv); } + catch (error) { + if (!(error instanceof LeaseTransportUnavailable) || controller.signal.aborted) throw error; + // One bounded same-intent recovery for a lost response. Never + // adopt a newer version by editing the rejected renewal request. + renewed = await cli(argv); + } + if (requireJsonObject(renewed, "delegated renewal").ok !== true) throw new LeaseSupervisionFailure("renewal_rejected"); } - if (requireJsonObject(renewed, "delegated renewal").ok !== true) throw new LeaseSupervisionFailure("renewal_rejected"); const observed = currentProof(await cli(context.read_argv)); - if (leaseVersion(observed) <= leaseVersion(lease)) throw new LeaseSupervisionFailure("renewal_not_advanced"); + if (renew && leaseVersion(observed) <= leaseVersion(lease)) throw new LeaseSupervisionFailure("renewal_not_advanced"); lease = observed; schedule(); } catch (error) { reject(error, "renewal"); } diff --git a/tests/control_plane/test_leased_host_process.py b/tests/control_plane/test_leased_host_process.py index 7e0ba2235b..ca050e118b 100644 --- a/tests/control_plane/test_leased_host_process.py +++ b/tests/control_plane/test_leased_host_process.py @@ -64,6 +64,54 @@ def command(*arguments): capture_output=True, text=True, timeout=30, check=True) +@pytest.mark.skipif(os.name == "nt", reason="POSIX owned process-group qualification") +@pytest.mark.parametrize("revoke", [False, True]) +def test_long_ttl_checks_authority_without_rewriting_lease(canonical_execution, tmp_path, revoke): + context, command, selected = canonical_execution + command("task-lease", "renew", *selected, "--owner", "worker", "--idempotency-key", + "original-execution", "--expected-version", "1", "--ttl-seconds", "2700") + context["lease"] = command("task-lease", "inspect", *selected)["lease"] + context["ttl_seconds"] = 2700 + version = context["lease"]["version"] + trace = tmp_path / "commands.jsonl" + relay = """import json,subprocess,sys +from pathlib import Path +with Path(sys.argv[1]).open('a') as stream: + stream.write(json.dumps({'phase':sys.argv[2]})+'\\n') +result=subprocess.run(sys.argv[3:],capture_output=True,text=True) +print(result.stdout,end='') +sys.exit(result.returncode) +""" + for field, phase in (("renew_argv", "renew"), ("read_argv", "read")): + context[field] = [sys.executable, "-c", relay, str(trace), phase, *context[field]] + released = [] + + def ready(_text): + if revoke and not released: + released.append(command("task-lease", "release", *selected, "--owner", "worker", + "--idempotency-key", "original-execution", "--expected-version", str(version))) + + started = time.monotonic() + observed = run_host_process([sys.executable, "-c", + "import time;print('ready',flush=True);time.sleep(33);print('finished')"], + project=tmp_path, input_text="", timeout_seconds=50, delegated_lease=context, on_stdout=ready) + phases = [json.loads(line)["phase"] for line in trace.read_text().splitlines()] + current = command("task-lease", "inspect", *selected)["lease"] + assert "renew" not in phases, "periodic proof must not rewrite a healthy long-lived lease" + assert phases.count("read") >= (2 if revoke else 3), phases + assert current["lease_epoch"] == context["lease"]["lease_epoch"] + assert current["idempotency_key"] == context["lease"]["idempotency_key"] + if revoke: + assert released[0]["ok"] is True + assert observed["outcome"] == "cancelled" and observed["output_complete"] is False + assert observed["lease_failure"] == {"reason": "lease_inactive", "boundary": "renewal"} + assert time.monotonic() - started < 45, "revocation must retain the existing proof cadence" + else: + assert observed["outcome"] == "exited" and observed["output_complete"] is True + assert "lease_failure" not in observed + assert current["version"] == version + + @pytest.mark.skipif(os.name == "nt", reason="POSIX owned process-group qualification") @pytest.mark.parametrize("fault", ["rejected", "hung", "lost_reply", "late_reply"]) def test_real_renewal_faults_keep_original_deadline_and_identity(canonical_execution, tmp_path, fault, record_property):