Merge branch 's3-async-tmux' into s3-followups

This commit is contained in:
2026-10-09 15:21:57 +07:00
5 changed files with 206 additions and 57 deletions
+13 -1
View File
@@ -134,12 +134,24 @@ describe("a member's panes on the member's own tmux server", () => {
assert.ok(calls.every((a) => a[0] !== "-S"));
});
it("lists sessions across every server, believing a member's only about their own", () => {
it("lists sessions across every server, believing a member's only about their own", async () => {
const orig = runAs.membersWithTmux;
runAs.membersWithTmux = () => [ACCT];
try {
// A member's list is async: until it lands their panes are UNKNOWN.
const cold = tmux.liveSessions();
assert.deepEqual([...cold].sort(), [HOST, "other"].sort());
assert.equal(cold.has(MEMBER), true, "unknown, kept");
assert.equal(cold.has("agenthub-lane-9"), false);
assert.deepEqual(tmux.listSessions("agenthub-lane-"), [HOST]);
await tmux.__settle();
assert.deepEqual([...tmux.liveSessions()].sort(), [MEMBER, HOST, "other"].sort());
assert.deepEqual(tmux.listSessions("agenthub-lane-").sort(), [HOST, MEMBER].sort());
// A session this process ends or makes is in the next answer at once.
tmux.killSession(MEMBER);
assert.deepEqual(tmux.listSessions("agenthub-lane-"), [HOST]);
tmux.newSession({ name: MEMBER, cwd: HOME, argv: ["bash"], env: {} });
assert.deepEqual(tmux.listSessions("agenthub-lane-").sort(), [HOST, MEMBER].sort());
} finally {
runAs.membersWithTmux = orig;
}
+47 -17
View File
@@ -237,25 +237,46 @@ describe("a member's stopped tmux server or planted FIFO", () => {
// sudo that never returns: a tmux client talking to a SIGSTOPped server.
const bin = path.join(TMP, "fakebin");
fs.mkdirSync(bin, { recursive: true });
fs.writeFileSync(path.join(bin, "sudo"), "#!/bin/sh\nexec sleep 60\n", { mode: 0o755 });
const spawned = path.join(TMP, "sudo-spawns");
fs.rmSync(spawned, { force: true });
fs.writeFileSync(path.join(bin, "sudo"), `#!/bin/sh\necho x >> '${spawned}'\nexec sleep 60\n`, {
mode: 0o755,
});
try {
// Polls like the heal beat / GET /api/lanes for 4 s — past the 3 s
// timeout — while timing every poll and the event loop's worst stall.
const child = inChild(
`const runAs = require(${JSON.stringify(`${LIB}/run-as`)});
const tmux = require(${JSON.stringify(`${LIB}/tmux`)});
const { monitorEventLoopDelay } = require("node:perf_hooks");
const proj = ${JSON.stringify(path.join(MIA, "proj"))};
const time = (f) => { const t = Date.now(); try { f(); return { ms: Date.now() - t }; }
catch (e) { return { code: e.code, ms: Date.now() - t }; } };
let live;
const first = time(() => (live = tmux.liveSessions()));
const again = time(() => tmux.liveSessions());
process.stdout.write(JSON.stringify({
isSet: live instanceof Set,
keeps: live.has("agenthub-lane-${lane}"),
other: live.has("agenthub-lane-999999"),
first, again,
git: time(() => runAs.execIn(proj, "git", ["status"])),
write: time(() => runAs.writeAs(runAs.accountForPath(proj), proj + "/f.txt", "", "x")),
}));`,
(async () => {
runAs.accountForSession("agenthub-lane-${lane}"); // opens the DB: not what is timed
const loop = monitorEventLoopDelay({ resolution: 10 });
loop.enable();
let live;
const first = time(() => (live = tmux.liveSessions()));
let slowest = 0;
const beat = setInterval(() => {
slowest = Math.max(slowest, time(() => tmux.liveSessions()).ms);
}, 50);
await new Promise((r) => setTimeout(r, 4000));
clearInterval(beat);
loop.disable();
const after = tmux.liveSessions();
process.stdout.write(JSON.stringify({
isSet: live instanceof Set,
keeps: live.has("agenthub-lane-${lane}"),
other: live.has("agenthub-lane-999999"),
keepsAfter: after.has("agenthub-lane-${lane}"),
first, slowest, stallMs: loop.max / 1e6,
sudos: require("node:fs").readFileSync(${JSON.stringify(spawned)}, "utf8").trim().split("\\n").length,
git: time(() => runAs.execIn(proj, "git", ["status"])),
write: time(() => runAs.writeAs(runAs.accountForPath(proj), proj + "/f.txt", "", "x")),
}));
})();`,
{ PATH: `${bin}:${process.env.PATH}` }
);
assert.equal(child.signal, null, "nothing hung");
@@ -265,8 +286,14 @@ describe("a member's stopped tmux server or planted FIFO", () => {
assert.equal(r.isSet, true, "the host's answer still counts");
assert.equal(r.keeps, true);
assert.equal(r.other, false);
assert.ok(r.first.ms < 10_000, `first list: ${r.first.ms} ms`);
assert.ok(r.again.ms < 500, `backed off: ${r.again.ms} ms`);
assert.equal(r.keepsAfter, true, "still unknown once the hung list was killed");
// Listing a member never waits for them: no poll, no event-loop turn
// takes anything like the 3 s a hung sudo is allowed.
assert.ok(r.first.ms < 100, `first list: ${r.first.ms} ms`);
assert.ok(r.slowest < 100, `slowest poll: ${r.slowest} ms`);
assert.ok(r.stallMs < 500, `event loop stalled ${r.stallMs} ms`);
// ONE sudo, killed at the timeout, then backed off — not one per poll.
assert.equal(r.sudos, 1);
assert.equal(r.git.code, "ETIMEDOUT");
assert.equal(r.write.code, "ETIMEDOUT");
} finally {
@@ -505,9 +532,12 @@ describe("who a pane runs as", () => {
assert.throws(() => auth.assignWorkspace(lane, "ana"), { code: "EOWNERFOLDER" });
// One that got there anyway (assigned before this rule, or a demoted admin's).
const theirs = addLane(path.join(HOST, "x"), "mia");
assert.throws(() => pty.spawnRun({ laneId: theirs, cwd: path.join(HOST, "x"), tool: "claude" }), {
code: "EOWNERFOLDER",
});
assert.throws(
() => pty.spawnRun({ laneId: theirs, cwd: path.join(HOST, "x"), tool: "claude" }),
{
code: "EOWNERFOLDER",
}
);
assert.throws(() => pty.spawnShell({ laneId: theirs, cwd: path.join(HOST, "x") }), {
code: "EOWNERFOLDER",
});