deepline 0.3.235 → 0.3.237
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 +6 -0
- package/dist/bundling-sources/sdk/src/http.ts +46 -10
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +26 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +299 -130
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +219 -46
- package/dist/cli/index.js +184 -56
- package/dist/cli/index.mjs +184 -56
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +37 -4
- package/dist/index.mjs +37 -4
- 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
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts
CHANGED
|
@@ -136,6 +136,8 @@ import {
|
|
|
136
136
|
completePostgresSchedulerComputeBillingSessionBootstrap,
|
|
137
137
|
deferPostgresSchedulerAttemptForCapacity,
|
|
138
138
|
enqueuePostgresSchedulerSandboxCleanupForAttempt,
|
|
139
|
+
enqueuePostgresSchedulerSandboxCleanupForRun,
|
|
140
|
+
hasPostgresSchedulerRecoverableDetachedRunner,
|
|
139
141
|
executePostgresSchedulerClaim,
|
|
140
142
|
ensurePostgresSchedulerTerminalComputeBillingJob,
|
|
141
143
|
failPostgresSchedulerAttempt,
|
|
@@ -221,6 +223,7 @@ import {
|
|
|
221
223
|
type RuntimeSchedulerPoolOptions,
|
|
222
224
|
withRuntimeSchedulerClient,
|
|
223
225
|
} from './postgres-pool';
|
|
226
|
+
import { runWithAbsurdSchedulerQuerySignal } from './absurd-pool';
|
|
224
227
|
import {
|
|
225
228
|
createPostgresSchedulerProgressReporter,
|
|
226
229
|
type PostgresSchedulerProgressReporter,
|
|
@@ -550,7 +553,10 @@ export async function runWithAbsurdExecutionLeaseHeartbeat<T>(input: {
|
|
|
550
553
|
lastHeartbeatAt = now;
|
|
551
554
|
heartbeatState.inFlight = (async () => {
|
|
552
555
|
try {
|
|
553
|
-
await
|
|
556
|
+
await runWithAbsurdSchedulerQuerySignal(
|
|
557
|
+
cancellationController.signal,
|
|
558
|
+
() => input.ctx.heartbeat(),
|
|
559
|
+
);
|
|
554
560
|
} catch (error) {
|
|
555
561
|
if (!isTransientHeartbeatTimeout(error) || stopped) throw error;
|
|
556
562
|
console.warn('[absurd-runtime.worker] lease_heartbeat_timeout_retry', {
|
|
@@ -559,10 +565,22 @@ export async function runWithAbsurdExecutionLeaseHeartbeat<T>(input: {
|
|
|
559
565
|
});
|
|
560
566
|
await sleep(heartbeatTimeoutRetryDelayMs);
|
|
561
567
|
if (stopped) return;
|
|
562
|
-
await
|
|
568
|
+
await runWithAbsurdSchedulerQuerySignal(
|
|
569
|
+
cancellationController.signal,
|
|
570
|
+
() => input.ctx.heartbeat(),
|
|
571
|
+
);
|
|
563
572
|
}
|
|
564
573
|
})()
|
|
565
574
|
.catch((error) => {
|
|
575
|
+
// The handoff deadline owns this cancellation. A cancelled local SQL
|
|
576
|
+
// call does not prove that the engine cancelled the durable task.
|
|
577
|
+
if (
|
|
578
|
+
cancellationController.signal.aborted &&
|
|
579
|
+
error === cancellationController.signal.reason &&
|
|
580
|
+
error instanceof AbsurdExecutionOperationTimeoutError
|
|
581
|
+
) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
566
584
|
const engineTerminal =
|
|
567
585
|
error instanceof CancelledTask || error instanceof FailedTask;
|
|
568
586
|
if (engineTerminal) {
|
|
@@ -595,9 +613,22 @@ export async function runWithAbsurdExecutionLeaseHeartbeat<T>(input: {
|
|
|
595
613
|
timer.unref?.();
|
|
596
614
|
let operationTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
597
615
|
try {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
)
|
|
616
|
+
// A completed backend still cannot park until its in-flight lease update
|
|
617
|
+
// settles. Keep that drain inside the same supervised handoff deadline.
|
|
618
|
+
const operation = (async () => {
|
|
619
|
+
try {
|
|
620
|
+
operationResult = await Promise.resolve().then(() =>
|
|
621
|
+
input.operation(cancellationController.signal),
|
|
622
|
+
);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
operationError = error;
|
|
625
|
+
} finally {
|
|
626
|
+
stopped = true;
|
|
627
|
+
stopHeartbeatTimer();
|
|
628
|
+
await heartbeatState.inFlight;
|
|
629
|
+
}
|
|
630
|
+
return operationResult;
|
|
631
|
+
})();
|
|
601
632
|
if (operationTimeoutMs !== null) {
|
|
602
633
|
operationResult = await Promise.race([
|
|
603
634
|
operation,
|
|
@@ -619,15 +650,11 @@ export async function runWithAbsurdExecutionLeaseHeartbeat<T>(input: {
|
|
|
619
650
|
operationResult = await operation;
|
|
620
651
|
}
|
|
621
652
|
} catch (error) {
|
|
622
|
-
operationError
|
|
653
|
+
operationError ??= error;
|
|
623
654
|
} finally {
|
|
624
655
|
if (operationTimeout) clearTimeout(operationTimeout);
|
|
625
656
|
stopped = true;
|
|
626
657
|
stopHeartbeatTimer();
|
|
627
|
-
const pendingHeartbeat = heartbeatState.inFlight;
|
|
628
|
-
if (pendingHeartbeat) {
|
|
629
|
-
await pendingHeartbeat.catch(() => undefined);
|
|
630
|
-
}
|
|
631
658
|
}
|
|
632
659
|
if (heartbeatFailure) {
|
|
633
660
|
if (
|
|
@@ -735,7 +762,7 @@ export type AbsurdRuntimeWorkerDeps = {
|
|
|
735
762
|
deleteRuntimeSandboxRef?: (
|
|
736
763
|
ref: RuntimeSandboxRef,
|
|
737
764
|
) => Promise<RuntimeSandboxDeleteOutcome>;
|
|
738
|
-
deleteRuntimeSandboxWithOutcome
|
|
765
|
+
deleteRuntimeSandboxWithOutcome?: (
|
|
739
766
|
sandboxId: string,
|
|
740
767
|
expectedOrganizationId: string | null,
|
|
741
768
|
) => Promise<DaytonaSandboxDeleteOutcome>;
|
|
@@ -860,40 +887,48 @@ function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
|
|
|
860
887
|
* of silently leaving the CLI stale.
|
|
861
888
|
*/
|
|
862
889
|
async function projectLifecycleOutbox(input: {
|
|
890
|
+
ctx: TaskContext;
|
|
863
891
|
deps: AbsurdRuntimeWorkerDeps;
|
|
864
892
|
launch: PlaySchedulerSubmitInput;
|
|
865
893
|
lifecycle: PostgresSchedulerLifecycleOutboxEvent;
|
|
866
894
|
options?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
867
895
|
transition: 'waiting' | 'resumed';
|
|
868
896
|
}): Promise<void> {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
+
await runWithAbsurdExecutionLeaseHeartbeat({
|
|
898
|
+
ctx: input.ctx,
|
|
899
|
+
runId: input.lifecycle.runId,
|
|
900
|
+
operation: async (signal) => {
|
|
901
|
+
try {
|
|
902
|
+
const result = await input.deps.projectOutbox(
|
|
903
|
+
input.launch,
|
|
904
|
+
input.lifecycle.runId,
|
|
905
|
+
input.options,
|
|
906
|
+
input.lifecycle.eventId,
|
|
907
|
+
signal,
|
|
908
|
+
);
|
|
909
|
+
if (result && !result.targetDelivered) {
|
|
910
|
+
console.error('[run-ledger-outbox.lifecycle_projection_pending]', {
|
|
911
|
+
runId: input.lifecycle.runId,
|
|
912
|
+
eventId: input.lifecycle.eventId,
|
|
913
|
+
transition: input.transition,
|
|
914
|
+
projected: result.projected,
|
|
915
|
+
failed: result.failed,
|
|
916
|
+
failedEventIds: result.failedEventIds,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
} catch (error) {
|
|
920
|
+
// Scheduler truth and the outbox row were committed atomically before this
|
|
921
|
+
// handoff. Keep the durable continuation live so recovery can heal a
|
|
922
|
+
// transient app-runtime failure, but do not hide the visibility gap.
|
|
923
|
+
console.error('[run-ledger-outbox.lifecycle_projection_failed]', {
|
|
924
|
+
runId: input.lifecycle.runId,
|
|
925
|
+
eventId: input.lifecycle.eventId,
|
|
926
|
+
transition: input.transition,
|
|
927
|
+
error: error instanceof Error ? error.message : String(error),
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
});
|
|
897
932
|
}
|
|
898
933
|
|
|
899
934
|
function nonJsonResponseTitle(
|
|
@@ -2397,23 +2432,26 @@ async function executeAbsurdAttemptAdapter(
|
|
|
2397
2432
|
let executionLaunch = launch;
|
|
2398
2433
|
try {
|
|
2399
2434
|
const tokenStartedAt = performance.now();
|
|
2400
|
-
const executorToken = await
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2435
|
+
const executorToken = await runWithAbsurdExecutionLeaseHeartbeat({
|
|
2436
|
+
ctx,
|
|
2437
|
+
runId,
|
|
2438
|
+
operation: () =>
|
|
2439
|
+
deps.mintAttemptExecutorToken({
|
|
2440
|
+
launch,
|
|
2441
|
+
attempt: generation,
|
|
2442
|
+
leaseToken,
|
|
2443
|
+
schedulerSchema: options?.schema ?? null,
|
|
2444
|
+
}),
|
|
2445
|
+
}).finally(() => {
|
|
2446
|
+
console.info(
|
|
2447
|
+
'[absurd-runtime.worker.phase] ' +
|
|
2448
|
+
JSON.stringify({
|
|
2449
|
+
runId,
|
|
2450
|
+
phase: 'executor_token_mint',
|
|
2451
|
+
totalMs: performance.now() - tokenStartedAt,
|
|
2452
|
+
}),
|
|
2453
|
+
);
|
|
2454
|
+
});
|
|
2417
2455
|
executionLaunch = {
|
|
2418
2456
|
...launch,
|
|
2419
2457
|
executorToken,
|
|
@@ -2434,12 +2472,56 @@ async function executeAbsurdAttemptAdapter(
|
|
|
2434
2472
|
runtimeApi,
|
|
2435
2473
|
});
|
|
2436
2474
|
} catch (error) {
|
|
2437
|
-
if (
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2475
|
+
if (error instanceof SuspendTask || error instanceof DeferTask) throw error;
|
|
2476
|
+
let detached: Awaited<
|
|
2477
|
+
ReturnType<typeof resumePostgresSchedulerDetachedRunnerAttempt>
|
|
2478
|
+
>;
|
|
2479
|
+
try {
|
|
2480
|
+
detached = await deps.runWithClient((client) =>
|
|
2481
|
+
resumePostgresSchedulerDetachedRunnerAttempt(
|
|
2482
|
+
client,
|
|
2483
|
+
{ runId, attempt: generation, leaseToken },
|
|
2484
|
+
options,
|
|
2485
|
+
),
|
|
2486
|
+
);
|
|
2487
|
+
} catch (recoveryError) {
|
|
2488
|
+
console.error(
|
|
2489
|
+
'[absurd-runtime.worker] detached_runner_recovery_unavailable',
|
|
2490
|
+
JSON.stringify({
|
|
2491
|
+
runId,
|
|
2492
|
+
attempt: generation,
|
|
2493
|
+
engineTaskId: ctx.taskID,
|
|
2494
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2495
|
+
recoveryError:
|
|
2496
|
+
recoveryError instanceof Error
|
|
2497
|
+
? recoveryError.message
|
|
2498
|
+
: String(recoveryError),
|
|
2499
|
+
cleanupAuthorized: false,
|
|
2500
|
+
}),
|
|
2501
|
+
);
|
|
2502
|
+
throw new AggregateError(
|
|
2503
|
+
[error, recoveryError],
|
|
2504
|
+
'Scheduler could not prove detached runner cleanup authority.',
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
if (detached) {
|
|
2508
|
+
console.error(
|
|
2509
|
+
'[absurd-runtime.worker] detached_runner_handoff_recovered',
|
|
2510
|
+
JSON.stringify({
|
|
2511
|
+
runId,
|
|
2512
|
+
attempt: generation,
|
|
2513
|
+
engineAttempt: ctx.attempt,
|
|
2514
|
+
engineTaskId: ctx.taskID,
|
|
2515
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2516
|
+
cleanupAuthorized: false,
|
|
2517
|
+
}),
|
|
2518
|
+
);
|
|
2519
|
+
throw new DeferTask(
|
|
2520
|
+
`Detached runner ${runId}/${generation} retains execution after scheduler handoff failure.`,
|
|
2521
|
+
1,
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
if (error instanceof CancelledTask || error instanceof FailedTask) {
|
|
2443
2525
|
// Park/cancel signals belong to the engine; never treat them as leg
|
|
2444
2526
|
// failures.
|
|
2445
2527
|
throw error;
|
|
@@ -2761,11 +2843,6 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
2761
2843
|
`absurd runtime worker: suspended runner result for ${runId} carried no suspension.`,
|
|
2762
2844
|
);
|
|
2763
2845
|
}
|
|
2764
|
-
await input.progressReporter.finalize(input.runnerResult, {
|
|
2765
|
-
status: 'running',
|
|
2766
|
-
replayAllTerminalLogs: input.fromDetachedRunner,
|
|
2767
|
-
terminalLogProducerAttempt: input.terminalLogProducerAttempt,
|
|
2768
|
-
});
|
|
2769
2846
|
const suspendedCheckpoint = absurdCheckpointBase(
|
|
2770
2847
|
input.runnerResult.checkpoint ?? checkpoint ?? null,
|
|
2771
2848
|
);
|
|
@@ -2812,16 +2889,29 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
2812
2889
|
store: attemptStore,
|
|
2813
2890
|
engine,
|
|
2814
2891
|
lifecycle: {
|
|
2815
|
-
waiting: async (lifecycle) =>
|
|
2892
|
+
waiting: async (lifecycle) => {
|
|
2893
|
+
await runWithAbsurdExecutionLeaseHeartbeat({
|
|
2894
|
+
ctx,
|
|
2895
|
+
runId,
|
|
2896
|
+
operation: () =>
|
|
2897
|
+
input.progressReporter.finalize(input.runnerResult, {
|
|
2898
|
+
status: 'running',
|
|
2899
|
+
replayAllTerminalLogs: input.fromDetachedRunner,
|
|
2900
|
+
terminalLogProducerAttempt: input.terminalLogProducerAttempt,
|
|
2901
|
+
}),
|
|
2902
|
+
});
|
|
2816
2903
|
await projectLifecycleOutbox({
|
|
2904
|
+
ctx,
|
|
2817
2905
|
deps,
|
|
2818
2906
|
launch,
|
|
2819
2907
|
lifecycle,
|
|
2820
2908
|
options,
|
|
2821
2909
|
transition: 'waiting',
|
|
2822
|
-
})
|
|
2910
|
+
});
|
|
2911
|
+
},
|
|
2823
2912
|
resumed: async (lifecycle) =>
|
|
2824
2913
|
await projectLifecycleOutbox({
|
|
2914
|
+
ctx,
|
|
2825
2915
|
deps,
|
|
2826
2916
|
launch,
|
|
2827
2917
|
lifecycle,
|
|
@@ -2916,6 +3006,7 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
2916
3006
|
return { ok: true, runId, schedulerStatus: 'idle' };
|
|
2917
3007
|
}
|
|
2918
3008
|
await projectLifecycleOutbox({
|
|
3009
|
+
ctx,
|
|
2919
3010
|
deps,
|
|
2920
3011
|
launch,
|
|
2921
3012
|
lifecycle: marked,
|
|
@@ -2962,6 +3053,7 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
2962
3053
|
return { ok: true, runId, schedulerStatus: 'idle' };
|
|
2963
3054
|
}
|
|
2964
3055
|
await projectLifecycleOutbox({
|
|
3056
|
+
ctx,
|
|
2965
3057
|
deps,
|
|
2966
3058
|
launch,
|
|
2967
3059
|
lifecycle: running,
|
|
@@ -3043,6 +3135,16 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
3043
3135
|
error: unknown,
|
|
3044
3136
|
runnerConfig: PlayRunnerExecutionConfig,
|
|
3045
3137
|
): Promise<void> => {
|
|
3138
|
+
if (
|
|
3139
|
+
await deps.runWithClient((client) =>
|
|
3140
|
+
hasPostgresSchedulerRecoverableDetachedRunner(
|
|
3141
|
+
client,
|
|
3142
|
+
{ runId, attempt: generation, leaseToken },
|
|
3143
|
+
options,
|
|
3144
|
+
),
|
|
3145
|
+
)
|
|
3146
|
+
)
|
|
3147
|
+
return;
|
|
3046
3148
|
const cleanupTerminalReason =
|
|
3047
3149
|
error instanceof CancelledTask ? 'lease_expired' : 'runner_failed';
|
|
3048
3150
|
await deps
|
|
@@ -3102,33 +3204,52 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
3102
3204
|
),
|
|
3103
3205
|
)
|
|
3104
3206
|
.catch(() => attemptResources);
|
|
3105
|
-
//
|
|
3106
|
-
//
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
(await persistAttemptSandboxCleanupBeforeBestEffortGc({
|
|
3113
|
-
deps,
|
|
3114
|
-
runId,
|
|
3115
|
-
attempt: generation,
|
|
3207
|
+
// All provider deletion goes through the durable cleanup lease, including
|
|
3208
|
+
// retired generations. Never pass a read-only resource snapshot to raw GC.
|
|
3209
|
+
if (!attemptResourceReadFailed) {
|
|
3210
|
+
await deps.runWithClient((client) =>
|
|
3211
|
+
enqueuePostgresSchedulerSandboxCleanupForRun(
|
|
3212
|
+
client,
|
|
3213
|
+
{ runId },
|
|
3116
3214
|
options,
|
|
3117
|
-
|
|
3215
|
+
),
|
|
3216
|
+
);
|
|
3217
|
+
const environments = new Set(
|
|
3218
|
+
allResources.flatMap((resource) => {
|
|
3219
|
+
if (!runtimeSandboxRefFromResource(resource)) return [];
|
|
3220
|
+
const value = resource as {
|
|
3221
|
+
runtimeEnvironment?: string;
|
|
3222
|
+
daytonaEnvironment?: string;
|
|
3223
|
+
};
|
|
3224
|
+
return [
|
|
3225
|
+
value.runtimeEnvironment === 'preview' ||
|
|
3226
|
+
value.daytonaEnvironment === 'preview'
|
|
3227
|
+
? ('preview' as const)
|
|
3228
|
+
: ('production' as const),
|
|
3229
|
+
];
|
|
3230
|
+
}),
|
|
3231
|
+
);
|
|
3232
|
+
for (const environment of environments) {
|
|
3233
|
+
await drainPostgresSchedulerSandboxCleanupOnce({
|
|
3234
|
+
runWithClient: deps.runWithClient,
|
|
3235
|
+
deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
|
|
3236
|
+
deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
|
|
3237
|
+
deleteRuntimeSandboxWithOutcome: deps.deleteRuntimeSandboxWithOutcome,
|
|
3238
|
+
readSandboxCrashDiagnostic: deps.readSandboxCrashDiagnostic,
|
|
3239
|
+
leaseOwner: `attempt-error:${runId}:${generation}`,
|
|
3240
|
+
runId,
|
|
3241
|
+
environment,
|
|
3242
|
+
schedulerOptions: options,
|
|
3243
|
+
timeoutMs: deps.sandboxGcTimeoutMs,
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3118
3247
|
const runtimeStateCleanup = releaseAbsurdAttemptRuntimeState({
|
|
3119
3248
|
config: runnerConfig,
|
|
3120
3249
|
runId,
|
|
3121
3250
|
runAttempt: generation,
|
|
3122
3251
|
resources: attemptResources,
|
|
3123
|
-
sandboxRefs: runtimeSandboxResourceRefs(allResources),
|
|
3124
3252
|
reason: cleanupTerminalReason,
|
|
3125
|
-
deleteRuntimeSandbox: immediateSandboxGcAllowed
|
|
3126
|
-
? deps.deleteRuntimeSandbox
|
|
3127
|
-
: undefined,
|
|
3128
|
-
deleteRuntimeSandboxRef: immediateSandboxGcAllowed
|
|
3129
|
-
? deps.deleteRuntimeSandboxRef
|
|
3130
|
-
: undefined,
|
|
3131
|
-
sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
|
|
3132
3253
|
});
|
|
3133
3254
|
if (ctx.maxAttempts !== null && ctx.attempt >= ctx.maxAttempts) {
|
|
3134
3255
|
// A final engine attempt cannot retry. Its durable failed terminal must
|
|
@@ -3165,30 +3286,41 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
3165
3286
|
pendingDetachedRunner = null;
|
|
3166
3287
|
let runnerResult: PlayRunnerResult;
|
|
3167
3288
|
try {
|
|
3168
|
-
runnerResult = await
|
|
3289
|
+
runnerResult = await runWithAbsurdExecutionLeaseHeartbeat({
|
|
3290
|
+
ctx,
|
|
3169
3291
|
runId,
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3292
|
+
operation: () =>
|
|
3293
|
+
resolveDetachedRunnerOutcome({
|
|
3294
|
+
runId,
|
|
3295
|
+
suspension: detached,
|
|
3296
|
+
checkpoint: absurdCheckpointBase(checkpoint),
|
|
3297
|
+
readRunnerTerminal,
|
|
3298
|
+
recordRunnerTerminal,
|
|
3299
|
+
readRunnerLiveness: () =>
|
|
3300
|
+
deps.runWithClient((client) =>
|
|
3301
|
+
readPostgresSchedulerRunnerLiveness(
|
|
3302
|
+
client,
|
|
3303
|
+
{ runId, attempt: detached.runnerAttempt },
|
|
3304
|
+
options,
|
|
3305
|
+
),
|
|
3306
|
+
),
|
|
3307
|
+
inspectDetachedRunner: deps.inspectDetachedRunner,
|
|
3308
|
+
deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
|
|
3309
|
+
deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
|
|
3310
|
+
sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
|
|
3311
|
+
}),
|
|
3186
3312
|
});
|
|
3187
3313
|
} catch (error) {
|
|
3188
3314
|
// The terminal fast path skips normal runner setup. Build its cleanup
|
|
3189
3315
|
// context only on failure, then preserve the same pre-retry resource
|
|
3190
3316
|
// release and sandbox-GC fence as the full execution path.
|
|
3191
|
-
const preparedRunnerConfig = await
|
|
3317
|
+
const preparedRunnerConfig = await runWithAbsurdExecutionLeaseHeartbeat(
|
|
3318
|
+
{
|
|
3319
|
+
ctx,
|
|
3320
|
+
runId,
|
|
3321
|
+
operation: () => prepareRunnerConfig(claim),
|
|
3322
|
+
},
|
|
3323
|
+
);
|
|
3192
3324
|
try {
|
|
3193
3325
|
await cleanupAfterExecutionError(
|
|
3194
3326
|
error,
|
|
@@ -3236,7 +3368,11 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
3236
3368
|
}
|
|
3237
3369
|
|
|
3238
3370
|
const progressReporter = deps.createProgressReporter(launch, generation);
|
|
3239
|
-
const preparedRunnerConfig = await
|
|
3371
|
+
const preparedRunnerConfig = await runWithAbsurdExecutionLeaseHeartbeat({
|
|
3372
|
+
ctx,
|
|
3373
|
+
runId,
|
|
3374
|
+
operation: () => prepareRunnerConfig(claim),
|
|
3375
|
+
});
|
|
3240
3376
|
const runnerConfig = preparedRunnerConfig.runnerConfig;
|
|
3241
3377
|
|
|
3242
3378
|
let runnerResult: PlayRunnerResult;
|
|
@@ -5003,6 +5139,9 @@ export function createAbsurdRuntimeTasks(
|
|
|
5003
5139
|
if (deps?.deleteRuntimeSandbox && !deps.deleteRuntimeSandboxRef) {
|
|
5004
5140
|
resolved.deleteRuntimeSandboxRef = undefined;
|
|
5005
5141
|
}
|
|
5142
|
+
if (deps?.deleteRuntimeSandbox && !deps.deleteRuntimeSandboxWithOutcome) {
|
|
5143
|
+
resolved.deleteRuntimeSandboxWithOutcome = undefined;
|
|
5144
|
+
}
|
|
5006
5145
|
absurd.registerTask({ name: ABSURD_PLAY_RUN_TASK, queue }, (params, ctx) =>
|
|
5007
5146
|
executeAbsurdRuntimeLaunch(
|
|
5008
5147
|
params as AbsurdPlayRunTaskParams,
|
|
@@ -5120,11 +5259,15 @@ async function readOpenAbsurdEngineRunId(input: {
|
|
|
5120
5259
|
* can win that same terminalization race while a replacement is starting. In
|
|
5121
5260
|
* that case Absurd rejects `complete_run` because the engine is already
|
|
5122
5261
|
* terminal; that is an idempotent success, not a reason to flap the new worker.
|
|
5262
|
+
* A receipt can also arrive before its wake is claimed. Retire that pending or
|
|
5263
|
+
* sleeping task with the existing cancellation primitive after scheduler truth
|
|
5264
|
+
* is durable, rather than failing recovery because complete_run needs a claim.
|
|
5123
5265
|
*/
|
|
5124
5266
|
async function completeAbsurdEngineRunIfStillRunning(input: {
|
|
5125
5267
|
client: PostgresSchedulerQueryClient;
|
|
5126
5268
|
queue: string;
|
|
5127
5269
|
absurdRunId: string;
|
|
5270
|
+
absurdTaskId: string;
|
|
5128
5271
|
result: unknown;
|
|
5129
5272
|
}): Promise<boolean> {
|
|
5130
5273
|
try {
|
|
@@ -5153,6 +5296,13 @@ async function completeAbsurdEngineRunIfStillRunning(input: {
|
|
|
5153
5296
|
) {
|
|
5154
5297
|
return false;
|
|
5155
5298
|
}
|
|
5299
|
+
if (state === 'pending' || state === 'sleeping') {
|
|
5300
|
+
return await cancelAbsurdEngineTaskAfterSchedulerTerminal({
|
|
5301
|
+
client: input.client,
|
|
5302
|
+
queue: input.queue,
|
|
5303
|
+
absurdTaskId: input.absurdTaskId,
|
|
5304
|
+
});
|
|
5305
|
+
}
|
|
5156
5306
|
throw error;
|
|
5157
5307
|
}
|
|
5158
5308
|
}
|
|
@@ -5206,9 +5356,9 @@ async function cancelAbsurdEngineTaskAfterSchedulerTerminal(input: {
|
|
|
5206
5356
|
* its eventual lease reclaim would allocate a fresh scheduler generation and
|
|
5207
5357
|
* re-run a play that has already completed. This recovery uses the original
|
|
5208
5358
|
* attempt lease and the exact same terminal path as the normal wake leg, then
|
|
5209
|
-
*
|
|
5210
|
-
*
|
|
5211
|
-
*
|
|
5359
|
+
* closes an engine run when it still exists. Completed and failed receipts
|
|
5360
|
+
* remain authoritative even after the final engine claim expires. Suspended
|
|
5361
|
+
* receipts retain the normal continuation handoff.
|
|
5212
5362
|
*/
|
|
5213
5363
|
export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
5214
5364
|
client: PostgresSchedulerQueryClient;
|
|
@@ -5228,6 +5378,7 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
|
5228
5378
|
{
|
|
5229
5379
|
limit: input.limit,
|
|
5230
5380
|
absurdReleaseId: input.absurdReleaseId,
|
|
5381
|
+
includeFailed: true,
|
|
5231
5382
|
},
|
|
5232
5383
|
options,
|
|
5233
5384
|
);
|
|
@@ -5236,7 +5387,10 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
|
5236
5387
|
|
|
5237
5388
|
for (const terminal of terminals) {
|
|
5238
5389
|
const runnerResult = parseDetachedRunnerResult(terminal.result);
|
|
5239
|
-
if (
|
|
5390
|
+
if (
|
|
5391
|
+
runnerResult?.status !== 'completed' &&
|
|
5392
|
+
runnerResult?.status !== 'failed'
|
|
5393
|
+
) {
|
|
5240
5394
|
skipped += 1;
|
|
5241
5395
|
continue;
|
|
5242
5396
|
}
|
|
@@ -5245,10 +5399,6 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
|
5245
5399
|
queue: input.queue,
|
|
5246
5400
|
schedulerRunId: terminal.runId,
|
|
5247
5401
|
});
|
|
5248
|
-
if (!absurdRun) {
|
|
5249
|
-
skipped += 1;
|
|
5250
|
-
continue;
|
|
5251
|
-
}
|
|
5252
5402
|
const launch = await readPostgresSchedulerLaunch(
|
|
5253
5403
|
input.client,
|
|
5254
5404
|
terminal.runId,
|
|
@@ -5267,6 +5417,16 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
|
5267
5417
|
let schedulerStatus: 'completed' | 'failed';
|
|
5268
5418
|
let terminalizedByAnotherWorker = false;
|
|
5269
5419
|
try {
|
|
5420
|
+
await markPostgresSchedulerAttemptRunning(
|
|
5421
|
+
input.client,
|
|
5422
|
+
{
|
|
5423
|
+
runId: terminal.runId,
|
|
5424
|
+
attempt: terminal.attempt,
|
|
5425
|
+
leaseToken: terminal.leaseToken,
|
|
5426
|
+
preserveHeartbeatAt: true,
|
|
5427
|
+
},
|
|
5428
|
+
options,
|
|
5429
|
+
);
|
|
5270
5430
|
const settled = await executePostgresSchedulerClaim(
|
|
5271
5431
|
input.client,
|
|
5272
5432
|
{
|
|
@@ -5296,22 +5456,26 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
|
|
|
5296
5456
|
terminal.runId,
|
|
5297
5457
|
options,
|
|
5298
5458
|
);
|
|
5299
|
-
if (observed?.status !== 'completed'
|
|
5300
|
-
|
|
5459
|
+
if (observed?.status !== 'completed' && observed?.status !== 'failed')
|
|
5460
|
+
throw error;
|
|
5461
|
+
schedulerStatus = observed.status;
|
|
5301
5462
|
terminalizedByAnotherWorker = true;
|
|
5302
5463
|
}
|
|
5303
5464
|
|
|
5304
|
-
const engineCompleted =
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5465
|
+
const engineCompleted = absurdRun
|
|
5466
|
+
? await completeAbsurdEngineRunIfStillRunning({
|
|
5467
|
+
client: input.client,
|
|
5468
|
+
queue: input.queue,
|
|
5469
|
+
absurdRunId: absurdRun.runId,
|
|
5470
|
+
absurdTaskId: absurdRun.taskId,
|
|
5471
|
+
result: {
|
|
5472
|
+
recovered: terminalizedByAnotherWorker
|
|
5473
|
+
? 'completed_runner_terminal_race'
|
|
5474
|
+
: 'completed_runner_terminal',
|
|
5475
|
+
schedulerStatus,
|
|
5476
|
+
},
|
|
5477
|
+
})
|
|
5478
|
+
: false;
|
|
5315
5479
|
if (terminalizedByAnotherWorker) {
|
|
5316
5480
|
if (engineCompleted) recovered += 1;
|
|
5317
5481
|
else skipped += 1;
|
|
@@ -6333,6 +6497,7 @@ function unavailableSandboxCrashDiagnostic(
|
|
|
6333
6497
|
async function claimPostgresSchedulerSandboxCleanupBatch(input: {
|
|
6334
6498
|
runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
|
|
6335
6499
|
leaseOwner: string;
|
|
6500
|
+
runId?: string;
|
|
6336
6501
|
environment: 'preview' | 'production';
|
|
6337
6502
|
schedulerOptions: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
6338
6503
|
limit: number;
|
|
@@ -6343,6 +6508,7 @@ async function claimPostgresSchedulerSandboxCleanupBatch(input: {
|
|
|
6343
6508
|
client,
|
|
6344
6509
|
{
|
|
6345
6510
|
leaseOwner: input.leaseOwner,
|
|
6511
|
+
runId: input.runId,
|
|
6346
6512
|
environment: input.environment,
|
|
6347
6513
|
limit: input.limit,
|
|
6348
6514
|
providers: input.providers,
|
|
@@ -6630,7 +6796,8 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
|
|
|
6630
6796
|
provider: job.provider,
|
|
6631
6797
|
sandboxId: job.sandboxId,
|
|
6632
6798
|
cleanupAttempt: job.attemptCount,
|
|
6633
|
-
|
|
6799
|
+
runStatusAtClaim: job.runStatusAtClaim,
|
|
6800
|
+
cleanupAuthority: 'durable_cleanup_claim',
|
|
6634
6801
|
providerOutcome: outcome.providerOutcome,
|
|
6635
6802
|
completionOutcome: outcome.completion,
|
|
6636
6803
|
});
|
|
@@ -6656,6 +6823,7 @@ export async function drainPostgresSchedulerSandboxCleanupOnce(input: {
|
|
|
6656
6823
|
deleteRuntimeSandboxRef?: AbsurdRuntimeWorkerDeps['deleteRuntimeSandboxRef'];
|
|
6657
6824
|
deleteRuntimeSandboxWithOutcome?: AbsurdRuntimeWorkerDeps['deleteRuntimeSandboxWithOutcome'];
|
|
6658
6825
|
leaseOwner: string;
|
|
6826
|
+
runId?: string;
|
|
6659
6827
|
environment: 'preview' | 'production';
|
|
6660
6828
|
schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
6661
6829
|
limit?: number;
|
|
@@ -6677,6 +6845,7 @@ export async function drainPostgresSchedulerSandboxCleanupOnce(input: {
|
|
|
6677
6845
|
const jobs = await claimPostgresSchedulerSandboxCleanupBatch({
|
|
6678
6846
|
runWithClient: input.runWithClient,
|
|
6679
6847
|
leaseOwner: input.leaseOwner,
|
|
6848
|
+
runId: input.runId,
|
|
6680
6849
|
environment: input.environment,
|
|
6681
6850
|
schedulerOptions: options,
|
|
6682
6851
|
limit,
|