Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion automation/scripts/adaptability_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,7 @@ def _comments(source: str) -> list[tuple[int, int, str]]:
for token in tokenize.generate_tokens(io.StringIO(source).readline):
if token.type == tokenize.COMMENT:
out.append((token.start[0], token.start[1], token.string))
except (tokenize.TokenError, IndentationError, SyntaxError):
except tokenize.TokenError, IndentationError, SyntaxError:
for number, line in enumerate(source.splitlines(), start=1):
stripped = line.lstrip()
if stripped.startswith("#"):
Expand Down
9 changes: 9 additions & 0 deletions changelog.d/executor-stuck-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Fixed

- **The stuck-launcher executor test no longer fails on a slow CI runner.** The fake
launcher execs the harness, so the process the test keeps alive carried the job's
one-second `RLIMIT_CPU`; when the harness start spent that second, the kernel killed
the "stuck" launcher inside the grace window and `stuck` read 0. The test now gives
the run the host's full CPU cap, which a real `sandbox do` outside the sandbox always
has, and asserts that limit on the survivor, so it fails every time without the fix
(#12260).
2 changes: 1 addition & 1 deletion core/adaptability.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ def _comments(source: str) -> list[tuple[int, int, str]]:
for token in tokenize.generate_tokens(io.StringIO(source).readline):
if token.type == tokenize.COMMENT:
out.append((token.start[0], token.start[1], token.string))
except (tokenize.TokenError, IndentationError, SyntaxError):
except tokenize.TokenError, IndentationError, SyntaxError:
for number, line in enumerate(source.splitlines(), start=1):
stripped = line.lstrip()
if stripped.startswith("#"):
Expand Down
10 changes: 9 additions & 1 deletion tests/unit/agents/renderer/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import asyncio
import base64
import os
import resource
import signal
import stat
import time
Expand Down Expand Up @@ -148,13 +149,20 @@ async def test_a_launcher_that_outlives_its_kill_blocks_renders_until_it_exits(
executor = SandboxExecutor(replace(config, kill_grace_s=0.2))
monkeypatch.setattr(executor_module, "READER_GRACE_S", 0.2)
monkeypatch.setattr(executor_module.os, "killpg", lambda pid, sig: None) # the kill has no effect
# The fake launcher execs the harness, so the survivor carries the run's RLIMIT_CPU, which a real
# `sandbox do` on the host never does. With the one second of a one-second job, the kernel's SIGKILL
# ended the "stuck" launcher inside the grace window whenever the harness start (matplotlib, a fresh
# font cache) spent that second, as it did on a loaded CI runner (2026-10-23). The full host cap
# leaves the survivor nothing but the test's own kill to die of.
monkeypatch.setattr(executor_module, "run_cpu_seconds", lambda limit_s, timeout_s: limit_s)

run = await executor.run_theme(job(SLEEPER, timeout_s=1.0), "light")

assert run.timed_out and executor.stuck == 1
survivor = executor._stuck[0]
assert resource.prlimit(survivor.pid, resource.RLIMIT_CPU) == (config.rlimit_cpu_s,) * 2
with pytest.raises(Unavailable, match="stuck"):
await executor.run_theme(job(PLOT), "light")
survivor = executor._stuck[0]
os.kill(survivor.pid, signal.SIGKILL)
await survivor.wait()
assert executor.stuck == 0
Expand Down
Loading