@shipfox/api-runners 12.6.0 → 12.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +8 -0
  3. package/dist/db/job-executions.d.ts +3 -0
  4. package/dist/db/job-executions.d.ts.map +1 -1
  5. package/dist/db/job-executions.js +23 -1
  6. package/dist/db/job-executions.js.map +1 -1
  7. package/dist/db/provisioner-tokens.d.ts.map +1 -1
  8. package/dist/db/provisioner-tokens.js +14 -2
  9. package/dist/db/provisioner-tokens.js.map +1 -1
  10. package/dist/db/reservations.d.ts +10 -0
  11. package/dist/db/reservations.d.ts.map +1 -1
  12. package/dist/db/reservations.js +137 -17
  13. package/dist/db/reservations.js.map +1 -1
  14. package/dist/db/runner-assignments.d.ts.map +1 -1
  15. package/dist/db/runner-assignments.js +8 -5
  16. package/dist/db/runner-assignments.js.map +1 -1
  17. package/dist/db/runner-instances.d.ts +0 -1
  18. package/dist/db/runner-instances.d.ts.map +1 -1
  19. package/dist/db/runner-instances.js +85 -100
  20. package/dist/db/runner-instances.js.map +1 -1
  21. package/dist/db/runner-states.d.ts +3 -1
  22. package/dist/db/runner-states.d.ts.map +1 -1
  23. package/dist/db/runner-states.js +6 -4
  24. package/dist/db/runner-states.js.map +1 -1
  25. package/dist/tsconfig.test.tsbuildinfo +1 -1
  26. package/package.json +2 -2
  27. package/src/db/job-executions.test.ts +119 -0
  28. package/src/db/job-executions.ts +24 -2
  29. package/src/db/provisioner-tokens.test.ts +19 -0
  30. package/src/db/provisioner-tokens.ts +13 -2
  31. package/src/db/reservations.test.ts +138 -0
  32. package/src/db/reservations.ts +311 -35
  33. package/src/db/runner-assignments.test.ts +20 -0
  34. package/src/db/runner-assignments.ts +6 -7
  35. package/src/db/runner-instances.test.ts +250 -15
  36. package/src/db/runner-instances.ts +136 -198
  37. package/src/db/runner-states.test.ts +16 -0
  38. package/src/db/runner-states.ts +9 -4
  39. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/runner-assignments.ts"],"sourcesContent":["import {and, eq, inArray, isNull, ne, notInArray, or, sql} from 'drizzle-orm';\nimport {\n ReservationExpiredError,\n ReservationNotFoundError,\n RunnerInstanceAlreadyAssignedError,\n RunnerInstanceNotAssignableError,\n} from '#core/errors.js';\nimport {\n type ProviderRunnerAssignmentObservation,\n type RunnerAssignmentSurface,\n recordProviderRunnerControlSessionToAssignment,\n} from '#metrics/instance.js';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {lockRunnerReservationAdvisoryKeysTx} from './reservation-locks.js';\nimport {terminalStates} from './runner-states.js';\nimport {reservations} from './schema/reservations.js';\nimport {runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\n\n/** Atomically consumes reservation units by writing the immutable assignment on each runner. */\nexport async function assignRunnerInstances(params: {\n provisionerId: string;\n reservationId: string;\n runnerInstanceIds: string[];\n}): Promise<string[]> {\n const result = await db().transaction(async (tx) =>\n assignRunnerInstancesTx(tx, {...params, surface: 'provisioner'}),\n );\n for (const observation of result.controlSessionToAssignment)\n recordProviderRunnerControlSessionToAssignment(observation);\n return result.runnerInstanceIds;\n}\n\ninterface AssignRunnerInstancesTxResult {\n runnerInstanceIds: string[];\n controlSessionToAssignment: ProviderRunnerAssignmentObservation[];\n}\n\nexport async function assignRunnerInstancesTx(\n tx: Tx,\n params: {\n provisionerId: string;\n reservationId: string;\n runnerInstanceIds: string[];\n surface: RunnerAssignmentSurface;\n },\n): Promise<AssignRunnerInstancesTxResult> {\n const runnerInstanceIds = [...params.runnerInstanceIds].sort();\n await lockRunnerReservationAdvisoryKeysTx(tx, {\n provisionerId: params.provisionerId,\n reservationIds: [params.reservationId],\n });\n const runnerRows = await tx\n .select({\n id: providerRunners.id,\n reservationId: providerRunners.reservationId,\n intendedReservationId: providerRunners.intendedReservationId,\n workspaceId: providerRunners.workspaceId,\n assignedAt: providerRunners.assignedAt,\n providerRunnerId: providerRunners.providerRunnerId,\n provider: providerRunners.providerKind,\n launchKind: providerRunners.launchKind,\n labels: providerRunners.labels,\n state: providerRunners.state,\n })\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n inArray(providerRunners.id, runnerInstanceIds),\n ),\n )\n .for('update');\n\n // The assignment outlives its short reservation row. A retry after maintenance\n // cleanup is complete when every owned runner already carries the committed assignment.\n if (\n runnerInstanceIds.length > 0 &&\n runnerRows.length === runnerInstanceIds.length &&\n runnerRows.every(\n (runner) =>\n runner.reservationId === params.reservationId &&\n runner.assignedAt !== null &&\n runner.workspaceId !== null,\n )\n ) {\n await tx\n .update(providerRunners)\n .set({intendedReservationId: null, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n inArray(providerRunners.id, runnerInstanceIds),\n ),\n );\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment: [],\n };\n }\n\n const [reservation] = await tx\n .select()\n .from(reservations)\n .where(\n and(\n eq(reservations.id, params.reservationId),\n eq(reservations.provisionerId, params.provisionerId),\n eq(reservations.kind, 'launch'),\n ),\n )\n .limit(1)\n .for('update');\n if (!reservation) throw new ReservationNotFoundError(params.reservationId);\n if (reservation.expiresAt <= new Date()) throw new ReservationExpiredError(params.reservationId);\n if (runnerRows.length !== runnerInstanceIds.length)\n throw new RunnerInstanceNotAssignableError(runnerInstanceIds[0] ?? '', 'runner-not-found');\n\n const activeControlSessions = await tx\n .select({\n runnerInstanceId: runnerControlSessions.runnerInstanceId,\n createdAt: runnerControlSessions.createdAt,\n })\n .from(runnerControlSessions)\n .where(\n and(\n inArray(\n runnerControlSessions.runnerInstanceId,\n runnerRows.map((runner) => runner.id),\n ),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n const runnerInstanceIdsWithControlSession = new Set(\n activeControlSessions.map((session) => session.runnerInstanceId),\n );\n const controlSessionCreatedAtByRunner = new Map(\n activeControlSessions.map((session) => [session.runnerInstanceId, session.createdAt]),\n );\n const runners = runnerRows.map((runner) => ({\n ...runner,\n controlSessionId: runnerInstanceIdsWithControlSession.has(runner.id) ? runner.id : null,\n controlSessionCreatedAt: controlSessionCreatedAtByRunner.get(runner.id) ?? null,\n }));\n\n const alreadyAssigned = runners.filter((runner) => runner.reservationId !== null);\n if (alreadyAssigned.some((runner) => runner.reservationId !== reservation.id))\n throw new RunnerInstanceAlreadyAssignedError(alreadyAssigned[0]?.id ?? '');\n const newRunners = runners.filter(\n (runner) =>\n runner.reservationId === null ||\n (runner.reservationId === reservation.id &&\n (runner.assignedAt === null || runner.workspaceId === null)),\n );\n const newRunnerIds = newRunners.map((runner) => runner.id);\n const assignedCount = await tx\n .select({count: sql<number>`count(*)::int`})\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n notInArray(providerRunners.id, newRunnerIds),\n or(\n and(\n eq(providerRunners.reservationId, reservation.id),\n isNull(providerRunners.reservationReleasedAt),\n ),\n and(\n eq(providerRunners.intendedReservationId, reservation.id),\n isNull(providerRunners.reservationReleasedAt),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n ),\n ),\n );\n if ((assignedCount[0]?.count ?? 0) + newRunners.length > reservation.count)\n throw new RunnerInstanceNotAssignableError(newRunners[0]?.id ?? '', 'capacity-exhausted');\n for (const runner of newRunners) {\n if (runner.state !== 'running')\n throw new RunnerInstanceNotAssignableError(runner.id, 'runner-not-running');\n if (!runner.providerRunnerId)\n throw new RunnerInstanceNotAssignableError(runner.id, 'provider-identity-missing');\n if (!runner.controlSessionId)\n throw new RunnerInstanceNotAssignableError(runner.id, 'control-session-not-active');\n if (!reservation.requiredLabels.every((label) => runner.labels.includes(label)))\n throw new RunnerInstanceNotAssignableError(runner.id, 'labels-mismatch');\n }\n if (newRunners.length > 0) {\n const assignedRows = await tx\n .update(providerRunners)\n .set({\n workspaceId: reservation.workspaceId,\n reservationId: reservation.id,\n intendedReservationId: null,\n assignedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n inArray(\n providerRunners.id,\n newRunners.map((runner) => runner.id),\n ),\n )\n .returning({id: providerRunners.id, assignedAt: providerRunners.assignedAt});\n const runnersById = new Map(newRunners.map((runner) => [runner.id, runner]));\n const controlSessionToAssignment: ProviderRunnerAssignmentObservation[] = [];\n for (const assigned of assignedRows) {\n const runner = runnersById.get(assigned.id);\n const controlSessionCreatedAt = runner?.controlSessionCreatedAt;\n if (!runner || !assigned.assignedAt || !controlSessionCreatedAt) continue;\n controlSessionToAssignment.push({\n durationMilliseconds: assigned.assignedAt.getTime() - controlSessionCreatedAt.getTime(),\n provider: runner.provider,\n launchKind: runner.launchKind,\n surface: params.surface,\n runnerInstanceId: runner.id,\n });\n }\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment,\n };\n }\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment: [],\n };\n}\n\nexport type RunnerReservationCapacityFailureReason =\n | 'reservation-not-found'\n | 'reservation-kind-mismatch'\n | 'reservation-expired'\n | 'capacity-exhausted';\n\nexport interface RunnerReservationCapacityValidation {\n acceptedByReservation: Map<string, number>;\n unavailableByReservation: Map<\n string,\n {reason: RunnerReservationCapacityFailureReason; count: number}\n >;\n}\n\nexport async function validateRunnerReservationCapacityTx(\n tx: Tx,\n params: {\n provisionerId: string;\n requests: readonly {reservationId: string; count: number}[];\n },\n options: {advisoryLocksHeld?: boolean} = {},\n): Promise<RunnerReservationCapacityValidation> {\n const requestedByReservation = new Map<string, number>();\n for (const request of params.requests) {\n if (request.count <= 0) continue;\n requestedByReservation.set(\n request.reservationId,\n (requestedByReservation.get(request.reservationId) ?? 0) + request.count,\n );\n }\n const reservationIds = [...requestedByReservation.keys()].sort();\n if (reservationIds.length === 0)\n return {acceptedByReservation: new Map(), unavailableByReservation: new Map()};\n\n if (!options.advisoryLocksHeld)\n await lockRunnerReservationAdvisoryKeysTx(tx, {\n provisionerId: params.provisionerId,\n reservationIds,\n });\n\n const reservationRows = await tx\n .select({\n id: reservations.id,\n count: reservations.count,\n expiresAt: reservations.expiresAt,\n isExpired: sql<boolean>`${reservations.expiresAt} <= now()`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.provisionerId, params.provisionerId),\n inArray(reservations.id, reservationIds),\n eq(reservations.kind, 'launch'),\n ),\n )\n .for('update');\n const nonLaunchReservationRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(\n and(\n eq(reservations.provisionerId, params.provisionerId),\n inArray(reservations.id, reservationIds),\n ne(reservations.kind, 'launch'),\n ),\n )\n .for('update');\n const reservationsById = new Map(\n reservationRows.map((reservation) => [reservation.id, reservation]),\n );\n const nonLaunchReservationIds = new Set(\n nonLaunchReservationRows.map((reservation) => reservation.id),\n );\n const requestedReservationIds = new Set(reservationIds);\n const usedRunnerIdsByReservation = new Map<string, Set<string>>();\n const usedRunnerRows = await tx\n .select({\n id: providerRunners.id,\n reservationId: providerRunners.reservationId,\n intendedReservationId: providerRunners.intendedReservationId,\n state: providerRunners.state,\n })\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n isNull(providerRunners.reservationReleasedAt),\n or(\n inArray(providerRunners.reservationId, reservationIds),\n and(\n inArray(providerRunners.intendedReservationId, reservationIds),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n ),\n ),\n );\n for (const runner of usedRunnerRows) {\n if (runner.reservationId && requestedReservationIds.has(runner.reservationId)) {\n const usedRunnerIds =\n usedRunnerIdsByReservation.get(runner.reservationId) ?? new Set<string>();\n usedRunnerIds.add(runner.id);\n usedRunnerIdsByReservation.set(runner.reservationId, usedRunnerIds);\n }\n if (\n runner.intendedReservationId &&\n requestedReservationIds.has(runner.intendedReservationId) &&\n !terminalStates.includes(runner.state as (typeof terminalStates)[number])\n ) {\n const usedRunnerIds =\n usedRunnerIdsByReservation.get(runner.intendedReservationId) ?? new Set<string>();\n usedRunnerIds.add(runner.id);\n usedRunnerIdsByReservation.set(runner.intendedReservationId, usedRunnerIds);\n }\n }\n\n const acceptedByReservation = new Map<string, number>();\n const unavailableByReservation = new Map<\n string,\n {reason: RunnerReservationCapacityFailureReason; count: number}\n >();\n for (const reservationId of reservationIds) {\n const requested = requestedByReservation.get(reservationId) ?? 0;\n const reservation = reservationsById.get(reservationId);\n if (!reservation) {\n unavailableByReservation.set(reservationId, {\n reason: nonLaunchReservationIds.has(reservationId)\n ? 'reservation-kind-mismatch'\n : 'reservation-not-found',\n count: requested,\n });\n continue;\n }\n if (reservation.isExpired) {\n unavailableByReservation.set(reservationId, {\n reason: 'reservation-expired',\n count: requested,\n });\n continue;\n }\n\n const used = usedRunnerIdsByReservation.get(reservationId)?.size ?? 0;\n const accepted = Math.min(requested, Math.max(0, reservation.count - used));\n acceptedByReservation.set(reservationId, accepted);\n if (accepted < requested) {\n unavailableByReservation.set(reservationId, {\n reason: 'capacity-exhausted',\n count: requested - accepted,\n });\n }\n }\n\n return {acceptedByReservation, unavailableByReservation};\n}\n"],"names":["and","eq","inArray","isNull","ne","notInArray","or","sql","ReservationExpiredError","ReservationNotFoundError","RunnerInstanceAlreadyAssignedError","RunnerInstanceNotAssignableError","recordProviderRunnerControlSessionToAssignment","db","lockRunnerReservationAdvisoryKeysTx","terminalStates","reservations","runnerControlSessions","providerRunners","assignRunnerInstances","params","result","transaction","tx","assignRunnerInstancesTx","surface","observation","controlSessionToAssignment","runnerInstanceIds","sort","provisionerId","reservationIds","reservationId","runnerRows","select","id","intendedReservationId","workspaceId","assignedAt","providerRunnerId","provider","providerKind","launchKind","labels","state","from","where","for","length","every","runner","update","set","updatedAt","reservation","kind","limit","expiresAt","Date","activeControlSessions","runnerInstanceId","createdAt","map","closedAt","runnerInstanceIdsWithControlSession","Set","session","controlSessionCreatedAtByRunner","Map","runners","controlSessionId","has","controlSessionCreatedAt","get","alreadyAssigned","filter","some","newRunners","newRunnerIds","assignedCount","count","reservationReleasedAt","requiredLabels","label","includes","assignedRows","returning","runnersById","assigned","push","durationMilliseconds","getTime","validateRunnerReservationCapacityTx","options","requestedByReservation","request","requests","keys","acceptedByReservation","unavailableByReservation","advisoryLocksHeld","reservationRows","isExpired","nonLaunchReservationRows","reservationsById","nonLaunchReservationIds","requestedReservationIds","usedRunnerIdsByReservation","usedRunnerRows","usedRunnerIds","add","requested","reason","used","size","accepted","Math","min","max"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/runner-assignments.ts"],"sourcesContent":["import {and, eq, inArray, isNull, ne, notInArray, or, sql} from 'drizzle-orm';\nimport {\n ReservationExpiredError,\n ReservationNotFoundError,\n RunnerInstanceAlreadyAssignedError,\n RunnerInstanceNotAssignableError,\n} from '#core/errors.js';\nimport {\n type ProviderRunnerAssignmentObservation,\n type RunnerAssignmentSurface,\n recordProviderRunnerControlSessionToAssignment,\n} from '#metrics/instance.js';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {lockRunnerReservationAdvisoryKeysTx} from './reservation-locks.js';\nimport {terminalStates} from './runner-states.js';\nimport {reservations} from './schema/reservations.js';\nimport {runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\n\n/** Atomically consumes reservation units by writing the immutable assignment on each runner. */\nexport async function assignRunnerInstances(params: {\n provisionerId: string;\n reservationId: string;\n runnerInstanceIds: string[];\n}): Promise<string[]> {\n const result = await db().transaction(async (tx) =>\n assignRunnerInstancesTx(tx, {...params, surface: 'provisioner'}),\n );\n for (const observation of result.controlSessionToAssignment)\n recordProviderRunnerControlSessionToAssignment(observation);\n return result.runnerInstanceIds;\n}\n\ninterface AssignRunnerInstancesTxResult {\n runnerInstanceIds: string[];\n controlSessionToAssignment: ProviderRunnerAssignmentObservation[];\n}\n\nexport async function assignRunnerInstancesTx(\n tx: Tx,\n params: {\n provisionerId: string;\n reservationId: string;\n runnerInstanceIds: string[];\n surface: RunnerAssignmentSurface;\n },\n): Promise<AssignRunnerInstancesTxResult> {\n const runnerInstanceIds = [...params.runnerInstanceIds].sort();\n await lockRunnerReservationAdvisoryKeysTx(tx, {\n provisionerId: params.provisionerId,\n reservationIds: [params.reservationId],\n });\n const runnerRows = await tx\n .select({\n id: providerRunners.id,\n reservationId: providerRunners.reservationId,\n intendedReservationId: providerRunners.intendedReservationId,\n workspaceId: providerRunners.workspaceId,\n assignedAt: providerRunners.assignedAt,\n providerRunnerId: providerRunners.providerRunnerId,\n provider: providerRunners.providerKind,\n launchKind: providerRunners.launchKind,\n labels: providerRunners.labels,\n state: providerRunners.state,\n })\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n inArray(providerRunners.id, runnerInstanceIds),\n ),\n )\n .for('update');\n\n // The assignment outlives its short reservation row. A retry after maintenance\n // cleanup is complete when every owned runner already carries the committed assignment.\n if (\n runnerInstanceIds.length > 0 &&\n runnerRows.length === runnerInstanceIds.length &&\n runnerRows.every(\n (runner) =>\n runner.reservationId === params.reservationId &&\n runner.assignedAt !== null &&\n runner.workspaceId !== null,\n )\n ) {\n await tx\n .update(providerRunners)\n .set({intendedReservationId: null, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n inArray(providerRunners.id, runnerInstanceIds),\n ),\n );\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment: [],\n };\n }\n\n const [reservation] = await tx\n .select()\n .from(reservations)\n .where(\n and(\n eq(reservations.id, params.reservationId),\n eq(reservations.provisionerId, params.provisionerId),\n eq(reservations.kind, 'launch'),\n ),\n )\n .limit(1)\n .for('update');\n if (!reservation) throw new ReservationNotFoundError(params.reservationId);\n if (reservation.expiresAt <= new Date()) throw new ReservationExpiredError(params.reservationId);\n if (runnerRows.length !== runnerInstanceIds.length)\n throw new RunnerInstanceNotAssignableError(runnerInstanceIds[0] ?? '', 'runner-not-found');\n\n const activeControlSessions = await tx\n .select({\n runnerInstanceId: runnerControlSessions.runnerInstanceId,\n createdAt: runnerControlSessions.createdAt,\n })\n .from(runnerControlSessions)\n .where(\n and(\n inArray(\n runnerControlSessions.runnerInstanceId,\n runnerRows.map((runner) => runner.id),\n ),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n const runnerInstanceIdsWithControlSession = new Set(\n activeControlSessions.map((session) => session.runnerInstanceId),\n );\n const controlSessionCreatedAtByRunner = new Map(\n activeControlSessions.map((session) => [session.runnerInstanceId, session.createdAt]),\n );\n const runners = runnerRows.map((runner) => ({\n ...runner,\n controlSessionId: runnerInstanceIdsWithControlSession.has(runner.id) ? runner.id : null,\n controlSessionCreatedAt: controlSessionCreatedAtByRunner.get(runner.id) ?? null,\n }));\n\n const alreadyAssigned = runners.filter((runner) => runner.reservationId !== null);\n if (alreadyAssigned.some((runner) => runner.reservationId !== reservation.id))\n throw new RunnerInstanceAlreadyAssignedError(alreadyAssigned[0]?.id ?? '');\n const newRunners = runners.filter(\n (runner) =>\n runner.reservationId === null ||\n (runner.reservationId === reservation.id &&\n (runner.assignedAt === null || runner.workspaceId === null)),\n );\n const newRunnerIds = newRunners.map((runner) => runner.id);\n const assignedCount = await tx\n .select({count: sql<number>`count(*)::int`})\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n notInArray(providerRunners.id, newRunnerIds),\n or(\n and(\n eq(providerRunners.reservationId, reservation.id),\n isNull(providerRunners.reservationReleasedAt),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n and(\n eq(providerRunners.intendedReservationId, reservation.id),\n isNull(providerRunners.reservationReleasedAt),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n ),\n ),\n );\n if ((assignedCount[0]?.count ?? 0) + newRunners.length > reservation.count)\n throw new RunnerInstanceNotAssignableError(newRunners[0]?.id ?? '', 'capacity-exhausted');\n for (const runner of newRunners) {\n if (runner.state !== 'running')\n throw new RunnerInstanceNotAssignableError(runner.id, 'runner-not-running');\n if (!runner.providerRunnerId)\n throw new RunnerInstanceNotAssignableError(runner.id, 'provider-identity-missing');\n if (!runner.controlSessionId)\n throw new RunnerInstanceNotAssignableError(runner.id, 'control-session-not-active');\n if (!reservation.requiredLabels.every((label) => runner.labels.includes(label)))\n throw new RunnerInstanceNotAssignableError(runner.id, 'labels-mismatch');\n }\n if (newRunners.length > 0) {\n const assignedRows = await tx\n .update(providerRunners)\n .set({\n workspaceId: reservation.workspaceId,\n reservationId: reservation.id,\n intendedReservationId: null,\n assignedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n inArray(\n providerRunners.id,\n newRunners.map((runner) => runner.id),\n ),\n )\n .returning({id: providerRunners.id, assignedAt: providerRunners.assignedAt});\n const runnersById = new Map(newRunners.map((runner) => [runner.id, runner]));\n const controlSessionToAssignment: ProviderRunnerAssignmentObservation[] = [];\n for (const assigned of assignedRows) {\n const runner = runnersById.get(assigned.id);\n const controlSessionCreatedAt = runner?.controlSessionCreatedAt;\n if (!runner || !assigned.assignedAt || !controlSessionCreatedAt) continue;\n controlSessionToAssignment.push({\n durationMilliseconds: assigned.assignedAt.getTime() - controlSessionCreatedAt.getTime(),\n provider: runner.provider,\n launchKind: runner.launchKind,\n surface: params.surface,\n runnerInstanceId: runner.id,\n });\n }\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment,\n };\n }\n return {\n runnerInstanceIds: params.runnerInstanceIds,\n controlSessionToAssignment: [],\n };\n}\n\nexport type RunnerReservationCapacityFailureReason =\n | 'reservation-not-found'\n | 'reservation-kind-mismatch'\n | 'reservation-expired'\n | 'capacity-exhausted';\n\nexport interface RunnerReservationCapacityValidation {\n acceptedByReservation: Map<string, number>;\n unavailableByReservation: Map<\n string,\n {reason: RunnerReservationCapacityFailureReason; count: number}\n >;\n}\n\nexport async function validateRunnerReservationCapacityTx(\n tx: Tx,\n params: {\n provisionerId: string;\n requests: readonly {reservationId: string; count: number}[];\n },\n options: {advisoryLocksHeld?: boolean} = {},\n): Promise<RunnerReservationCapacityValidation> {\n const requestedByReservation = new Map<string, number>();\n for (const request of params.requests) {\n if (request.count <= 0) continue;\n requestedByReservation.set(\n request.reservationId,\n (requestedByReservation.get(request.reservationId) ?? 0) + request.count,\n );\n }\n const reservationIds = [...requestedByReservation.keys()].sort();\n if (reservationIds.length === 0)\n return {acceptedByReservation: new Map(), unavailableByReservation: new Map()};\n\n if (!options.advisoryLocksHeld)\n await lockRunnerReservationAdvisoryKeysTx(tx, {\n provisionerId: params.provisionerId,\n reservationIds,\n });\n\n const reservationRows = await tx\n .select({\n id: reservations.id,\n count: reservations.count,\n expiresAt: reservations.expiresAt,\n isExpired: sql<boolean>`${reservations.expiresAt} <= now()`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.provisionerId, params.provisionerId),\n inArray(reservations.id, reservationIds),\n eq(reservations.kind, 'launch'),\n ),\n )\n .for('update');\n const nonLaunchReservationRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(\n and(\n eq(reservations.provisionerId, params.provisionerId),\n inArray(reservations.id, reservationIds),\n ne(reservations.kind, 'launch'),\n ),\n )\n .for('update');\n const reservationsById = new Map(\n reservationRows.map((reservation) => [reservation.id, reservation]),\n );\n const nonLaunchReservationIds = new Set(\n nonLaunchReservationRows.map((reservation) => reservation.id),\n );\n const requestedReservationIds = new Set(reservationIds);\n const usedRunnerIdsByReservation = new Map<string, Set<string>>();\n const usedRunnerRows = await tx\n .select({\n id: providerRunners.id,\n reservationId: providerRunners.reservationId,\n intendedReservationId: providerRunners.intendedReservationId,\n })\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n isNull(providerRunners.reservationReleasedAt),\n or(\n and(\n inArray(providerRunners.reservationId, reservationIds),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n and(\n inArray(providerRunners.intendedReservationId, reservationIds),\n notInArray(providerRunners.state, [...terminalStates]),\n ),\n ),\n ),\n );\n for (const runner of usedRunnerRows) {\n if (runner.reservationId && requestedReservationIds.has(runner.reservationId)) {\n const usedRunnerIds =\n usedRunnerIdsByReservation.get(runner.reservationId) ?? new Set<string>();\n usedRunnerIds.add(runner.id);\n usedRunnerIdsByReservation.set(runner.reservationId, usedRunnerIds);\n }\n if (runner.intendedReservationId && requestedReservationIds.has(runner.intendedReservationId)) {\n const usedRunnerIds =\n usedRunnerIdsByReservation.get(runner.intendedReservationId) ?? new Set<string>();\n usedRunnerIds.add(runner.id);\n usedRunnerIdsByReservation.set(runner.intendedReservationId, usedRunnerIds);\n }\n }\n\n const acceptedByReservation = new Map<string, number>();\n const unavailableByReservation = new Map<\n string,\n {reason: RunnerReservationCapacityFailureReason; count: number}\n >();\n for (const reservationId of reservationIds) {\n const requested = requestedByReservation.get(reservationId) ?? 0;\n const reservation = reservationsById.get(reservationId);\n if (!reservation) {\n unavailableByReservation.set(reservationId, {\n reason: nonLaunchReservationIds.has(reservationId)\n ? 'reservation-kind-mismatch'\n : 'reservation-not-found',\n count: requested,\n });\n continue;\n }\n if (reservation.isExpired) {\n unavailableByReservation.set(reservationId, {\n reason: 'reservation-expired',\n count: requested,\n });\n continue;\n }\n\n const used = usedRunnerIdsByReservation.get(reservationId)?.size ?? 0;\n const accepted = Math.min(requested, Math.max(0, reservation.count - used));\n acceptedByReservation.set(reservationId, accepted);\n if (accepted < requested) {\n unavailableByReservation.set(reservationId, {\n reason: 'capacity-exhausted',\n count: requested - accepted,\n });\n }\n }\n\n return {acceptedByReservation, unavailableByReservation};\n}\n"],"names":["and","eq","inArray","isNull","ne","notInArray","or","sql","ReservationExpiredError","ReservationNotFoundError","RunnerInstanceAlreadyAssignedError","RunnerInstanceNotAssignableError","recordProviderRunnerControlSessionToAssignment","db","lockRunnerReservationAdvisoryKeysTx","terminalStates","reservations","runnerControlSessions","providerRunners","assignRunnerInstances","params","result","transaction","tx","assignRunnerInstancesTx","surface","observation","controlSessionToAssignment","runnerInstanceIds","sort","provisionerId","reservationIds","reservationId","runnerRows","select","id","intendedReservationId","workspaceId","assignedAt","providerRunnerId","provider","providerKind","launchKind","labels","state","from","where","for","length","every","runner","update","set","updatedAt","reservation","kind","limit","expiresAt","Date","activeControlSessions","runnerInstanceId","createdAt","map","closedAt","runnerInstanceIdsWithControlSession","Set","session","controlSessionCreatedAtByRunner","Map","runners","controlSessionId","has","controlSessionCreatedAt","get","alreadyAssigned","filter","some","newRunners","newRunnerIds","assignedCount","count","reservationReleasedAt","requiredLabels","label","includes","assignedRows","returning","runnersById","assigned","push","durationMilliseconds","getTime","validateRunnerReservationCapacityTx","options","requestedByReservation","request","requests","keys","acceptedByReservation","unavailableByReservation","advisoryLocksHeld","reservationRows","isExpired","nonLaunchReservationRows","reservationsById","nonLaunchReservationIds","requestedReservationIds","usedRunnerIdsByReservation","usedRunnerRows","usedRunnerIds","add","requested","reason","used","size","accepted","Math","min","max"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import type { RunnerInstance, RunnerInstanceState } from '#core/entities/runner-instance.js';
2
2
  import type { Tx } from './db.js';
3
3
  import { type RunnerInstanceBoundJobExecution } from './job-executions.js';
4
- export declare const activeStates: readonly ["starting", "running", "stopping"];
5
4
  export declare const divergenceCountStates: readonly ["starting", "running"];
6
5
  export type RunnerInstanceTerminateIntentReason = 'activation-timeout' | 'job-cancelled';
7
6
  export interface RunnerInstanceTerminateIntent {
@@ -1 +1 @@
1
- {"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"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"}
@@ -7,9 +7,9 @@ import { recordRunnerReservationCapacityFailure } from '#metrics/index.js';
7
7
  import { db } from './db.js';
8
8
  import { listRunningJobExecutionsByRunnerInstanceTx } from './job-executions.js';
9
9
  import { lockRunnerReservationAdvisoryKeysTx } from './reservation-locks.js';
10
- import { releaseReservationUnits } from './reservations.js';
10
+ import { releaseTerminalRunnerInstanceReservationsByIds } from './reservations.js';
11
11
  import { validateRunnerReservationCapacityTx } from './runner-assignments.js';
12
- import { terminalStates } from './runner-states.js';
12
+ import { activeStates, terminalStates } from './runner-states.js';
13
13
  import { ephemeralRegistrationTokens } from './schema/ephemeral-registration-tokens.js';
14
14
  import { provisionerTokens } from './schema/provisioner-tokens.js';
15
15
  import { reservations } from './schema/reservations.js';
@@ -17,11 +17,6 @@ import { runnerControlSessions } from './schema/runner-control-sessions.js';
17
17
  import { providerRunners, toRunnerInstance } from './schema/runner-instances.js';
18
18
  import { runnerSessions } from './schema/runner-sessions.js';
19
19
  import { runningJobExecutions } from './schema/running-job-executions.js';
20
- export const activeStates = [
21
- 'starting',
22
- 'running',
23
- 'stopping'
24
- ];
25
20
  export const divergenceCountStates = [
26
21
  'starting',
27
22
  'running'
@@ -52,6 +47,12 @@ export async function reportRunnerInstances(params) {
52
47
  }
53
48
  const events = await hydrateRunnerSessionIdsFromConsumedTokens(tx, params, aggregatedEvents);
54
49
  const reservationSafeEvents = await guardReportedReservationIdsTx(tx, params, events);
50
+ const existingReportedAtByProviderRunnerId = hasTerminalEvent ? await listExistingProviderRunnerReportedAtTx(tx, params, reservationSafeEvents) : new Map();
51
+ const freshTerminalEvents = reservationSafeEvents.filter((event)=>{
52
+ if (!isTerminalState(event.state)) return false;
53
+ const existingReportedAt = existingReportedAtByProviderRunnerId.get(event.providerRunnerId);
54
+ return existingReportedAt === undefined || event.reportedAt >= existingReportedAt;
55
+ });
55
56
  const terminateIntentsHonored = await listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, reservationSafeEvents);
56
57
  const values = reservationSafeEvents.map((event)=>({
57
58
  workspaceId: params.workspaceId,
@@ -102,7 +103,7 @@ export async function reportRunnerInstances(params) {
102
103
  OR ${providerRunnerMilestoneUpdateCondition()}
103
104
  `
104
105
  });
105
- const reservationsReleased = hasTerminalEvent ? await releaseTerminalRunnerInstanceReservations(tx, params, reservationSafeEvents) : 0;
106
+ const reservationsReleased = freshTerminalEvents.length > 0 ? await releaseTerminalRunnerInstanceReservations(tx, params, freshTerminalEvents) : 0;
106
107
  return {
107
108
  accepted: reservationSafeEvents.length,
108
109
  reservationsReleased,
@@ -110,18 +111,54 @@ export async function reportRunnerInstances(params) {
110
111
  };
111
112
  });
112
113
  }
114
+ async function listExistingProviderRunnerReportedAtTx(tx, params, events) {
115
+ const providerRunnerIds = [
116
+ ...new Set(events.map((event)=>event.providerRunnerId))
117
+ ];
118
+ if (providerRunnerIds.length === 0) return new Map();
119
+ const rows = await tx.select({
120
+ providerRunnerId: providerRunners.providerRunnerId,
121
+ reportedAt: providerRunners.reportedAt
122
+ }).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, providerRunnerIds)));
123
+ return new Map(rows.flatMap((row)=>row.providerRunnerId ? [
124
+ [
125
+ row.providerRunnerId,
126
+ row.reportedAt
127
+ ]
128
+ ] : []));
129
+ }
113
130
  async function guardReportedReservationIdsTx(tx, params, events) {
114
- const reservationIds = [
131
+ const reportedReservationIds = [
115
132
  ...new Set(events.flatMap((event)=>event.reservationId ? [
116
133
  event.reservationId
117
134
  ] : []))
118
135
  ].sort();
119
- if (reservationIds.length === 0) return events;
120
- await lockRunnerReservationAdvisoryKeysTx(tx, {
136
+ const providerRunnerIds = [
137
+ ...new Set(events.map((event)=>event.providerRunnerId))
138
+ ];
139
+ if (reportedReservationIds.length === 0 && !events.some((event)=>isTerminalState(event.state))) return events;
140
+ // Lock every reservation currently associated with the reported runners before taking a
141
+ // runner row lock. Terminal reports may omit reservationId, or carry a stale one; discovering
142
+ // the stored IDs here keeps report projection in the same reservation-then-runner order as
143
+ // assignment and terminal cleanup.
144
+ const existingReservationRows = await tx.select({
145
+ reservationId: providerRunners.reservationId,
146
+ intendedReservationId: providerRunners.intendedReservationId
147
+ }).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, providerRunnerIds)));
148
+ const storedReservationIds = existingReservationRows.flatMap((row)=>[
149
+ row.reservationId,
150
+ row.intendedReservationId
151
+ ].filter((reservationId)=>reservationId !== null));
152
+ const reservationIds = [
153
+ ...new Set([
154
+ ...reportedReservationIds,
155
+ ...storedReservationIds
156
+ ])
157
+ ].sort();
158
+ if (reservationIds.length > 0) await lockRunnerReservationAdvisoryKeysTx(tx, {
121
159
  provisionerId: params.provisionerId,
122
160
  reservationIds
123
161
  });
124
- const providerRunnerIds = events.map((event)=>event.providerRunnerId);
125
162
  const existingRows = await tx.select({
126
163
  providerRunnerId: providerRunners.providerRunnerId,
127
164
  reservationId: providerRunners.reservationId,
@@ -349,18 +386,18 @@ export async function reconcileRunnerInstances(params) {
349
386
  terminatedAt: sql`coalesce(${providerRunners.terminatedAt}, now())`,
350
387
  updatedAt: sql`now()`
351
388
  }).where(and(inArray(providerRunners.id, staleAbsentRows.map((row)=>row.id)), inArray(providerRunners.state, activeStates), lt(providerRunners.reportedAt, sql`now() - (${params.terminateGraceSeconds} || ' seconds')::interval`))).returning({
389
+ id: providerRunners.id,
352
390
  providerRunnerId: providerRunners.providerRunnerId
353
391
  });
354
392
  absentIds = updated.flatMap((row)=>row.providerRunnerId ? [
355
393
  row.providerRunnerId
356
394
  ] : []);
357
- if (params.workspaceId) {
358
- reservationsReleased = await releaseTerminalRunnerInstanceReservationsByIds(tx, {
359
- workspaceId: params.workspaceId,
360
- provisionerId: params.provisionerId,
361
- providerRunnerIds: absentIds
362
- });
363
- }
395
+ reservationsReleased = await releaseTerminalRunnerInstanceReservationsByIds(tx, {
396
+ workspaceId: params.workspaceId,
397
+ provisionerId: params.provisionerId,
398
+ runnerInstanceIds: updated.map((row)=>row.id),
399
+ requireUnlinkedSession: false
400
+ });
364
401
  }
365
402
  }
366
403
  const observedRows = observedRunnerInstanceIds.length === 0 ? [] : (await tx.select().from(providerRunners).where(and(params.workspaceId ? eq(providerRunners.workspaceId, params.workspaceId) : isNull(providerRunners.workspaceId), eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, observedRunnerInstanceIds)))).map(toRunnerInstance);
@@ -389,12 +426,21 @@ export async function reapStaleRunnerInstances(params) {
389
426
  provisionerId: providerRunners.provisionerId,
390
427
  providerRunnerId: providerRunners.providerRunnerId
391
428
  }).from(providerRunners).where(staleRunnerInstanceWhere(tx, cutoff)).orderBy(asc(providerRunners.updatedAt), asc(providerRunners.id)).limit(params.limit);
392
- if (candidateRows.length === 0) return {
429
+ const terminalReservationRows = candidateRows.length === 0 ? await tx.select({
430
+ id: providerRunners.id,
431
+ workspaceId: providerRunners.workspaceId,
432
+ provisionerId: providerRunners.provisionerId,
433
+ providerRunnerId: providerRunners.providerRunnerId
434
+ }).from(providerRunners).where(and(inArray(providerRunners.state, terminalStates), isNotNull(providerRunners.runnerSessionId), lt(providerRunners.updatedAt, cutoff), isNull(providerRunners.reservationReleasedAt), or(isNotNull(providerRunners.reservationId), isNotNull(providerRunners.intendedReservationId)))).orderBy(asc(providerRunners.updatedAt), asc(providerRunners.id)).limit(params.limit) : [];
435
+ if (candidateRows.length === 0 && terminalReservationRows.length === 0) return {
393
436
  reaped: 0,
394
437
  reservationsReleased: 0
395
438
  };
396
439
  const workspaceIds = [
397
- ...new Set(candidateRows.flatMap((row)=>row.workspaceId ? [
440
+ ...new Set([
441
+ ...candidateRows,
442
+ ...terminalReservationRows
443
+ ].flatMap((row)=>row.workspaceId ? [
398
444
  row.workspaceId
399
445
  ] : []))
400
446
  ].sort();
@@ -407,6 +453,7 @@ export async function reapStaleRunnerInstances(params) {
407
453
  failedAt: sql`coalesce(${providerRunners.failedAt}, now())`,
408
454
  updatedAt: sql`now()`
409
455
  }).where(and(inArray(providerRunners.id, candidateRows.map((row)=>row.id)), staleRunnerInstanceWhere(tx, cutoff))).returning({
456
+ id: providerRunners.id,
410
457
  workspaceId: providerRunners.workspaceId,
411
458
  provisionerId: providerRunners.provisionerId,
412
459
  providerRunnerId: providerRunners.providerRunnerId
@@ -416,7 +463,15 @@ export async function reapStaleRunnerInstances(params) {
416
463
  reservationsReleased += await releaseTerminalRunnerInstanceReservationsByIds(tx, {
417
464
  workspaceId: group.workspaceId,
418
465
  provisionerId: group.provisionerId,
419
- providerRunnerIds: group.providerRunnerIds,
466
+ runnerInstanceIds: group.runnerInstanceIds,
467
+ requireUnlinkedSession: false
468
+ });
469
+ }
470
+ for (const group of groupRunnerInstanceIds(terminalReservationRows)){
471
+ reservationsReleased += await releaseTerminalRunnerInstanceReservationsByIds(tx, {
472
+ workspaceId: group.workspaceId,
473
+ provisionerId: group.provisionerId,
474
+ runnerInstanceIds: group.runnerInstanceIds,
420
475
  requireUnlinkedSession: false
421
476
  });
422
477
  }
@@ -432,83 +487,14 @@ async function releaseTerminalRunnerInstanceReservations(tx, params, events) {
432
487
  return await releaseTerminalRunnerInstanceReservationsByIds(tx, {
433
488
  workspaceId: params.workspaceId,
434
489
  provisionerId: params.provisionerId,
435
- providerRunnerIds: terminalEvents.map((event)=>event.providerRunnerId)
490
+ providerRunnerIds: terminalEvents.map((event)=>event.providerRunnerId),
491
+ reportedAtByProviderRunnerId: new Map(terminalEvents.map((event)=>[
492
+ event.providerRunnerId,
493
+ event.reportedAt
494
+ ])),
495
+ requireUnlinkedSession: false
436
496
  });
437
497
  }
438
- async function releaseTerminalRunnerInstanceReservationsByIds(tx, params) {
439
- if (params.providerRunnerIds.length === 0) return 0;
440
- const reservationWorkspacePredicate = params.workspaceId === null ? sql`` : sql`and ${eq(reservations.workspaceId, params.workspaceId)}`;
441
- const rows = await tx.select({
442
- id: providerRunners.id,
443
- releaseReservationId: sql`coalesce(
444
- (select ${reservations.id}
445
- from ${reservations}
446
- where ${reservations.id} = ${providerRunners.intendedReservationId}
447
- and ${reservations.provisionerId} = ${params.provisionerId}
448
- ${reservationWorkspacePredicate}),
449
- (select ${reservations.id}
450
- from ${reservations}
451
- where ${reservations.id} = ${providerRunners.reservationId}
452
- and ${reservations.provisionerId} = ${params.provisionerId}
453
- ${reservationWorkspacePredicate})
454
- )`,
455
- releaseReservationWorkspaceId: sql`coalesce(
456
- (select ${reservations.workspaceId}
457
- from ${reservations}
458
- where ${reservations.id} = ${providerRunners.intendedReservationId}
459
- and ${reservations.provisionerId} = ${params.provisionerId}
460
- ${reservationWorkspacePredicate}),
461
- (select ${reservations.workspaceId}
462
- from ${reservations}
463
- where ${reservations.id} = ${providerRunners.reservationId}
464
- and ${reservations.provisionerId} = ${params.provisionerId}
465
- ${reservationWorkspacePredicate})
466
- )`
467
- }).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, params.providerRunnerIds), inArray(providerRunners.state, terminalStates), or(isNotNull(providerRunners.reservationId), isNotNull(providerRunners.intendedReservationId)), params.workspaceId === null ? undefined : or(and(eq(providerRunners.workspaceId, params.workspaceId), or(exists(tx.select({
468
- id: reservations.id
469
- }).from(reservations).where(and(eq(reservations.workspaceId, params.workspaceId), eq(reservations.provisionerId, params.provisionerId), eq(reservations.id, providerRunners.reservationId)))), exists(tx.select({
470
- id: reservations.id
471
- }).from(reservations).where(and(eq(reservations.workspaceId, params.workspaceId), eq(reservations.provisionerId, params.provisionerId), eq(reservations.id, providerRunners.intendedReservationId)))))), and(isNull(providerRunners.workspaceId), isNotNull(providerRunners.intendedReservationId), exists(tx.select({
472
- id: reservations.id
473
- }).from(reservations).where(and(eq(reservations.workspaceId, params.workspaceId), eq(reservations.provisionerId, params.provisionerId), eq(reservations.id, providerRunners.intendedReservationId)))))), params.requireUnlinkedSession === false ? undefined : isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt))).for('update');
474
- if (rows.length === 0) return 0;
475
- const updated = await tx.update(providerRunners).set({
476
- intendedReservationId: null,
477
- reservationReleasedAt: sql`now()`,
478
- updatedAt: sql`now()`
479
- }).where(and(inArray(providerRunners.id, rows.map((row)=>row.id)), params.requireUnlinkedSession === false ? undefined : isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt))).returning({
480
- id: providerRunners.id
481
- });
482
- const releasesByReservationId = new Map();
483
- const updatedIds = new Set(updated.map((row)=>row.id));
484
- for (const row of rows){
485
- if (!updatedIds.has(row.id)) continue;
486
- if (!row.releaseReservationId || !row.releaseReservationWorkspaceId) continue;
487
- const key = `${row.releaseReservationWorkspaceId}:${row.releaseReservationId}`;
488
- const release = releasesByReservationId.get(key) ?? {
489
- workspaceId: row.releaseReservationWorkspaceId,
490
- reservationId: row.releaseReservationId,
491
- count: 0
492
- };
493
- release.count += 1;
494
- releasesByReservationId.set(key, release);
495
- }
496
- if (releasesByReservationId.size === 0) return 0;
497
- let released = 0;
498
- for (const release of releasesByReservationId.values()){
499
- released += await releaseReservationUnits(tx, {
500
- workspaceId: release.workspaceId,
501
- provisionerId: params.provisionerId,
502
- releases: [
503
- {
504
- reservationId: release.reservationId,
505
- count: release.count
506
- }
507
- ]
508
- });
509
- }
510
- return released;
511
- }
512
498
  function staleRunnerInstanceCutoff(thresholdSeconds) {
513
499
  return sql`now() - (${thresholdSeconds} || ' seconds')::interval`;
514
500
  }
@@ -524,14 +510,13 @@ function staleRunnerInstanceWhere(tx, cutoff) {
524
510
  function groupRunnerInstanceIds(rows) {
525
511
  const groups = new Map();
526
512
  for (const row of rows){
527
- if (!row.providerRunnerId) continue;
528
513
  const key = `${row.workspaceId ?? ''}:${row.provisionerId}`;
529
514
  const group = groups.get(key) ?? {
530
515
  workspaceId: row.workspaceId,
531
516
  provisionerId: row.provisionerId,
532
- providerRunnerIds: []
517
+ runnerInstanceIds: []
533
518
  };
534
- group.providerRunnerIds.push(row.providerRunnerId);
519
+ group.runnerInstanceIds.push(row.id);
535
520
  groups.set(key, group);
536
521
  }
537
522
  return [