deepline 0.3.200 → 0.3.202
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/dist/bundling-sources/sdk/src/client.ts +62 -1
- package/dist/bundling-sources/sdk/src/http.ts +55 -6
- package/dist/bundling-sources/sdk/src/index.ts +1 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +10 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +4 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +460 -96
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -1
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +401 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +458 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-no-progress.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +25 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +119 -171
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-work-counters.ts +37 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +160 -58
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +111 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +51 -46
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +16 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +304 -33
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +64 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +133 -5
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +5 -5
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/result-receipt-policy.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +14 -5
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +3 -9
- package/dist/cli/index.js +364 -39
- package/dist/cli/index.mjs +367 -41
- package/dist/index.d.mts +131 -3
- package/dist/index.d.ts +131 -3
- package/dist/index.js +64 -7
- package/dist/index.mjs +64 -7
- package/dist/install-integrity.json +3 -0
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
abandonUnstartedModalCancellationReservations,
|
|
3
|
+
cancelPostgresSchedulerRun,
|
|
4
|
+
enqueuePostgresSchedulerSandboxCleanupForRun,
|
|
5
|
+
hasPendingPostgresSchedulerSandboxCleanupForRun,
|
|
6
|
+
readPostgresSchedulerResult,
|
|
7
|
+
} from './postgres';
|
|
2
8
|
import type {
|
|
3
9
|
PostgresSchedulerOptions,
|
|
4
10
|
PostgresSchedulerQueryClient,
|
|
@@ -14,31 +20,100 @@ import { resolveRuntimeSchedulerSchemaEnv } from './runtime-scheduler-schema';
|
|
|
14
20
|
|
|
15
21
|
/** Bound deadline reconciliation just like Absurd's own cancellation sweep. */
|
|
16
22
|
export const ABSURD_CANCELLED_DEADLINE_RECONCILE_LIMIT = 100;
|
|
23
|
+
const QUEUED_CANCELLATION_RECONCILE_LIMIT = 20;
|
|
17
24
|
|
|
18
25
|
function quoteAbsurdTable(table: string): string {
|
|
19
26
|
return `"${table.replaceAll('"', '""')}"`;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
async function finalizeCancelledRunAfterCleanup(input: {
|
|
30
|
+
client: PostgresSchedulerQueryClient;
|
|
31
|
+
runId: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
schedulerSchema: string;
|
|
34
|
+
schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
35
|
+
controlKey: string;
|
|
36
|
+
}): Promise<'pending' | 'cancelled' | 'already_terminal'> {
|
|
37
|
+
await input.client.query('BEGIN');
|
|
38
|
+
try {
|
|
39
|
+
await input.client.query(
|
|
40
|
+
`SELECT set_config('lock_timeout', $1, true),
|
|
41
|
+
set_config('statement_timeout', $2, true)`,
|
|
42
|
+
[
|
|
43
|
+
`${RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS}ms`,
|
|
44
|
+
`${RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS}ms`,
|
|
45
|
+
],
|
|
46
|
+
);
|
|
47
|
+
// Sandbox admission locks the run before reserving an operation; resource
|
|
48
|
+
// writers lock attempts. Hold both through the final cleanup check.
|
|
49
|
+
await input.client.query(
|
|
50
|
+
`SELECT run_id FROM ${input.schedulerSchema}.runs
|
|
51
|
+
WHERE run_id = $1 FOR UPDATE`,
|
|
52
|
+
[input.runId],
|
|
53
|
+
);
|
|
54
|
+
await input.client.query(
|
|
55
|
+
`SELECT attempt FROM ${input.schedulerSchema}.attempts
|
|
56
|
+
WHERE run_id = $1 FOR UPDATE`,
|
|
57
|
+
[input.runId],
|
|
58
|
+
);
|
|
59
|
+
await abandonUnstartedModalCancellationReservations(
|
|
60
|
+
input.client,
|
|
61
|
+
{ runId: input.runId },
|
|
62
|
+
input.schedulerOptions,
|
|
63
|
+
);
|
|
64
|
+
await enqueuePostgresSchedulerSandboxCleanupForRun(
|
|
65
|
+
input.client,
|
|
66
|
+
{ runId: input.runId },
|
|
67
|
+
input.schedulerOptions,
|
|
68
|
+
);
|
|
69
|
+
if (
|
|
70
|
+
await hasPendingPostgresSchedulerSandboxCleanupForRun(
|
|
71
|
+
input.client,
|
|
72
|
+
{ runId: input.runId },
|
|
73
|
+
input.schedulerOptions,
|
|
74
|
+
)
|
|
75
|
+
) {
|
|
76
|
+
await input.client.query('COMMIT');
|
|
77
|
+
return 'pending';
|
|
78
|
+
}
|
|
79
|
+
const cancelled = await cancelPostgresSchedulerRun(
|
|
80
|
+
input.client,
|
|
81
|
+
{ runId: input.runId, reason: input.reason },
|
|
82
|
+
input.schedulerOptions,
|
|
83
|
+
);
|
|
84
|
+
if (
|
|
85
|
+
!cancelled &&
|
|
86
|
+
!(await readPostgresSchedulerResult(
|
|
87
|
+
input.client,
|
|
88
|
+
input.runId,
|
|
89
|
+
input.schedulerOptions,
|
|
90
|
+
))
|
|
91
|
+
) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Cancellation did not terminalize scheduler run "${input.runId}".`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
await input.client.query(
|
|
97
|
+
`DELETE FROM ${input.schedulerSchema}.control WHERE key = $1`,
|
|
98
|
+
[input.controlKey],
|
|
99
|
+
);
|
|
100
|
+
await input.client.query('COMMIT');
|
|
101
|
+
return cancelled ? 'cancelled' : 'already_terminal';
|
|
102
|
+
} catch (error) {
|
|
103
|
+
await input.client.query('ROLLBACK').catch(() => undefined);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Put legacy manual and deadline cancellations into the same durable queue
|
|
109
|
+
* as caller requests. A queued task is excluded from later scans, so one
|
|
110
|
+
* pending sandbox cannot hide other cancelled tasks behind the batch limit. */
|
|
36
111
|
export async function reconcileAbsurdRuntimeCancellations(input: {
|
|
37
112
|
client: PostgresSchedulerQueryClient;
|
|
38
113
|
queue: string;
|
|
39
114
|
schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
40
115
|
limit?: number;
|
|
41
|
-
}): Promise<{
|
|
116
|
+
}): Promise<{ queued: number }> {
|
|
42
117
|
const requestedLimit = Math.floor(
|
|
43
118
|
input.limit ?? ABSURD_CANCELLED_DEADLINE_RECONCILE_LIMIT,
|
|
44
119
|
);
|
|
@@ -54,16 +129,21 @@ export async function reconcileAbsurdRuntimeCancellations(input: {
|
|
|
54
129
|
resolveRuntimeSchedulerSchemaEnv() ||
|
|
55
130
|
'runtime_scheduler',
|
|
56
131
|
);
|
|
57
|
-
const
|
|
58
|
-
run_id: string | null;
|
|
59
|
-
cancellation_kind: 'manual' | 'deadline';
|
|
60
|
-
}>(
|
|
132
|
+
const queued = await input.client.query<{ key: string }>(
|
|
61
133
|
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
134
|
+
INSERT INTO ${schedulerSchema}.control (key, value)
|
|
135
|
+
SELECT 'runtime_cancel:' || run.run_id,
|
|
136
|
+
jsonb_build_object(
|
|
137
|
+
'reason', CASE
|
|
138
|
+
WHEN task.cancellation ? 'manual'
|
|
139
|
+
THEN COALESCE(
|
|
140
|
+
NULLIF(task.cancellation->>'reason', ''),
|
|
141
|
+
'Run cancellation was requested by the caller.'
|
|
142
|
+
)
|
|
143
|
+
ELSE 'Run did not start within its configured scheduler queue deadline.'
|
|
144
|
+
END,
|
|
145
|
+
'requestedAt', clock_timestamp()
|
|
146
|
+
)
|
|
67
147
|
FROM ${schedulerSchema}.runs AS run
|
|
68
148
|
JOIN absurd.${taskTable} AS task
|
|
69
149
|
ON task.task_id::text = run.absurd_task_id
|
|
@@ -78,57 +158,79 @@ export async function reconcileAbsurdRuntimeCancellations(input: {
|
|
|
78
158
|
AND task.cancelled_at >= task.cancel_at
|
|
79
159
|
)
|
|
80
160
|
)
|
|
161
|
+
AND NOT EXISTS (
|
|
162
|
+
SELECT 1 FROM ${schedulerSchema}.control AS pending
|
|
163
|
+
WHERE pending.key = 'runtime_cancel:' || run.run_id
|
|
164
|
+
)
|
|
81
165
|
ORDER BY task.cancelled_at ASC NULLS LAST, task.task_id ASC
|
|
82
166
|
LIMIT $2
|
|
167
|
+
ON CONFLICT (key) DO NOTHING
|
|
168
|
+
RETURNING key
|
|
83
169
|
`,
|
|
84
170
|
[ABSURD_PLAY_RUN_TASK, limit],
|
|
85
171
|
);
|
|
172
|
+
return { queued: queued.rows.length };
|
|
173
|
+
}
|
|
86
174
|
|
|
175
|
+
/** Drain caller-requested cancellations independently of their release lane. */
|
|
176
|
+
export async function reconcileQueuedAbsurdRuntimeCancellations(input: {
|
|
177
|
+
client: PostgresSchedulerQueryClient;
|
|
178
|
+
schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
179
|
+
limit?: number;
|
|
180
|
+
}): Promise<{ reconciled: number; skipped: number }> {
|
|
181
|
+
const schedulerSchema = quoteAbsurdTable(
|
|
182
|
+
input.schedulerOptions?.schema?.trim() ||
|
|
183
|
+
resolveRuntimeSchedulerSchemaEnv() ||
|
|
184
|
+
'runtime_scheduler',
|
|
185
|
+
);
|
|
186
|
+
const requestedLimit = Math.floor(
|
|
187
|
+
input.limit ?? QUEUED_CANCELLATION_RECONCILE_LIMIT,
|
|
188
|
+
);
|
|
189
|
+
const limit = Number.isFinite(requestedLimit)
|
|
190
|
+
? Math.max(1, Math.min(QUEUED_CANCELLATION_RECONCILE_LIMIT, requestedLimit))
|
|
191
|
+
: QUEUED_CANCELLATION_RECONCILE_LIMIT;
|
|
192
|
+
const queued = await input.client.query<{
|
|
193
|
+
key: string;
|
|
194
|
+
reason: string | null;
|
|
195
|
+
}>(
|
|
196
|
+
`WITH selected AS (
|
|
197
|
+
SELECT key FROM ${schedulerSchema}.control
|
|
198
|
+
WHERE starts_with(key, 'runtime_cancel:')
|
|
199
|
+
ORDER BY updated_at ASC, key ASC
|
|
200
|
+
LIMIT $1
|
|
201
|
+
FOR UPDATE SKIP LOCKED
|
|
202
|
+
)
|
|
203
|
+
UPDATE ${schedulerSchema}.control AS request
|
|
204
|
+
SET updated_at = clock_timestamp()
|
|
205
|
+
FROM selected
|
|
206
|
+
WHERE request.key = selected.key
|
|
207
|
+
RETURNING request.key, request.value->>'reason' AS reason`,
|
|
208
|
+
[limit],
|
|
209
|
+
);
|
|
87
210
|
let reconciled = 0;
|
|
88
211
|
let skipped = 0;
|
|
89
|
-
for (const row of
|
|
90
|
-
const runId = row.
|
|
212
|
+
for (const row of queued.rows) {
|
|
213
|
+
const runId = row.key.slice('runtime_cancel:'.length);
|
|
91
214
|
if (!runId) {
|
|
92
|
-
|
|
93
|
-
continue;
|
|
215
|
+
throw new Error(`Malformed runtime cancellation request "${row.key}".`);
|
|
94
216
|
}
|
|
95
|
-
await input.client.query('BEGIN');
|
|
96
217
|
try {
|
|
97
|
-
await
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
input.client,
|
|
107
|
-
{
|
|
108
|
-
runId,
|
|
109
|
-
reason:
|
|
110
|
-
row.cancellation_kind === 'manual'
|
|
111
|
-
? 'Run cancellation was requested by the caller.'
|
|
112
|
-
: 'Run did not start within its configured scheduler queue deadline.',
|
|
113
|
-
},
|
|
114
|
-
input.schedulerOptions,
|
|
115
|
-
);
|
|
116
|
-
await input.client.query('COMMIT');
|
|
117
|
-
if (didCancel) reconciled += 1;
|
|
218
|
+
const outcome = await finalizeCancelledRunAfterCleanup({
|
|
219
|
+
client: input.client,
|
|
220
|
+
runId,
|
|
221
|
+
reason: row.reason || 'Cancelled play run.',
|
|
222
|
+
schedulerSchema,
|
|
223
|
+
schedulerOptions: input.schedulerOptions,
|
|
224
|
+
controlKey: row.key,
|
|
225
|
+
});
|
|
226
|
+
if (outcome === 'cancelled') reconciled += 1;
|
|
118
227
|
else skipped += 1;
|
|
119
228
|
} catch (error) {
|
|
120
|
-
await input.client.query('ROLLBACK').catch(() => undefined);
|
|
121
|
-
// A worker-held lock is expected to be retried by the next lane sweep;
|
|
122
|
-
// never let one blocked cancellation stall the claim loop or create a
|
|
123
|
-
// second unbounded cancellation pile.
|
|
124
229
|
if (
|
|
125
230
|
isRuntimeSchedulerQueryCancellationError(error) ||
|
|
126
231
|
isRuntimeSchedulerRetryableTransactionError(error)
|
|
127
232
|
) {
|
|
128
233
|
skipped += 1;
|
|
129
|
-
// The hardened pool destroys a client after a statement timeout or a
|
|
130
|
-
// retryable transaction error. Do not issue the next candidate on that
|
|
131
|
-
// same socket; the next worker sweep acquires a fresh client.
|
|
132
234
|
break;
|
|
133
235
|
}
|
|
134
236
|
throw error;
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts
CHANGED
|
@@ -262,10 +262,13 @@ import { shouldTerminalizeSandboxCapacityRejection } from '../launch-source-poli
|
|
|
262
262
|
import {
|
|
263
263
|
ABSURD_CANCELLED_DEADLINE_RECONCILE_LIMIT,
|
|
264
264
|
reconcileAbsurdRuntimeCancellations,
|
|
265
|
+
reconcileQueuedAbsurdRuntimeCancellations,
|
|
265
266
|
} from './absurd-deadline-reconciliation';
|
|
266
267
|
import {
|
|
267
268
|
abandonRuntimeSandboxOperationReservation,
|
|
268
269
|
bindRuntimeSandboxCapacityLease,
|
|
270
|
+
readReservedDaytonaCancellationOperation,
|
|
271
|
+
recordRecoveredDaytonaCancellationSandbox,
|
|
269
272
|
reserveRuntimeSandboxOperation,
|
|
270
273
|
runtimeSandboxCapacityAdmissionMode,
|
|
271
274
|
RuntimeSandboxCapacityLimitError,
|
|
@@ -373,8 +376,8 @@ export const ABSURD_ADOPTED_LANE_POLL_INTERVAL_MS =
|
|
|
373
376
|
const HEARTBEAT_THROTTLE_MS = (HEARTBEAT_CLAIM_TIMEOUT_SECONDS * 1000) / 3;
|
|
374
377
|
const HEARTBEAT_TIMEOUT_RETRY_DELAY_MS = 1_000;
|
|
375
378
|
/**
|
|
376
|
-
* Sandbox deletion is
|
|
377
|
-
* Daytona SDK may hang before its own delete timeout (for example while
|
|
379
|
+
* Sandbox deletion is a durable obligation and may precede a cancellation's
|
|
380
|
+
* terminal write. The Daytona SDK may hang before its own delete timeout (for example while
|
|
378
381
|
* looking up an already-autostopped sandbox), so never let that external call
|
|
379
382
|
* occupy an Absurd worker slot indefinitely.
|
|
380
383
|
*/
|
|
@@ -6644,6 +6647,75 @@ export async function reconcilePostgresSchedulerAmbiguousModalCreatesOnce(input:
|
|
|
6644
6647
|
return { observed: intents.length, resourcesRecorded, resolvedAbsent };
|
|
6645
6648
|
}
|
|
6646
6649
|
|
|
6650
|
+
async function reconcileReservedDaytonaCancellationOnce(input: {
|
|
6651
|
+
runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
|
|
6652
|
+
environment: 'preview' | 'production';
|
|
6653
|
+
schedulerOptions: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
6654
|
+
}): Promise<void> {
|
|
6655
|
+
const operation = await input.runWithClient((client) =>
|
|
6656
|
+
readReservedDaytonaCancellationOperation(
|
|
6657
|
+
client,
|
|
6658
|
+
input.environment,
|
|
6659
|
+
input.schedulerOptions,
|
|
6660
|
+
),
|
|
6661
|
+
);
|
|
6662
|
+
if (!operation) return;
|
|
6663
|
+
const [{ Daytona }, { loadDaytonaRequiredConfig }] = await Promise.all([
|
|
6664
|
+
import('@daytonaio/sdk'),
|
|
6665
|
+
import('../daytona-runtime-config'),
|
|
6666
|
+
]);
|
|
6667
|
+
const daytona = new Daytona(
|
|
6668
|
+
loadDaytonaRequiredConfig({ environment: input.environment }).clientOptions,
|
|
6669
|
+
);
|
|
6670
|
+
const sandboxName = `dl-${operation.providerOperationToken}`;
|
|
6671
|
+
let sandbox: Awaited<ReturnType<typeof daytona.get>>;
|
|
6672
|
+
try {
|
|
6673
|
+
sandbox = await deleteRuntimeSandboxBounded({
|
|
6674
|
+
deleteRuntimeSandbox: (name) => daytona.get(name),
|
|
6675
|
+
sandboxId: sandboxName,
|
|
6676
|
+
timeoutMs: ABSURD_SANDBOX_GC_TIMEOUT_MS,
|
|
6677
|
+
});
|
|
6678
|
+
} catch (error) {
|
|
6679
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6680
|
+
if (!/\b(?:sandbox )?not found\b|\b404\b/i.test(message)) throw error;
|
|
6681
|
+
if (
|
|
6682
|
+
Date.now() - operation.createdAtMs <
|
|
6683
|
+
RUNTIME_RELIABILITY_POLICY.recovery.horizonMs
|
|
6684
|
+
) {
|
|
6685
|
+
return;
|
|
6686
|
+
}
|
|
6687
|
+
await input.runWithClient((client) =>
|
|
6688
|
+
abandonRuntimeSandboxOperationReservation(
|
|
6689
|
+
client,
|
|
6690
|
+
{ operationKey: operation.operationKey },
|
|
6691
|
+
input.schedulerOptions,
|
|
6692
|
+
),
|
|
6693
|
+
);
|
|
6694
|
+
return;
|
|
6695
|
+
}
|
|
6696
|
+
if (sandbox.labels?.operationKey !== operation.providerOperationToken) {
|
|
6697
|
+
throw new Error(
|
|
6698
|
+
`Daytona cancellation lookup ${sandboxName} lacks its exact operation label.`,
|
|
6699
|
+
);
|
|
6700
|
+
}
|
|
6701
|
+
if (!sandbox.organizationId) {
|
|
6702
|
+
throw new Error(
|
|
6703
|
+
`Daytona cancellation lookup ${sandboxName} lacks its routing organization.`,
|
|
6704
|
+
);
|
|
6705
|
+
}
|
|
6706
|
+
await input.runWithClient((client) =>
|
|
6707
|
+
recordRecoveredDaytonaCancellationSandbox(
|
|
6708
|
+
client,
|
|
6709
|
+
{
|
|
6710
|
+
operationKey: operation.operationKey,
|
|
6711
|
+
sandboxId: sandbox.id,
|
|
6712
|
+
routingDomain: sandbox.organizationId,
|
|
6713
|
+
},
|
|
6714
|
+
input.schedulerOptions,
|
|
6715
|
+
),
|
|
6716
|
+
);
|
|
6717
|
+
}
|
|
6718
|
+
|
|
6647
6719
|
export async function runPostgresSchedulerSandboxCleanupController(input: {
|
|
6648
6720
|
runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
|
|
6649
6721
|
deleteRuntimeSandbox: AbsurdRuntimeWorkerDeps['deleteRuntimeSandbox'];
|
|
@@ -6681,6 +6753,8 @@ export async function runPostgresSchedulerSandboxCleanupController(input: {
|
|
|
6681
6753
|
const reportReconciliationPollFailure = createRateLimitedRuntimeLogger({
|
|
6682
6754
|
log: (...args) => console.warn(...args),
|
|
6683
6755
|
});
|
|
6756
|
+
let reservedRecovery: Promise<void> | null = null;
|
|
6757
|
+
let nextReservedRecoveryAt = 0;
|
|
6684
6758
|
|
|
6685
6759
|
const startJob = (job: PostgresSchedulerSandboxCleanupJob): void => {
|
|
6686
6760
|
const task = processPostgresSchedulerSandboxCleanupJob({
|
|
@@ -6726,6 +6800,37 @@ export async function runPostgresSchedulerSandboxCleanupController(input: {
|
|
|
6726
6800
|
};
|
|
6727
6801
|
|
|
6728
6802
|
while (!input.shouldStop()) {
|
|
6803
|
+
if (
|
|
6804
|
+
(!input.providers || input.providers.includes('daytona')) &&
|
|
6805
|
+
!reservedRecovery &&
|
|
6806
|
+
Date.now() >= nextReservedRecoveryAt
|
|
6807
|
+
) {
|
|
6808
|
+
nextReservedRecoveryAt = Date.now() + 60_000;
|
|
6809
|
+
const recovery = reconcileReservedDaytonaCancellationOnce(input)
|
|
6810
|
+
.catch((error) => {
|
|
6811
|
+
reportReconciliationPollFailure(
|
|
6812
|
+
'[absurd-runtime.worker] daytona_cancellation_reservation_recovery_failed',
|
|
6813
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
6814
|
+
);
|
|
6815
|
+
})
|
|
6816
|
+
.finally(() => {
|
|
6817
|
+
if (reservedRecovery === recovery) reservedRecovery = null;
|
|
6818
|
+
});
|
|
6819
|
+
reservedRecovery = recovery;
|
|
6820
|
+
}
|
|
6821
|
+
try {
|
|
6822
|
+
await input.runWithClient((client) =>
|
|
6823
|
+
reconcileQueuedAbsurdRuntimeCancellations({
|
|
6824
|
+
client,
|
|
6825
|
+
schedulerOptions: input.schedulerOptions,
|
|
6826
|
+
}),
|
|
6827
|
+
);
|
|
6828
|
+
} catch (error) {
|
|
6829
|
+
reportReconciliationPollFailure(
|
|
6830
|
+
'[absurd-runtime.worker] cancellation_reconciliation_poll_failed',
|
|
6831
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
6832
|
+
);
|
|
6833
|
+
}
|
|
6729
6834
|
if (input.providers?.includes('modal')) {
|
|
6730
6835
|
try {
|
|
6731
6836
|
const reconciliation =
|
|
@@ -7082,9 +7187,9 @@ export async function startRuntimeWorkerHost(
|
|
|
7082
7187
|
queue,
|
|
7083
7188
|
schedulerOptions,
|
|
7084
7189
|
});
|
|
7085
|
-
if (cancellations.
|
|
7190
|
+
if (cancellations.queued > 0) {
|
|
7086
7191
|
reportCancellationProgress(
|
|
7087
|
-
'[absurd-runtime.worker]
|
|
7192
|
+
'[absurd-runtime.worker] cancellations_queued',
|
|
7088
7193
|
{
|
|
7089
7194
|
queue,
|
|
7090
7195
|
releaseId,
|
|
@@ -7244,9 +7349,9 @@ export async function startRuntimeWorkerHost(
|
|
|
7244
7349
|
queue: adoptedQueue,
|
|
7245
7350
|
schedulerOptions,
|
|
7246
7351
|
});
|
|
7247
|
-
if (cancellations.
|
|
7352
|
+
if (cancellations.queued > 0) {
|
|
7248
7353
|
reportAdoptedCancellationProgress(
|
|
7249
|
-
'[absurd-runtime.worker]
|
|
7354
|
+
'[absurd-runtime.worker] cancellations_queued',
|
|
7250
7355
|
{
|
|
7251
7356
|
queue: adoptedQueue,
|
|
7252
7357
|
releaseId,
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* Persist the absurd task id
|
|
15
15
|
* on the run row so a later cancel can target it.
|
|
16
16
|
* observe/result → reuse the postgres LISTEN/NOTIFY run handle.
|
|
17
|
-
* cancel →
|
|
18
|
-
* the
|
|
17
|
+
* cancel → commit the Absurd stop and a durable cancellation request;
|
|
18
|
+
* the worker deletes sandboxes before the final run write.
|
|
19
19
|
* signal → durable signal row (authoritative payload) + absurd emitEvent
|
|
20
20
|
* (the wakeup only; payload is re-read from the signal row).
|
|
21
21
|
*/
|
|
@@ -29,8 +29,6 @@ import {
|
|
|
29
29
|
type PlaySchedulerSubmitInput,
|
|
30
30
|
} from '../scheduler-backend';
|
|
31
31
|
import {
|
|
32
|
-
cancelPostgresSchedulerRun,
|
|
33
|
-
postgresSchedulerCancellationOutboxLease,
|
|
34
32
|
postgresSchedulerRunHandle,
|
|
35
33
|
readPostgresSchedulerLaunch,
|
|
36
34
|
readPostgresSchedulerAbsurdTaskId,
|
|
@@ -48,6 +46,8 @@ import {
|
|
|
48
46
|
withRuntimeSchedulerClient,
|
|
49
47
|
withRuntimeSchedulerTransaction,
|
|
50
48
|
} from './postgres-pool';
|
|
49
|
+
import { RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE } from '../scheduler-backend';
|
|
50
|
+
import { resolveRuntimeSchedulerSchemaEnv } from './runtime-scheduler-schema';
|
|
51
51
|
import { withRuntimeSchedulerSubmitAdmission } from './submit-admission';
|
|
52
52
|
import {
|
|
53
53
|
ABSURD_PLAY_RUN_MAX_ATTEMPTS,
|
|
@@ -200,7 +200,7 @@ function absurdRunHandle(input: {
|
|
|
200
200
|
const runsQueue = absurdQueueNameForRelease(releaseId);
|
|
201
201
|
const cancellationKey = `${poolOptions.url}::${effectiveOptions.schema ?? ''}::${input.runId}`;
|
|
202
202
|
|
|
203
|
-
const
|
|
203
|
+
const queueCancellation = async (reason?: string) =>
|
|
204
204
|
withRuntimeSchedulerTransaction(
|
|
205
205
|
poolOptions,
|
|
206
206
|
{
|
|
@@ -208,9 +208,8 @@ function absurdRunHandle(input: {
|
|
|
208
208
|
statementTimeoutMs: RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS,
|
|
209
209
|
},
|
|
210
210
|
async (client) => {
|
|
211
|
-
// Resolve the immutable release lane
|
|
212
|
-
//
|
|
213
|
-
// has no preliminary lane lookup that can succeed or fail separately.
|
|
211
|
+
// Resolve the immutable release lane in the transaction that records
|
|
212
|
+
// the request. A failed final run write cannot roll this marker back.
|
|
214
213
|
const launch = await readPostgresSchedulerLaunch(
|
|
215
214
|
client,
|
|
216
215
|
input.runId,
|
|
@@ -231,67 +230,73 @@ function absurdRunHandle(input: {
|
|
|
231
230
|
input.runId,
|
|
232
231
|
effectiveOptions,
|
|
233
232
|
);
|
|
233
|
+
if (
|
|
234
|
+
await readPostgresSchedulerResult(
|
|
235
|
+
client,
|
|
236
|
+
input.runId,
|
|
237
|
+
effectiveOptions,
|
|
238
|
+
)
|
|
239
|
+
) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
234
242
|
if (taskId) {
|
|
235
|
-
const
|
|
243
|
+
const engineTaskPresent = await cancelAbsurdTaskIfPresent(client, {
|
|
236
244
|
queue: cancellationQueue,
|
|
237
245
|
taskId,
|
|
238
246
|
runId: input.runId,
|
|
239
247
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// schema cutover. The explicit marker keeps a user cancel distinct
|
|
244
|
-
// from a task cancelled by its configured max_delay sweep.
|
|
245
|
-
if (engineTaskCancelled) {
|
|
248
|
+
if (engineTaskPresent) {
|
|
249
|
+
// Older workers only reconcile `manual` or `max_delay`. This
|
|
250
|
+
// marker is diagnostic; the control row is the cross-lane queue.
|
|
246
251
|
await client.query(
|
|
247
252
|
`UPDATE absurd.${quotePostgresIdentifier(`t_${cancellationQueue}`)}
|
|
248
253
|
SET cancel_at = COALESCE(cancel_at, clock_timestamp()),
|
|
249
254
|
cancellation = COALESCE(cancellation, '{}'::jsonb)
|
|
250
|
-
|| jsonb_build_object('
|
|
251
|
-
WHERE task_id = $1::uuid
|
|
252
|
-
AND state = 'cancelled'`,
|
|
255
|
+
|| jsonb_build_object('sandboxFirst', true)
|
|
256
|
+
WHERE task_id = $1::uuid`,
|
|
253
257
|
[taskId],
|
|
254
258
|
);
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
reason,
|
|
262
|
-
outboxLease: postgresSchedulerCancellationOutboxLease(input.runId),
|
|
263
|
-
},
|
|
264
|
-
effectiveOptions,
|
|
261
|
+
const schedulerSchema = quotePostgresIdentifier(
|
|
262
|
+
effectiveOptions.schema?.trim() ||
|
|
263
|
+
resolveRuntimeSchedulerSchemaEnv() ||
|
|
264
|
+
'runtime_scheduler',
|
|
265
265
|
);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
}
|
|
266
|
+
await client.query(
|
|
267
|
+
`INSERT INTO ${schedulerSchema}.control (key, value)
|
|
268
|
+
VALUES ($1, jsonb_build_object(
|
|
269
|
+
'reason', left($2::text, 4000),
|
|
270
|
+
'requestedAt', clock_timestamp()
|
|
271
|
+
))
|
|
272
|
+
ON CONFLICT (key) DO NOTHING`,
|
|
273
|
+
[
|
|
274
|
+
`runtime_cancel:${input.runId}`,
|
|
275
|
+
reason?.trim() || 'Cancelled play run.',
|
|
276
|
+
],
|
|
277
|
+
);
|
|
278
|
+
return true;
|
|
281
279
|
},
|
|
282
280
|
{
|
|
283
281
|
runId: input.runId,
|
|
284
|
-
phase: '
|
|
282
|
+
phase: 'queue_run_cancellation',
|
|
285
283
|
priority: 'critical',
|
|
286
284
|
},
|
|
285
|
+
{ maxAttempts: 1 },
|
|
287
286
|
);
|
|
288
287
|
|
|
289
288
|
const requestCancellation = async (reason?: string) => {
|
|
290
289
|
await runCancellationSingleFlight(cancellationKey, async () => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
290
|
+
const queued = await queueCancellation(reason);
|
|
291
|
+
if (queued) {
|
|
292
|
+
const error = new Error(
|
|
293
|
+
`Cancellation queued for ${input.runId}; sandbox cleanup and final run update are pending.`,
|
|
294
|
+
);
|
|
295
|
+
Object.assign(error, {
|
|
296
|
+
code: RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE,
|
|
297
|
+
});
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
295
300
|
});
|
|
296
301
|
};
|
|
297
302
|
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts
CHANGED
|
@@ -3080,6 +3080,13 @@ async function penalizeRateStateStatements(
|
|
|
3080
3080
|
GREATEST(h.retry_at_ms, COALESCE($5::bigint, nv.now_ms + $2::bigint))
|
|
3081
3081
|
- COALESCE(h.rate_observation_started_at_ms, nv.now_ms)
|
|
3082
3082
|
END) AS cycle_ms,
|
|
3083
|
+
CASE
|
|
3084
|
+
WHEN $5::bigint IS NOT NULL AND h.rate_successes <= 0 THEN
|
|
3085
|
+
CASE WHEN h.seen_backpressure THEN
|
|
3086
|
+
LEAST(60000, GREATEST(1000, h.rate_recovery_interval_ms * 2))
|
|
3087
|
+
ELSE 1000 END
|
|
3088
|
+
ELSE NULL
|
|
3089
|
+
END AS no_progress_probe_interval_ms,
|
|
3083
3090
|
nv.now_ms
|
|
3084
3091
|
FROM held h CROSS JOIN now_value nv
|
|
3085
3092
|
), throttle_ceiling AS (
|
|
@@ -3087,12 +3094,12 @@ async function penalizeRateStateStatements(
|
|
|
3087
3094
|
GREATEST(${MIN_ADAPTIVE_RPS_FLOOR},
|
|
3088
3095
|
CASE WHEN e.seen_backpressure AND e.rate_successes = 0 THEN
|
|
3089
3096
|
CASE WHEN $5::bigint IS NOT NULL
|
|
3090
|
-
--
|
|
3091
|
-
--
|
|
3092
|
-
--
|
|
3093
|
-
--
|
|
3094
|
-
--
|
|
3095
|
-
THEN
|
|
3097
|
+
-- A fresh provider deadline is not a throughput measurement.
|
|
3098
|
+
-- Repeated probes with no successful work are evidence that
|
|
3099
|
+
-- the scope is still unavailable, however. Space subsequent
|
|
3100
|
+
-- probes exponentially (up to one per minute) across every
|
|
3101
|
+
-- run sharing this provider credential.
|
|
3102
|
+
THEN 1000.0 / e.no_progress_probe_interval_ms
|
|
3096
3103
|
ELSE e.refill_before END
|
|
3097
3104
|
ELSE e.refill_before / 2 END
|
|
3098
3105
|
) AS next_ceiling_rps
|
|
@@ -3115,7 +3122,9 @@ async function penalizeRateStateStatements(
|
|
|
3115
3122
|
rate_throttle_ceiling_rps = CASE WHEN e.new_episode
|
|
3116
3123
|
THEN e.next_ceiling_rps ELSE e.rate_throttle_ceiling_rps END,
|
|
3117
3124
|
rate_observation_started_at_ms = CASE WHEN e.new_episode THEN NULL ELSE e.rate_observation_started_at_ms END,
|
|
3118
|
-
rate_recovery_interval_ms = CASE WHEN e.new_episode
|
|
3125
|
+
rate_recovery_interval_ms = CASE WHEN e.new_episode
|
|
3126
|
+
THEN COALESCE(e.no_progress_probe_interval_ms, e.cycle_ms)
|
|
3127
|
+
ELSE e.rate_recovery_interval_ms END,
|
|
3119
3128
|
last_ramp_at_ms = CASE WHEN e.new_episode THEN e.deadline ELSE e.last_ramp_at_ms END,
|
|
3120
3129
|
-- All pre-429 permits are revalidated against this durable hold
|
|
3121
3130
|
-- before dispatch. Their obsolete schedule debt must not add a
|