Coalesce fleet refreshes and suppress unchanged sweep events
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@ import { migrate } from '$lib/server/db/migrate.js';
|
||||
import { startWorker } from '$lib/server/pipeline/worker.js';
|
||||
import { interruptDeployments } from '$lib/server/provisioning/history.js';
|
||||
import { authenticationHandle } from '$lib/server/auth/guard.js';
|
||||
import { emit } from '$lib/server/pipeline/bus.js';
|
||||
import { invalidateFleet } from '$lib/server/pipeline/bus.js';
|
||||
import { startPerformanceMonitor } from '$lib/server/performance.js';
|
||||
import { withOperationSource } from '$lib/server/hosts/operation-context.js';
|
||||
|
||||
@@ -30,6 +30,6 @@ export const handle: Handle = async (input) => {
|
||||
if (process.env.EC_PERF_LOG === 'true' && elapsed > 250 && !input.event.route.id?.startsWith('/api/agent/')) {
|
||||
console.info('[request]', { route: input.event.route.id, method: input.event.request.method, status: response.status, elapsedMs: Math.round(elapsed) });
|
||||
}
|
||||
if (input.event.locals.user && !['GET','HEAD','OPTIONS'].includes(input.event.request.method) && response.status < 400) emit({ type: 'fleet-invalidated' });
|
||||
if (input.event.locals.user && !['GET','HEAD','OPTIONS'].includes(input.event.request.method) && response.status < 400) invalidateFleet();
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { pruneHostObservations } from '../hosts/index.js';
|
||||
interface PendingRow {
|
||||
deployment_id: string; server: string; restart_state: string;
|
||||
restarted_at: string | null; files_changed: number;
|
||||
players: number | null; error: string | null;
|
||||
}
|
||||
type SweepResult = { checked: number; restarted: number; healthy: number; held: number; failed: number };
|
||||
let sweepInFlight: Promise<SweepResult> | null = null;
|
||||
@@ -39,7 +40,9 @@ async function runSweep(): Promise<SweepResult> {
|
||||
attempts = attempts + CASE WHEN ? IS NOT NULL AND checked_at IS NOT ? THEN 1 ELSE 0 END,
|
||||
restarted_at = COALESCE(restarted_at, ?) WHERE deployment_id = ? AND server = ?`)
|
||||
.run(state, operation.checked_at, operation.players, error, operation.checked_at, operation.checked_at, restartedAt, row.deployment_id, row.server);
|
||||
emit({ type: 'server-state', deploymentId: row.deployment_id, server: row.server, state, players: operation.players, error });
|
||||
if (state !== row.restart_state || operation.players !== row.players || error !== row.error) {
|
||||
emit({ type: 'server-state', deploymentId: row.deployment_id, server: row.server, state, players: operation.players, error });
|
||||
}
|
||||
}
|
||||
db().prepare(`UPDATE deployments SET finished_at = ? WHERE finished_at IS NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM deployment_servers WHERE deployment_id = deployments.id AND server_deleted_at IS NULL AND restart_state IN ('pending','failed','held'))`).run(nowIso());
|
||||
|
||||
@@ -124,12 +124,37 @@ export function startFleetMonitor(): () => void {
|
||||
if (!browser || stop) return () => {};
|
||||
const stopPolling = pollVisible(() => refreshFleet(), 60_000, !get(fleet).receivedAt || Date.now() - get(fleet).receivedAt > 30_000);
|
||||
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let stopped = false;
|
||||
let pending = false;
|
||||
let refreshing = false;
|
||||
let lastEventRefresh = -Infinity;
|
||||
const schedule = () => {
|
||||
if (stopped || refreshing || refreshTimer !== undefined || !pending || document.visibilityState === 'hidden') return;
|
||||
refreshTimer = setTimeout(async () => {
|
||||
refreshTimer = undefined;
|
||||
refreshing = true;
|
||||
try {
|
||||
// Wait for any manual/poll refresh, then fetch evidence newer than
|
||||
// the event. Do not use refreshFleet(true)'s immediate follow-up.
|
||||
if (inFlight) await inFlight;
|
||||
if (stopped || document.visibilityState === 'hidden') return;
|
||||
pending = false;
|
||||
lastEventRefresh = Date.now();
|
||||
await refreshFleet();
|
||||
} finally {
|
||||
refreshing = false;
|
||||
schedule();
|
||||
}
|
||||
}, Math.max(250, lastEventRefresh + 2000 - Date.now()));
|
||||
};
|
||||
const changed = () => {
|
||||
if (refreshTimer || document.visibilityState === 'hidden') return;
|
||||
refreshTimer = setTimeout(() => { refreshTimer = undefined; void refreshFleet(true); }, 250);
|
||||
if (document.visibilityState === 'hidden') return;
|
||||
pending = true;
|
||||
schedule();
|
||||
};
|
||||
window.addEventListener('ec:fleet-changed', changed);
|
||||
stop = () => {
|
||||
stopped = true;
|
||||
stopPolling(); clearTimeout(refreshTimer); window.removeEventListener('ec:fleet-changed', changed); stop = null;
|
||||
};
|
||||
return stop;
|
||||
|
||||
Reference in New Issue
Block a user