@shipfox/api-runners 12.3.0 → 12.4.1
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.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +36 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -2
- package/dist/config.js.map +1 -1
- package/dist/core/demand.d.ts +4 -2
- package/dist/core/demand.d.ts.map +1 -1
- package/dist/core/demand.js +22 -6
- package/dist/core/demand.js.map +1 -1
- package/dist/core/entities/reservation.d.ts +1 -0
- package/dist/core/entities/reservation.d.ts.map +1 -1
- package/dist/core/entities/reservation.js.map +1 -1
- package/dist/core/entities/runner-instance.d.ts +3 -0
- package/dist/core/entities/runner-instance.d.ts.map +1 -1
- package/dist/core/entities/runner-instance.js.map +1 -1
- package/dist/core/errors.d.ts +5 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/runner-activation.d.ts +4 -1
- package/dist/core/runner-activation.d.ts.map +1 -1
- package/dist/core/runner-activation.js +32 -3
- package/dist/core/runner-activation.js.map +1 -1
- package/dist/core/runner-control-sessions.d.ts +8 -4
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +116 -20
- package/dist/core/runner-control-sessions.js.map +1 -1
- package/dist/core/runner-instances.d.ts +1 -0
- package/dist/core/runner-instances.d.ts.map +1 -1
- package/dist/core/runner-instances.js +1 -0
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/db/db.d.ts +68 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +1 -1
- package/dist/db/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +35 -4
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/reservation-locks.d.ts +6 -0
- package/dist/db/reservation-locks.d.ts.map +1 -0
- package/dist/db/reservation-locks.js +15 -0
- package/dist/db/reservation-locks.js.map +1 -0
- package/dist/db/reservations.d.ts +17 -3
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +246 -51
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +25 -1
- package/dist/db/runner-assignments.d.ts.map +1 -1
- package/dist/db/runner-assignments.js +160 -18
- package/dist/db/runner-assignments.js.map +1 -1
- package/dist/db/runner-instances.d.ts +11 -2
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +167 -24
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +14 -1
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/runner-states.d.ts +2 -0
- package/dist/db/runner-states.d.ts.map +1 -0
- package/dist/db/runner-states.js +7 -0
- package/dist/db/runner-states.js.map +1 -0
- package/dist/db/schema/reservations.d.ts +18 -0
- package/dist/db/schema/reservations.d.ts.map +1 -1
- package/dist/db/schema/reservations.js +7 -1
- package/dist/db/schema/reservations.js.map +1 -1
- package/dist/db/schema/runner-instances.d.ts +18 -0
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +8 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -2
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +72 -2
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +161 -1
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +25 -4
- package/dist/metrics/service.js.map +1 -1
- package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
- package/dist/presentation/dto/poll-demand.js +3 -0
- package/dist/presentation/dto/poll-demand.js.map +1 -1
- package/dist/presentation/dto/runner-instances.d.ts +4 -1
- package/dist/presentation/dto/runner-instances.d.ts.map +1 -1
- package/dist/presentation/dto/runner-instances.js +4 -1
- package/dist/presentation/dto/runner-instances.js.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.js +10 -4
- package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +1 -0
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/reconcile-runner-instances.js +4 -2
- package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -1
- package/dist/presentation/routes/runner-enrollment.d.ts +4 -1
- package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
- package/dist/presentation/routes/runner-enrollment.js +11 -6
- package/dist/presentation/routes/runner-enrollment.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0005_previous_lenny_balinger.sql +5 -0
- package/drizzle/meta/0005_snapshot.json +2220 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/config.test.ts +22 -0
- package/src/config.ts +15 -2
- package/src/core/demand.test.ts +192 -3
- package/src/core/demand.ts +44 -4
- package/src/core/entities/reservation.ts +1 -0
- package/src/core/entities/runner-instance.ts +4 -0
- package/src/core/errors.ts +30 -1
- package/src/core/runner-activation.ts +48 -2
- package/src/core/runner-control-sessions.test.ts +135 -3
- package/src/core/runner-control-sessions.ts +141 -19
- package/src/core/runner-instances.test.ts +19 -0
- package/src/core/runner-instances.ts +2 -0
- package/src/core/runner-sessions.test.ts +121 -1
- package/src/db/ephemeral-registration-tokens.test.ts +20 -1
- package/src/db/ephemeral-registration-tokens.ts +1 -0
- package/src/db/job-executions.test.ts +98 -12
- package/src/db/job-executions.ts +52 -4
- package/src/db/reservation-locks.ts +22 -0
- package/src/db/reservations.test.ts +733 -22
- package/src/db/reservations.ts +408 -144
- package/src/db/runner-assignments.test.ts +105 -1
- package/src/db/runner-assignments.ts +245 -25
- package/src/db/runner-instances.test.ts +357 -2
- package/src/db/runner-instances.ts +289 -55
- package/src/db/runner-sessions.ts +19 -1
- package/src/db/runner-states.ts +7 -0
- package/src/db/schema/reservations.ts +5 -1
- package/src/db/schema/runner-instances.ts +9 -0
- package/src/metrics/index.ts +24 -1
- package/src/metrics/instance.test.ts +118 -0
- package/src/metrics/instance.ts +247 -3
- package/src/metrics/service.test.ts +46 -1
- package/src/metrics/service.ts +55 -8
- package/src/presentation/dto/poll-demand.ts +3 -0
- package/src/presentation/dto/runner-instances.ts +8 -1
- package/src/presentation/routes/assign-runner-instances.ts +14 -2
- package/src/presentation/routes/late-runner-enrollment.test.ts +219 -2
- package/src/presentation/routes/poll-demand.test.ts +56 -0
- package/src/presentation/routes/poll-demand.ts +1 -0
- package/src/presentation/routes/reconcile-runner-instances.test.ts +34 -2
- package/src/presentation/routes/reconcile-runner-instances.ts +7 -1
- package/src/presentation/routes/runner-enrollment.test.ts +316 -3
- package/src/presentation/routes/runner-enrollment.ts +8 -5
- package/test/factories/reservation.ts +2 -0
- package/test/factories/runner-instance.ts +6 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
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 terminalStates: readonly ["stopped", "failed", "terminated"];
|
|
5
4
|
export declare const activeStates: readonly ["starting", "running", "stopping"];
|
|
6
5
|
export declare const divergenceCountStates: readonly ["starting", "running"];
|
|
7
|
-
export type RunnerInstanceTerminateIntentReason = 'job-cancelled';
|
|
6
|
+
export type RunnerInstanceTerminateIntentReason = 'activation-timeout' | 'job-cancelled';
|
|
8
7
|
export interface RunnerInstanceTerminateIntent {
|
|
9
8
|
providerRunnerId: string;
|
|
10
9
|
reason: RunnerInstanceTerminateIntentReason;
|
|
10
|
+
activationTimeoutRetry?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export interface ActiveRunnerInstanceTemplateCount {
|
|
13
13
|
templateKey: string;
|
|
@@ -64,6 +64,15 @@ export declare function listActiveRunnerInstanceCountsByTemplateTx(tx: Tx, param
|
|
|
64
64
|
export declare function countStaleEnrolledRunnerInstances(params: {
|
|
65
65
|
graceSeconds: number;
|
|
66
66
|
}): Promise<number>;
|
|
67
|
+
export type ProviderRunnerPhase = 'control_session' | 'enrollment' | 'assignment' | 'activation' | 'idle';
|
|
68
|
+
export interface ProviderRunnerPhaseMetric {
|
|
69
|
+
phase: ProviderRunnerPhase;
|
|
70
|
+
provider: string;
|
|
71
|
+
launchKind: 'demand' | 'warm' | 'manual';
|
|
72
|
+
count: number;
|
|
73
|
+
oldestAgeMilliseconds: number;
|
|
74
|
+
}
|
|
75
|
+
export declare function listProviderRunnerByPhaseMetrics(): Promise<ProviderRunnerPhaseMetric[]>;
|
|
67
76
|
export declare function listActiveRunnerInstances(params: {
|
|
68
77
|
workspaceId: string;
|
|
69
78
|
windowSeconds: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
2
|
import { and, asc, desc, eq, exists, gt, inArray, isNotNull, isNull, lt, notExists, notInArray, or, sql } from 'drizzle-orm';
|
|
3
3
|
import { alias } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { config } from '#config.js';
|
|
4
5
|
import { sanitizeRunnerLabels } from '#core/runner-labels.js';
|
|
6
|
+
import { recordRunnerReservationCapacityFailure } from '#metrics/index.js';
|
|
5
7
|
import { db } from './db.js';
|
|
6
8
|
import { listRunningJobExecutionsByRunnerInstanceTx } from './job-executions.js';
|
|
9
|
+
import { lockRunnerReservationAdvisoryKeysTx } from './reservation-locks.js';
|
|
7
10
|
import { releaseReservationUnits } from './reservations.js';
|
|
11
|
+
import { validateRunnerReservationCapacityTx } from './runner-assignments.js';
|
|
12
|
+
import { terminalStates } from './runner-states.js';
|
|
8
13
|
import { ephemeralRegistrationTokens } from './schema/ephemeral-registration-tokens.js';
|
|
9
14
|
import { provisionerTokens } from './schema/provisioner-tokens.js';
|
|
10
15
|
import { reservations } from './schema/reservations.js';
|
|
@@ -12,11 +17,6 @@ import { runnerControlSessions } from './schema/runner-control-sessions.js';
|
|
|
12
17
|
import { providerRunners, toRunnerInstance } from './schema/runner-instances.js';
|
|
13
18
|
import { runnerSessions } from './schema/runner-sessions.js';
|
|
14
19
|
import { runningJobExecutions } from './schema/running-job-executions.js';
|
|
15
|
-
export const terminalStates = [
|
|
16
|
-
'stopped',
|
|
17
|
-
'failed',
|
|
18
|
-
'terminated'
|
|
19
|
-
];
|
|
20
20
|
export const activeStates = [
|
|
21
21
|
'starting',
|
|
22
22
|
'running',
|
|
@@ -51,8 +51,9 @@ export async function reportRunnerInstances(params) {
|
|
|
51
51
|
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId ?? params.provisionerId}))`);
|
|
52
52
|
}
|
|
53
53
|
const events = await hydrateRunnerSessionIdsFromConsumedTokens(tx, params, aggregatedEvents);
|
|
54
|
-
const
|
|
55
|
-
const
|
|
54
|
+
const reservationSafeEvents = await guardReportedReservationIdsTx(tx, params, events);
|
|
55
|
+
const terminateIntentsHonored = await listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, reservationSafeEvents);
|
|
56
|
+
const values = reservationSafeEvents.map((event)=>({
|
|
56
57
|
workspaceId: params.workspaceId,
|
|
57
58
|
provisionerId: params.provisionerId,
|
|
58
59
|
providerRunnerId: event.providerRunnerId,
|
|
@@ -101,14 +102,92 @@ export async function reportRunnerInstances(params) {
|
|
|
101
102
|
OR ${providerRunnerMilestoneUpdateCondition()}
|
|
102
103
|
`
|
|
103
104
|
});
|
|
104
|
-
const reservationsReleased = hasTerminalEvent ? await releaseTerminalRunnerInstanceReservations(tx, params,
|
|
105
|
+
const reservationsReleased = hasTerminalEvent ? await releaseTerminalRunnerInstanceReservations(tx, params, reservationSafeEvents) : 0;
|
|
105
106
|
return {
|
|
106
|
-
accepted:
|
|
107
|
+
accepted: reservationSafeEvents.length,
|
|
107
108
|
reservationsReleased,
|
|
108
109
|
terminateIntentsHonored
|
|
109
110
|
};
|
|
110
111
|
});
|
|
111
112
|
}
|
|
113
|
+
async function guardReportedReservationIdsTx(tx, params, events) {
|
|
114
|
+
const reservationIds = [
|
|
115
|
+
...new Set(events.flatMap((event)=>event.reservationId ? [
|
|
116
|
+
event.reservationId
|
|
117
|
+
] : []))
|
|
118
|
+
].sort();
|
|
119
|
+
if (reservationIds.length === 0) return events;
|
|
120
|
+
await lockRunnerReservationAdvisoryKeysTx(tx, {
|
|
121
|
+
provisionerId: params.provisionerId,
|
|
122
|
+
reservationIds
|
|
123
|
+
});
|
|
124
|
+
const providerRunnerIds = events.map((event)=>event.providerRunnerId);
|
|
125
|
+
const existingRows = await tx.select({
|
|
126
|
+
providerRunnerId: providerRunners.providerRunnerId,
|
|
127
|
+
reservationId: providerRunners.reservationId,
|
|
128
|
+
intendedReservationId: providerRunners.intendedReservationId,
|
|
129
|
+
reservationReleasedAt: providerRunners.reservationReleasedAt
|
|
130
|
+
}).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, providerRunnerIds))).for('update');
|
|
131
|
+
const existingByProviderRunnerId = new Map(existingRows.flatMap((row)=>row.providerRunnerId ? [
|
|
132
|
+
[
|
|
133
|
+
row.providerRunnerId,
|
|
134
|
+
row
|
|
135
|
+
]
|
|
136
|
+
] : []));
|
|
137
|
+
const candidateReservationByProviderRunnerId = new Map();
|
|
138
|
+
for (const event of events){
|
|
139
|
+
if (!event.reservationId) continue;
|
|
140
|
+
if (isTerminalState(event.state)) continue;
|
|
141
|
+
const existing = existingByProviderRunnerId.get(event.providerRunnerId);
|
|
142
|
+
if (existing?.reservationId || existing?.intendedReservationId === event.reservationId) continue;
|
|
143
|
+
if (existing?.intendedReservationId || existing?.reservationReleasedAt) continue;
|
|
144
|
+
candidateReservationByProviderRunnerId.set(event.providerRunnerId, event.reservationId);
|
|
145
|
+
}
|
|
146
|
+
const validation = await validateRunnerReservationCapacityTx(tx, {
|
|
147
|
+
provisionerId: params.provisionerId,
|
|
148
|
+
requests: [
|
|
149
|
+
...candidateReservationByProviderRunnerId.values()
|
|
150
|
+
].map((reservationId)=>({
|
|
151
|
+
reservationId,
|
|
152
|
+
count: 1
|
|
153
|
+
}))
|
|
154
|
+
}, {
|
|
155
|
+
advisoryLocksHeld: true
|
|
156
|
+
});
|
|
157
|
+
for (const { reason, count } of validation.unavailableByReservation.values())recordRunnerReservationCapacityFailure(reason, count);
|
|
158
|
+
const remainingAcceptedByReservation = new Map(validation.acceptedByReservation);
|
|
159
|
+
return events.map((event)=>{
|
|
160
|
+
const existing = existingByProviderRunnerId.get(event.providerRunnerId);
|
|
161
|
+
const reservationId = candidateReservationByProviderRunnerId.get(event.providerRunnerId);
|
|
162
|
+
if (!reservationId) {
|
|
163
|
+
if (event.reservationId && isTerminalState(event.state)) {
|
|
164
|
+
// Terminal cleanup releases the IDs stored on the projection row. An arbitrary
|
|
165
|
+
// terminal report must not consume reservation capacity during that cleanup.
|
|
166
|
+
const matchesExistingReservation = existing !== undefined && (existing.reservationId === event.reservationId || existing.intendedReservationId === event.reservationId);
|
|
167
|
+
if (!matchesExistingReservation) return {
|
|
168
|
+
...event,
|
|
169
|
+
reservationId: null
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (event.reservationId && existing?.intendedReservationId && existing.intendedReservationId !== event.reservationId) return {
|
|
173
|
+
...event,
|
|
174
|
+
reservationId: null
|
|
175
|
+
};
|
|
176
|
+
if (event.reservationId && existing?.reservationReleasedAt) return {
|
|
177
|
+
...event,
|
|
178
|
+
reservationId: null
|
|
179
|
+
};
|
|
180
|
+
return event;
|
|
181
|
+
}
|
|
182
|
+
const remaining = remainingAcceptedByReservation.get(reservationId) ?? 0;
|
|
183
|
+
if (remaining === 0) return {
|
|
184
|
+
...event,
|
|
185
|
+
reservationId: null
|
|
186
|
+
};
|
|
187
|
+
remainingAcceptedByReservation.set(reservationId, remaining - 1);
|
|
188
|
+
return event;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
112
191
|
export async function listActiveRunnerInstanceCountsByTemplateTx(tx, params) {
|
|
113
192
|
const rows = await tx.select({
|
|
114
193
|
templateKey: providerRunners.templateKey,
|
|
@@ -131,6 +210,49 @@ export async function countStaleEnrolledRunnerInstances(params) {
|
|
|
131
210
|
}).from(runnerControlSessions).where(and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`))))));
|
|
132
211
|
return row?.count ?? 0;
|
|
133
212
|
}
|
|
213
|
+
export async function listProviderRunnerByPhaseMetrics() {
|
|
214
|
+
const phase = sql`case
|
|
215
|
+
when ${runnerControlSessions.id} is null then 'control_session'
|
|
216
|
+
when ${providerRunners.state} <> 'running' then 'enrollment'
|
|
217
|
+
when ${providerRunners.intendedReservationId} is not null
|
|
218
|
+
and ${providerRunners.workspaceId} is null then 'assignment'
|
|
219
|
+
when ${providerRunners.workspaceId} is not null then 'activation'
|
|
220
|
+
else 'idle'
|
|
221
|
+
end`;
|
|
222
|
+
const startedAt = sql`case
|
|
223
|
+
when ${runnerControlSessions.id} is null then ${providerRunners.createdAt}
|
|
224
|
+
when ${providerRunners.state} <> 'running' then ${runnerControlSessions.createdAt}
|
|
225
|
+
when ${providerRunners.intendedReservationId} is not null
|
|
226
|
+
and ${providerRunners.workspaceId} is null then ${runnerControlSessions.createdAt}
|
|
227
|
+
when ${providerRunners.workspaceId} is not null
|
|
228
|
+
then coalesce(${providerRunners.assignedAt}, ${runnerControlSessions.createdAt})
|
|
229
|
+
else coalesce(
|
|
230
|
+
${runnerControlSessions.createdAt},
|
|
231
|
+
${providerRunners.assignedAt},
|
|
232
|
+
${providerRunners.createdAt}
|
|
233
|
+
)
|
|
234
|
+
end`;
|
|
235
|
+
const rows = await db().select({
|
|
236
|
+
phase,
|
|
237
|
+
provider: sql`coalesce(${providerRunners.providerKind}, 'unknown')`,
|
|
238
|
+
launchKind: providerRunners.launchKind,
|
|
239
|
+
count: sql`count(*)::int`,
|
|
240
|
+
oldestAgeMilliseconds: sql`coalesce(
|
|
241
|
+
max(extract(epoch from (now() - (${startedAt}))) * 1000),
|
|
242
|
+
0
|
|
243
|
+
)::double precision`
|
|
244
|
+
}).from(providerRunners).leftJoin(runnerControlSessions, and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`))).where(and(inArray(providerRunners.state, [
|
|
245
|
+
'starting',
|
|
246
|
+
'running'
|
|
247
|
+
]), isNull(providerRunners.runnerSessionId))).groupBy(phase, providerRunners.providerKind, providerRunners.launchKind);
|
|
248
|
+
return rows.map((row)=>({
|
|
249
|
+
phase: row.phase,
|
|
250
|
+
provider: row.provider,
|
|
251
|
+
launchKind: row.launchKind,
|
|
252
|
+
count: row.count,
|
|
253
|
+
oldestAgeMilliseconds: Math.max(0, row.oldestAgeMilliseconds)
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
134
256
|
export async function listActiveRunnerInstances(params) {
|
|
135
257
|
const rows = await db().select().from(providerRunners).where(and(eq(providerRunners.workspaceId, params.workspaceId), inArray(providerRunners.state, activeStates), sql`${providerRunners.updatedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`)).orderBy(desc(providerRunners.updatedAt), desc(providerRunners.id)).limit(params.limit ?? 1000);
|
|
136
258
|
return rows.map(toRunnerInstance);
|
|
@@ -153,21 +275,47 @@ export async function listProvisionerTerminateIntentRowsTx(tx, params) {
|
|
|
153
275
|
returnedCount: returnedRows.length
|
|
154
276
|
}, 'provisioner terminate intents truncated by poll-demand limit');
|
|
155
277
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
...row,
|
|
159
|
-
providerRunnerId: row.providerRunnerId
|
|
160
|
-
}
|
|
278
|
+
const activationTimeoutRunnerIds = returnedRows.flatMap((row)=>row.reason === 'activation-timeout' && row.providerRunnerId ? [
|
|
279
|
+
row.providerRunnerId
|
|
161
280
|
] : []);
|
|
281
|
+
if (activationTimeoutRunnerIds.length > 0) {
|
|
282
|
+
await tx.update(providerRunners).set({
|
|
283
|
+
reservationReleasedAt: sql`now()`,
|
|
284
|
+
updatedAt: sql`now()`
|
|
285
|
+
}).where(and(eq(providerRunners.workspaceId, params.workspaceId), eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, activationTimeoutRunnerIds), isNull(providerRunners.reservationReleasedAt)));
|
|
286
|
+
}
|
|
287
|
+
return returnedRows.flatMap((row)=>toRunnerInstanceTerminateIntent(row));
|
|
288
|
+
}
|
|
289
|
+
function toRunnerInstanceTerminateIntent(row) {
|
|
290
|
+
if (!row.providerRunnerId) return [];
|
|
291
|
+
return [
|
|
292
|
+
{
|
|
293
|
+
providerRunnerId: row.providerRunnerId,
|
|
294
|
+
reason: row.reason,
|
|
295
|
+
...row.reason === 'activation-timeout' && row.activationTimeoutRetry ? {
|
|
296
|
+
activationTimeoutRetry: true
|
|
297
|
+
} : {}
|
|
298
|
+
}
|
|
299
|
+
];
|
|
162
300
|
}
|
|
163
301
|
function provisionerTerminateIntentsQuery(tx, params) {
|
|
164
302
|
const newerRunningJobExecutions = alias(runningJobExecutions, 'newer_running_jobs');
|
|
303
|
+
const latestCancelledJob = exists(tx.select({
|
|
304
|
+
id: runningJobExecutions.id
|
|
305
|
+
}).from(runningJobExecutions).where(and(eq(runningJobExecutions.workspaceId, params.workspaceId), eq(runningJobExecutions.provisionerId, params.provisionerId), eq(runningJobExecutions.providerRunnerId, providerRunners.providerRunnerId), isNotNull(runningJobExecutions.cancellationRequestedAt), notExists(tx.select({
|
|
306
|
+
id: newerRunningJobExecutions.id
|
|
307
|
+
}).from(newerRunningJobExecutions).where(and(eq(newerRunningJobExecutions.workspaceId, runningJobExecutions.workspaceId), eq(newerRunningJobExecutions.provisionerId, runningJobExecutions.provisionerId), eq(newerRunningJobExecutions.providerRunnerId, runningJobExecutions.providerRunnerId), or(gt(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), and(eq(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), gt(newerRunningJobExecutions.jobExecutionId, runningJobExecutions.jobExecutionId)))))))));
|
|
308
|
+
const activationTimeout = and(eq(providerRunners.launchKind, 'demand'), isNull(providerRunners.runnerSessionId), lt(providerRunners.createdAt, sql`now() - (${config.RUNNER_DEMAND_ACTIVATION_TIMEOUT_SECONDS} || ' seconds')::interval`), notExists(tx.select({
|
|
309
|
+
id: reservations.id
|
|
310
|
+
}).from(reservations).where(and(or(eq(reservations.id, providerRunners.reservationId), eq(reservations.id, providerRunners.intendedReservationId)), eq(reservations.workspaceId, params.workspaceId), eq(reservations.provisionerId, params.provisionerId), gt(reservations.expiresAt, sql`now()`)))));
|
|
165
311
|
return tx.select({
|
|
166
312
|
providerRunnerId: providerRunners.providerRunnerId,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
313
|
+
activationTimeoutRetry: sql`${providerRunners.reservationReleasedAt} is not null`,
|
|
314
|
+
reason: sql`case
|
|
315
|
+
when ${activationTimeout} then 'activation-timeout'
|
|
316
|
+
else 'job-cancelled'
|
|
317
|
+
end`
|
|
318
|
+
}).from(providerRunners).where(and(eq(providerRunners.workspaceId, params.workspaceId), eq(providerRunners.provisionerId, params.provisionerId), isNotNull(providerRunners.providerRunnerId), inArray(providerRunners.state, activeStates), params.providerRunnerIds && params.providerRunnerIds.length > 0 ? inArray(providerRunners.providerRunnerId, params.providerRunnerIds) : undefined, or(latestCancelledJob, activationTimeout)));
|
|
171
319
|
}
|
|
172
320
|
async function listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, events) {
|
|
173
321
|
if (!params.workspaceId) return [];
|
|
@@ -180,12 +328,7 @@ async function listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, even
|
|
|
180
328
|
provisionerId: params.provisionerId,
|
|
181
329
|
providerRunnerIds: terminatedRunnerInstanceIds
|
|
182
330
|
}).orderBy(asc(providerRunners.providerRunnerId));
|
|
183
|
-
return rows.flatMap((row)=>row
|
|
184
|
-
{
|
|
185
|
-
...row,
|
|
186
|
-
providerRunnerId: row.providerRunnerId
|
|
187
|
-
}
|
|
188
|
-
] : []);
|
|
331
|
+
return rows.flatMap((row)=>toRunnerInstanceTerminateIntent(row));
|
|
189
332
|
}
|
|
190
333
|
export async function reconcileRunnerInstances(params) {
|
|
191
334
|
const observedRunnerInstanceIds = [
|