slopfixing queue stuffs

This commit is contained in:
legop3
2026-07-21 23:38:57 -04:00
parent b26daed93f
commit 3a26b4871a
10 changed files with 218 additions and 30 deletions
+67 -5
View File
@@ -112,6 +112,7 @@ function driverAdded(roverId, socketId, options = {}) {
const { force, pauseQueue } = normalizeDriverAddOptions(options);
const queue = ensureQueue(roverId);
const alreadyQueued = queue.queue.includes(socketId);
const previousQueueLength = queue.queue.length;
if (pauseQueue) {
applyAdminQueuePause(roverId, socketId, queue);
@@ -141,6 +142,30 @@ function driverAdded(roverId, socketId, options = {}) {
if (!queue.current || force) {
queue.current = socketId;
}
/*
A normal join changes who will receive a future turn; it does not begin a
new turn for the person who is already driving. Preserve both deadlines and
the current driver's activity state while an already-rotating queue grows.
The one-to-two transition is intentionally different. Before the second
driver arrives there is no reason to time the sole driver, so this is the
moment the first real rotating turn begins. A forced grant also changes the
active driver immediately and therefore starts that driver's fresh turn.
*/
const turnsAreRotating = getMode() === MODES.TURNS && queue.queue.length > 1;
const beginsRotatingTurns = turnsAreRotating && previousQueueLength <= 1;
if (turnsAreRotating && (force || beginsRotatingTurns)) {
startCurrentTurn(roverId, queue);
return;
}
if (turnsAreRotating) {
setActiveDriver(roverId, queue.current);
turnEvents.emit('queue', { roverId, reason: 'driver-joined' });
return;
}
syncState(roverId);
}
@@ -165,13 +190,32 @@ function driverRemoved(roverId, socketId) {
idleSkips.delete(roverId);
}
}
idleDisarmed.delete(roverId);
if (queue.current === socketId) {
/*
Removing the active driver is a real handoff. advanceTurn() owns clearing
the old turn state and creating the next driver's deadlines, keeping this
path identical for explicit release, disconnect, and stale-socket reap.
*/
stopRover(roverId);
advanceTurn(roverId);
} else {
scheduleIdleTimer(roverId);
return;
}
/*
A waiting driver leaving must not re-arm idle detection or extend the
unrelated active driver's turn. If this removal ends rotation entirely,
syncState() clears both deadlines immediately; otherwise only the public
queue membership changes.
*/
if (getMode() !== MODES.TURNS || queue.queue.length <= 1) {
syncState(roverId);
return;
}
if (isQueuePaused(queue)) {
turnEvents.emit('queue', { roverId, reason: 'driver-left-admin-pause' });
return;
}
turnEvents.emit('queue', { roverId, reason: 'driver-left' });
}
function cleanupRover(roverId) {
@@ -242,11 +286,24 @@ function syncState(roverId) {
if (!queue.current) {
queue.current = queue.queue[0];
}
/*
syncState() is used when entering turns mode and when a previously untimed
queue becomes rotatable. Both cases begin a genuine turn rather than merely
publishing a membership update.
*/
startCurrentTurn(roverId, queue);
}
function startCurrentTurn(roverId, queue) {
/*
This is the sole normal entry point for a fresh timed turn. Keeping active
ownership, the full-turn deadline, and the activity grace window together
prevents callers from resetting only part of the lifecycle.
*/
setActiveDriver(roverId, queue.current);
idleDisarmed.set(roverId, false);
scheduleNextTurn(roverId);
scheduleIdleTimer(roverId);
turnEvents.emit('queue', { roverId });
}
function scheduleNextTurn(roverId) {
@@ -423,7 +480,12 @@ function reapStaleDrivers() {
});
}
setInterval(reapStaleDrivers, STALE_REAPER_MS);
/*
Stale cleanup should run for the lifetime of the server, but it should not by
itself keep short-lived tools or the Node test runner alive after their work
has completed.
*/
setInterval(reapStaleDrivers, STALE_REAPER_MS).unref();
module.exports = {
driverAdded,
@@ -0,0 +1,96 @@
// Turn Service Tests
// Purpose: Protects timed-turn state from unrelated queue membership changes.
// Scope: Exercises public turn operations and inspects only their published queue model; no HTTP server is started.
const assert = require('node:assert/strict');
const test = require('node:test');
const { MODES, setMode } = require('../modeManager');
const turnService = require('./index');
function enterTurnsMode() {
/*
Mode authorization is irrelevant to this state-machine test. The forced
transition uses the same production mode event, ensuring existing queues
are reconciled exactly as they would be after an administrator mode change.
*/
setMode(MODES.TURNS, null, { force: true });
}
function resetRover(roverId) {
turnService.cleanupRover(roverId);
}
test('joining an active rotation preserves the current turn and completed activity grace', () => {
const roverId = 'turn-test-join';
enterTurnsMode();
turnService.driverAdded(roverId, 'driver-a');
turnService.driverAdded(roverId, 'driver-b');
const started = turnService.getTurnQueues()[roverId];
assert.ok(started.deadline);
assert.ok(started.idleDeadline);
turnService.recordActivity(roverId, 'driver-a');
const active = turnService.getTurnQueues()[roverId];
assert.equal(active.idleDeadline, null);
turnService.driverAdded(roverId, 'driver-c');
const joined = turnService.getTurnQueues()[roverId];
assert.equal(joined.current, 'driver-a');
assert.equal(joined.deadline, active.deadline);
assert.equal(joined.idleDeadline, null);
resetRover(roverId);
});
test('a waiting driver leaving preserves both active deadlines', () => {
const roverId = 'turn-test-waiting-leave';
enterTurnsMode();
turnService.driverAdded(roverId, 'driver-a');
turnService.driverAdded(roverId, 'driver-b');
turnService.driverAdded(roverId, 'driver-c');
const before = turnService.getTurnQueues()[roverId];
turnService.driverRemoved(roverId, 'driver-c');
const after = turnService.getTurnQueues()[roverId];
assert.equal(after.current, 'driver-a');
assert.equal(after.deadline, before.deadline);
assert.equal(after.idleDeadline, before.idleDeadline);
resetRover(roverId);
});
test('dropping to one driver clears turn and idle deadlines immediately', () => {
const roverId = 'turn-test-single-driver';
enterTurnsMode();
turnService.driverAdded(roverId, 'driver-a');
turnService.driverAdded(roverId, 'driver-b');
assert.ok(turnService.getTurnQueues()[roverId].deadline);
turnService.driverRemoved(roverId, 'driver-b');
const single = turnService.getTurnQueues()[roverId];
assert.equal(single.current, 'driver-a');
assert.equal(single.deadline, null);
assert.equal(single.idleDeadline, null);
resetRover(roverId);
});
test('turn deadlines are absent outside turns mode during queue grants', () => {
const roverId = 'turn-test-admin-mode';
setMode(MODES.ADMIN, null, { force: true });
turnService.driverAdded(roverId, 'driver-a');
turnService.driverAdded(roverId, 'driver-b', { force: true });
const queue = turnService.getTurnQueues()[roverId];
/*
In non-turn modes syncState keeps the first regular queued driver current.
Explicit administrator takeover uses the separate pauseQueue path; this
assertion is concerned only with ensuring neither grant creates timers.
*/
assert.equal(queue.current, 'driver-a');
assert.equal(queue.deadline, null);
assert.equal(queue.idleDeadline, null);
resetRover(roverId);
});