Merge branch 's3-async-tmux' into s3-followups
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user