fix(server): prove completed history before idle sleep (#18646)

Reviewed and verified at 592bce69c6.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-11-03 12:37:25 -06:00
1 parent 367f29478d
commit ccc63ea458
44 files changed
+4100 -112

No files matched your search

+57 -2
View File
@@ -46,6 +46,7 @@ import {
import { and, desc, eq, gte, inArray, isNull, lte, ne, or, sql } from "drizzle-orm";
import { asNumber, asString, parseObject, renderTemplate } from "../adapters/utils.js";
import { conflict, unprocessable } from "../errors.js";
import { registerIdleLocalWorkProbe, type IdleLocalWork } from "./idle-local-work.js";
import { withExecutionWorkspaceLifecycleLock } from "./execution-workspace-lifecycle-lock.js";
import { resolveHomeAwarePath } from "../home-paths.js";
import { hasVerifiedWorktreeSeedManifest, isVerifiedWorktreeSeedManifest } from "../worktree-seed-manifest.js";
@@ -283,6 +284,23 @@ const quarantinedRuntimeExposurePorts = new Set<number>();
* companion together or not at all.
*/
const exposurePortPairClaims = new ExposurePortPairClaims();
let runtimeReconstructionComplete = false;
let runtimeStopsInFlight = 0;
let runtimeReconstructionEpoch = 0;
let runtimeReconciliation: ReturnType<typeof reconstructRuntimeServices> | null = null;
/** Read owner memory only. Durable stopped rows do not describe a surviving
* registry process, an allocation being published, or unfinished exposure cleanup. */
export function readWorkspaceRuntimeIdleWork(): IdleLocalWork {
if (!runtimeReconstructionComplete || runtimeReconciliation !== null) return "unknown";
return runtimeStopsInFlight || runtimeServicesById.size || runtimeProvisionByWorkspace.size || runtimeControlStartByOwner.size
|| runtimeReplacementClaimsByReuseKey.size || quarantinedRuntimeExposurePorts.size
|| exposurePortPairClaims.activePorts().size
|| [...inFlightAllocatedPorts.values()].some(until => until > Date.now()) ? "present" : "none";
}
// This registry and its probe share the application process lifetime, like the
// native-session owner. Stopping one resource must not unregister its siblings.
registerIdleLocalWorkProbe(readWorkspaceRuntimeIdleWork);
/**
* Execution-workspace statuses that still hold an exclusive lease. `archived`
* is the only terminal state; everything else — including `idle` — is a lane an
@@ -525,6 +543,8 @@ type ProcessOutputAccumulator = {
export async function resetRuntimeServicesForTests(
opts: { terminateProcesses?: boolean; simulateSupervisorExit?: boolean } = {},
) {
runtimeReconstructionComplete = false;
runtimeReconstructionEpoch += 1;
if (opts.terminateProcesses) {
for (const serviceId of [...runtimeServicesById.keys()]) {
await stopRuntimeService(serviceId).catch(() => undefined);
@@ -7155,7 +7175,21 @@ async function cleanupRecordExposure(
await persistRuntimeServiceRecord(record.db, record).catch(() => undefined);
}
async function withRuntimeStopWork<T>(stop: () => Promise<T>): Promise<T> {
runtimeStopsInFlight += 1;
try { return await stop(); }
catch (error) {
runtimeReconstructionComplete = false;
runtimeReconstructionEpoch += 1;
throw error;
} finally { runtimeStopsInFlight -= 1; }
}
async function stopRuntimeService(serviceId: string) {
return withRuntimeStopWork(() => stopRuntimeServiceRecord(serviceId));
}
async function stopRuntimeServiceRecord(serviceId: string) {
const record = runtimeServicesById.get(serviceId);
if (!record) return;
clearIdleTimer(record);
@@ -8296,7 +8330,11 @@ export async function releaseRuntimeServicesForRun(runId: string) {
}
}
export async function stopRuntimeServicesForExecutionWorkspace(input: {
export async function stopRuntimeServicesForExecutionWorkspace(input: Parameters<typeof stopRuntimeServicesForExecutionWorkspaceOwned>[0]) {
return withRuntimeStopWork(() => stopRuntimeServicesForExecutionWorkspaceOwned(input));
}
async function stopRuntimeServicesForExecutionWorkspaceOwned(input: {
db?: Db;
executionWorkspaceId: string;
workspaceCwd?: string | null;
@@ -8351,7 +8389,11 @@ export async function stopRuntimeServicesForExecutionWorkspace(input: {
}
}
export async function stopRuntimeServicesForProjectWorkspace(input: {
export async function stopRuntimeServicesForProjectWorkspace(input: Parameters<typeof stopRuntimeServicesForProjectWorkspaceOwned>[0]) {
return withRuntimeStopWork(() => stopRuntimeServicesForProjectWorkspaceOwned(input));
}
async function stopRuntimeServicesForProjectWorkspaceOwned(input: {
db?: Db;
projectWorkspaceId: string;
runtimeServiceId?: string | null;
@@ -8577,6 +8619,19 @@ export async function refreshPersistedRuntimeServiceHealth(input: {
}
export async function reconcilePersistedRuntimeServicesOnStartup(db: Db) {
if (runtimeReconciliation) return runtimeReconciliation;
runtimeReconstructionComplete = false;
const epoch = ++runtimeReconstructionEpoch;
runtimeReconciliation = reconstructRuntimeServices(db).then(result => {
if (epoch === runtimeReconstructionEpoch) {
runtimeReconstructionComplete = result.restartFailed === 0 && result.exposureReservationDrift.length === 0;
}
return result;
}).finally(() => { runtimeReconciliation = null; });
return runtimeReconciliation;
}
async function reconstructRuntimeServices(db: Db) {
const rows = await db
.select()
.from(workspaceRuntimeServices)