feat(state): honor sequenceAbortToken so external aborts cleanly break sequences
Consumer half of the abort-token mechanism added in generalFunctions
state.js. executeSequence captures host.state.sequenceAbortToken at
entry, then re-checks before every state transition and after the
optional ramp-down. If MGC (or any external caller) bumps the token
mid-sequence, the loop bails out cleanly — no more barge-through where
a pre-empted shutdown advances through stopping → coolingdown after a
fresh demand has already engaged the pump.
Without this the MGC rendezvous planner can't reliably re-dispatch a
pump that's mid-shutdown: the new flowmovement claims the gate, but
the old shutdown's for-loop keeps running on microtasks and steps the
FSM into idle/off underneath it.
Also: wiki regen following the same visual-first 14-section template as
the other EVOLV nodes — Reference-{Architecture,Contracts,Examples,
Limitations}.md split with _Sidebar.md index.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,15 @@ async function executeSequence(host, rawName) {
|
||||
host.logger.warn(`Sequence '${name}' not defined.`);
|
||||
return;
|
||||
}
|
||||
// Snapshot the sequence-abort token at entry, BEFORE any awaits. If an
|
||||
// external abort advances the counter while we're inside this call
|
||||
// (setpoint ramp-down, waitForOperational, or the state transition
|
||||
// loop), every check below sees the mismatch and breaks out so the
|
||||
// new dispatch can claim the FSM. Capturing later would conflate the
|
||||
// abort that fired during setpoint(0) with the initial entry state.
|
||||
const startToken = host.state.sequenceAbortToken ?? 0;
|
||||
const aborted = () => (host.state.sequenceAbortToken ?? 0) !== startToken;
|
||||
|
||||
const interruptible = new Set(['shutdown', 'emergencystop']);
|
||||
if (interruptible.has(name)) host.state.delayedMove = null;
|
||||
const current = host.state.getCurrentState();
|
||||
@@ -74,9 +83,18 @@ async function executeSequence(host, rawName) {
|
||||
if (host.state.getCurrentState() === 'operational' && name === 'shutdown') {
|
||||
host.logger.info(`Machine will ramp down to position 0 before performing ${name} sequence`);
|
||||
await setpoint(host, 0);
|
||||
if (aborted()) {
|
||||
host.logger.warn(`Sequence '${name}' interrupted during ramp-down by external abort; not entering shutdown loop.`);
|
||||
host.updatePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
host.logger.info(` --------- Executing sequence: ${name} -------------`);
|
||||
for (const s of sequence) {
|
||||
if (aborted()) {
|
||||
host.logger.warn(`Sequence '${name}' interrupted at step '${s}' by external abort; stopping further transitions.`);
|
||||
break;
|
||||
}
|
||||
try { await host.state.transitionToState(s); }
|
||||
catch (e) { host.logger.error(`Error during sequence '${name}': ${e}`); break; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user