row 302 (#1809, Vikunja 588): a run threadIdle or landRelease released answers launcher-released on every call needing the claim until it ends, role.claim refuses it (Dewey D1), and the runner exits stopped without releasing again
This commit is contained in:
1 parent
f799e8b155
commit
64f6558ee4
5 files changed
+48
-20
No files matched your search
@@ -324,6 +324,14 @@ verb. It rechecks the thread and releases the run's claim in one call, and retur
|
||||
launcher's snapshot. The launcher signals the run only after a release, so a message sent
|
||||
after the snapshot gets a turn from the same run or waits as `pending` for the next one. It
|
||||
refuses `not-holder` for a run that doesn't hold that thread and `run-ended` for an ended run.
|
||||
After a release, and until the run ends, every call of the run's that needs the claim
|
||||
(`message.receive`, `state`, `message.read`, `message.send` and every other verb that checks
|
||||
the holder) refuses `launcher-released`, not `not-holder` (row 302). `role.claim` refuses it
|
||||
too, before the claim, so the run can't claim again. A call
|
||||
the broker answers after the release and before the signal is that stop, not a lost claim, so
|
||||
the runner exits 0. The launch-landing pass's `landRelease` (`packages/cli/README.md`) answers
|
||||
the same way for the role run it releases. Any other lost claim (a stop, a revoke, a clear, a
|
||||
limit, a newer holder) still refuses `not-holder`.
|
||||
|
||||
A message delivered to a thread run that then ended without reading it is stranded, as
|
||||
`stranded` (Agent state, below) describes for a role. It counts as neither `pending` nor
|
||||
|
||||
@@ -419,6 +419,10 @@ export class Broker {
|
||||
#hostRuns = new Map();
|
||||
// Row 125 (#1619, REQ-LIMIT-4): a run a limit stops, by business:run, until its next receive or state.
|
||||
#stops = new Map();
|
||||
// Row 302 (#1809): runs the launcher released, by business:run, until endLaunch. threadIdle and
|
||||
// landRelease release and then signal, so the run's own calls in between answer launcher-released,
|
||||
// not not-holder, and the runner exits stopped instead of as a lost session.
|
||||
#launcherReleased = new Set();
|
||||
#clock = 0;
|
||||
#processing;
|
||||
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
|
||||
@@ -1003,7 +1007,8 @@ export class Broker {
|
||||
if (s.reader) fail('read-only');
|
||||
if (s.human) fail('agent-required');
|
||||
const h = this.#own(s);
|
||||
if (h?.op !== 'claim' || (s.thread ? h.generation !== this.#runGeneration(s) : h.holder_run !== s.run)) fail('not-holder');
|
||||
if (h?.op !== 'claim' || (s.thread ? h.generation !== this.#runGeneration(s) : h.holder_run !== s.run))
|
||||
fail(this.#launcherReleased.has(s.business + ':' + s.run) ? 'launcher-released' : 'not-holder');
|
||||
return h;
|
||||
}
|
||||
// Row 264a (#1769, T2): the generation a thread run holds, its latest claim's in its session, or
|
||||
@@ -1053,6 +1058,7 @@ export class Broker {
|
||||
if (t?.holder !== record.run) fail('not-holder');
|
||||
if (t.pending || t.unread) return { released: false, pending: t.pending, unread: t.unread };
|
||||
this.#store.transaction(() => this.#claimEnd(session, this.#own(session), 'release', null));
|
||||
this.#launcherReleased.add(key);
|
||||
return { released: true };
|
||||
}
|
||||
// Row 271 (264d, #1776, D7 steps 3-5): the business's clears still in the launcher's hands, as of
|
||||
@@ -1307,6 +1313,7 @@ export class Broker {
|
||||
const exitCode = record.exitCode ?? null;
|
||||
if (exitCode !== null && !Number.isSafeInteger(exitCode)) fail('invalid-request');
|
||||
this.#ended.add(key);
|
||||
this.#launcherReleased.delete(key);
|
||||
for (const [cap, s] of this.#sessions)
|
||||
if (!s.human && !s.reader && s.business === record.business && s.run === record.run) this.#sessions.delete(cap);
|
||||
return this.#store.transaction(() => {
|
||||
@@ -4161,6 +4168,7 @@ export class Broker {
|
||||
);
|
||||
if (unread) return { released: false, unread };
|
||||
this.#store.transaction(() => this.#claimEnd(session, h, 'release', null, null, 'land-relaunch'));
|
||||
this.#launcherReleased.add(key);
|
||||
return { released: true };
|
||||
}
|
||||
// Row 194 (#1692, D1.1): the launcher's lifecycle pass. Trusted launcher API, not socket verbs.
|
||||
@@ -5149,6 +5157,9 @@ export class Broker {
|
||||
case 'role.claim': {
|
||||
keys(a, []);
|
||||
if (s.human) fail('agent-required');
|
||||
// Row 302 (#1809, Dewey's D1): a run the launcher released is being stopped and doesn't claim
|
||||
// again, or a later not-holder on that claim would answer launcher-released.
|
||||
if (this.#launcherReleased.has(s.business + ':' + s.run)) fail('launcher-released');
|
||||
// Row 121: a thread run claims its thread, so idle threads hold nothing (REQ-THREAD-2).
|
||||
// Row 264a (#1769): the claim is the run's session's, with the session's next generation.
|
||||
if (s.thread) {
|
||||
|
||||
@@ -563,6 +563,8 @@ each holder that `launch.instances` lists:
|
||||
per run as `run <run>: landRelease refused: <code>`. After a release the
|
||||
launch log gets a `land-relaunch` record (`instance`, `run`,
|
||||
`decision`) and the runner gets SIGTERM, as `mosaic stop` sends it. The
|
||||
run's calls between the release and the signal refuse `launcher-released`,
|
||||
so the runner exits 0 either way (row 302). The
|
||||
next tick starts the instance with the decision in its `landing`.
|
||||
|
||||
The launcher keeps none of this on disk. A host that restarts ends the
|
||||
|
||||
@@ -293,7 +293,7 @@ it never touches the disk and isn't in the environment or argv.
|
||||
|
||||
| Exit | Meaning | Launch end reason |
|
||||
|---|---|---|
|
||||
| 0 | stopped | `stopped` |
|
||||
| 0 | stopped: by a signal, or the launcher's idle stop or land-relaunch released the claim (`launcher-released`) | `stopped` |
|
||||
| 2 | bad run directory or stdin | `failed` |
|
||||
| 20 | founder credentials | `founder-credentials` |
|
||||
| 21 | `role.claim` refused | `claim-refused` |
|
||||
|
||||
@@ -53,6 +53,9 @@ export const FOUNDER_ENV = Object.freeze([
|
||||
const USABLE = new Set(["valid", "expiring", "rotation-due"]);
|
||||
// Capabilities that stopped working: the run ended, the claim was revoked.
|
||||
const SESSION_GONE = new Set(["unauthenticated", "not-holder", "run-ended"]);
|
||||
// Row 302 (#1809): the launcher's idle stop or land-relaunch released the claim and will stop the run.
|
||||
// That is a stop, not a lost session, so the run exits stopped even when its call lands before the signal.
|
||||
const LAUNCHER_RELEASED = "launcher-released";
|
||||
const BODY_MAX = 32000;
|
||||
const DEFAULTS = Object.freeze({ turnTimeout: 900, pollInterval: 2000, brokerRetries: 30 });
|
||||
|
||||
@@ -482,6 +485,20 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
|
||||
let code = EXIT.stopped;
|
||||
let failures = 0;
|
||||
// Whether a broker refusal ends the run, and how: a launcher release stops it, a gone session is 22.
|
||||
// The reply site has logged its failure already, so it passes say=false.
|
||||
let released = false;
|
||||
const ended = (err, say = true) => {
|
||||
if (err.code === LAUNCHER_RELEASED) {
|
||||
log("runner: session released by the launcher");
|
||||
released = true;
|
||||
return true;
|
||||
}
|
||||
if (!SESSION_GONE.has(err.code)) return false;
|
||||
if (say) log(`runner: session stopped working: ${err.code}`);
|
||||
code = EXIT.session;
|
||||
return true;
|
||||
};
|
||||
let n = 0;
|
||||
const sleep = (ms) => new Promise((r) => {
|
||||
const t = setTimeout(r, ms);
|
||||
@@ -500,9 +517,8 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
try {
|
||||
block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
} catch (err) {
|
||||
if (SESSION_GONE.has(err.code)) {
|
||||
log(`runner: session stopped working: ${err.code}`);
|
||||
code = EXIT.session;
|
||||
// A run in its landing turns hasn't received, so landRelease keeps it; thread runs have no landing.
|
||||
if (ended(err)) {
|
||||
gone = true;
|
||||
break;
|
||||
}
|
||||
@@ -524,11 +540,7 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
messages = await client.call("message.receive");
|
||||
failures = 0;
|
||||
} catch (e) {
|
||||
if (SESSION_GONE.has(e.code)) {
|
||||
log(`runner: session stopped working: ${e.code}`);
|
||||
code = EXIT.session;
|
||||
break;
|
||||
}
|
||||
if (ended(e)) break;
|
||||
if (++failures >= session.brokerRetries) {
|
||||
log(`runner: broker unreachable after ${failures} tries: ${e.code ?? e.message}`);
|
||||
code = EXIT.broker;
|
||||
@@ -545,11 +557,8 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
try {
|
||||
block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
} catch (e) {
|
||||
if (SESSION_GONE.has(e.code)) {
|
||||
log(`runner: session stopped working: ${e.code}`);
|
||||
code = EXIT.session;
|
||||
break loop;
|
||||
}
|
||||
// Neither release happens while a delivered message is unread, so this sees one only by a bug.
|
||||
if (ended(e)) break loop;
|
||||
unread = `could not read the current state: ${e.code ?? e.message}`;
|
||||
}
|
||||
++n;
|
||||
@@ -576,17 +585,15 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
await client.call("message.read", { id: m.id });
|
||||
} catch (e) {
|
||||
log(`runner: reply to ${m.id} failed: ${e.code ?? e.message}`);
|
||||
if (SESSION_GONE.has(e.code)) {
|
||||
code = EXIT.session;
|
||||
break loop;
|
||||
}
|
||||
// As for the state above: the message isn't read yet, so no launcher release has happened.
|
||||
if (ended(e, false)) break loop;
|
||||
}
|
||||
}
|
||||
if (!messages.length) await sleep(session.pollInterval);
|
||||
}
|
||||
server.close();
|
||||
rmSync(session.toolSocket, { force: true });
|
||||
if (code === EXIT.stopped) {
|
||||
if (code === EXIT.stopped && !released) {
|
||||
try {
|
||||
await client.call("role.release");
|
||||
} catch (e) {
|
||||
|
||||
Reference in new issue
Block a user