deepline 0.3.247 → 0.3.249
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 +177 -13
- package/dist/bundling-sources/sdk/src/index.ts +8 -0
- package/dist/bundling-sources/sdk/src/plays/input-example.ts +40 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/overview.ts +457 -0
- package/dist/bundling-sources/sdk/src/runs/receipts.ts +23 -0
- package/dist/bundling-sources/sdk/src/tool-calls.ts +21 -1
- package/dist/bundling-sources/sdk/src/types.ts +16 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +19 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +17 -3
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -8
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +26 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -14
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +6 -3
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +26 -21
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +23 -21
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +105 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +40 -4
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -5
- package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +56 -16
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +327 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +87 -30
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +24 -13
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +23 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +76 -33
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +90 -35
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +112 -50
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +447 -77
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +22 -4
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +105 -121
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +1648 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +16 -19
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +14 -6
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +40 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +5 -2
- package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +11 -7
- package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +5 -2
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +11 -2
- package/dist/cli/index.js +3504 -2368
- package/dist/cli/index.mjs +3188 -2052
- package/dist/{compiler-manifest-n8ZtPv2h.d.mts → compiler-manifest-DavmLa8y.d.mts} +1 -0
- package/dist/{compiler-manifest-n8ZtPv2h.d.ts → compiler-manifest-DavmLa8y.d.ts} +1 -0
- package/dist/index.d.mts +97 -5
- package/dist/index.d.ts +97 -5
- package/dist/index.js +135 -15
- package/dist/index.mjs +135 -15
- package/dist/install-integrity.json +9 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +1 -1
- 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/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CreateSandboxFromSnapshotParams, Sandbox } from '@daytonaio/sdk';
|
|
2
|
+
import { DaytonaLaunchOutcomeUnknownError } from './daytona-launch-transport';
|
|
2
3
|
import type {
|
|
3
4
|
PlayRunnerExecutionConfig,
|
|
4
5
|
PlayRunnerResult,
|
|
@@ -40,8 +41,18 @@ const DAYTONA_NETWORK_ALLOW_LIST_ENV = 'DEEPLINE_DAYTONA_NETWORK_ALLOW_LIST';
|
|
|
40
41
|
|
|
41
42
|
export const DAYTONA_CANCELLED_ERROR = 'Daytona play runner cancelled';
|
|
42
43
|
|
|
43
|
-
export type DaytonaClient =
|
|
44
|
-
|
|
44
|
+
export type DaytonaClient = {
|
|
45
|
+
create(
|
|
46
|
+
params: CreateSandboxFromSnapshotParams,
|
|
47
|
+
options: { timeout: number },
|
|
48
|
+
): Promise<Sandbox>;
|
|
49
|
+
get?(name: string): Promise<Sandbox>;
|
|
50
|
+
delete(sandbox: Pick<Sandbox, 'id'>, timeoutSeconds?: number): Promise<void>;
|
|
51
|
+
scope(
|
|
52
|
+
sandbox: Sandbox,
|
|
53
|
+
timeoutMs: number,
|
|
54
|
+
): { sandbox: Sandbox; dispose(): void };
|
|
55
|
+
};
|
|
45
56
|
export type DaytonaSandbox = Awaited<ReturnType<DaytonaClient['create']>>;
|
|
46
57
|
export type DaytonaExecutionContext = PlayRunnerExecutionConfig['context'];
|
|
47
58
|
export type DaytonaStageEmitter = (
|
|
@@ -55,6 +66,7 @@ type DaytonaCreateCallObserver = (
|
|
|
55
66
|
|
|
56
67
|
export type AcquiredDaytonaSandbox = {
|
|
57
68
|
sandbox: DaytonaSandbox;
|
|
69
|
+
deleteSandbox: () => Promise<void>;
|
|
58
70
|
daytonaOrganizationId: string;
|
|
59
71
|
billingStartedAt: number;
|
|
60
72
|
billingEndedAt?: number;
|
|
@@ -125,6 +137,25 @@ export function resolveDaytonaSandboxAcquisitionUnavailableReason(
|
|
|
125
137
|
return null;
|
|
126
138
|
}
|
|
127
139
|
|
|
140
|
+
function waitForAcquisitionRetry(
|
|
141
|
+
ms: number,
|
|
142
|
+
signal?: AbortSignal,
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
signal?.throwIfAborted();
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const abort = () => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
reject(signal?.reason);
|
|
149
|
+
};
|
|
150
|
+
const timer = setTimeout(() => {
|
|
151
|
+
signal?.removeEventListener('abort', abort);
|
|
152
|
+
resolve();
|
|
153
|
+
}, ms);
|
|
154
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
155
|
+
if (signal?.aborted) abort();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
128
159
|
function isDaytonaSandboxStartTimeout(error: string): boolean {
|
|
129
160
|
return /Failed to create and start sandbox within \d+ seconds\. Operation timed out\./.test(
|
|
130
161
|
error,
|
|
@@ -161,11 +192,12 @@ function daytonaCreateErrorClass(
|
|
|
161
192
|
async function rejectAcquiredSandbox(
|
|
162
193
|
sandbox: DaytonaSandbox,
|
|
163
194
|
reason: string,
|
|
164
|
-
reservation
|
|
165
|
-
releaseSandboxCapacity
|
|
195
|
+
reservation: { leaseId: string } | undefined,
|
|
196
|
+
releaseSandboxCapacity: ((leaseId: string) => Promise<void>) | undefined,
|
|
197
|
+
deleteSandbox: () => Promise<void>,
|
|
166
198
|
): Promise<never> {
|
|
167
199
|
try {
|
|
168
|
-
await
|
|
200
|
+
await deleteSandbox();
|
|
169
201
|
} catch (error) {
|
|
170
202
|
const cleanupError = error instanceof Error ? error.message : String(error);
|
|
171
203
|
throw new Error(
|
|
@@ -346,20 +378,22 @@ type TimedOutDaytonaSandboxReconciliation =
|
|
|
346
378
|
async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
347
379
|
daytona: DaytonaClient;
|
|
348
380
|
sandboxName: string;
|
|
381
|
+
cancellationSignal?: AbortSignal;
|
|
349
382
|
}): Promise<TimedOutDaytonaSandboxReconciliation> {
|
|
350
383
|
if (!input.daytona.get) return { kind: 'not_found' };
|
|
351
384
|
let lastDeleteConflict: unknown = null;
|
|
352
385
|
for (const delayMs of DAYTONA_TIMED_OUT_CREATE_RECONCILE_DELAYS_MS) {
|
|
386
|
+
input.cancellationSignal?.throwIfAborted();
|
|
353
387
|
if (delayMs > 0) {
|
|
354
|
-
await
|
|
388
|
+
await waitForAcquisitionRetry(delayMs, input.cancellationSignal);
|
|
355
389
|
}
|
|
356
390
|
let sandbox: DaytonaSandbox;
|
|
357
391
|
try {
|
|
358
392
|
sandbox = await input.daytona.get(input.sandboxName);
|
|
359
393
|
} catch (error) {
|
|
360
|
-
// A
|
|
361
|
-
//
|
|
362
|
-
//
|
|
394
|
+
// A current absence is not proof that the sent create cannot commit.
|
|
395
|
+
// Return it to the durable operation owner without another create or
|
|
396
|
+
// releasing capacity; later recovery re-reads the same identity.
|
|
363
397
|
if (isDaytonaSandboxNotFound(error)) return { kind: 'not_found' };
|
|
364
398
|
continue;
|
|
365
399
|
}
|
|
@@ -372,7 +406,7 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
372
406
|
return { kind: 'started', sandbox };
|
|
373
407
|
}
|
|
374
408
|
try {
|
|
375
|
-
await
|
|
409
|
+
await input.daytona.delete(sandbox, 30);
|
|
376
410
|
} catch (error) {
|
|
377
411
|
// Daytona can make the named sandbox lookup-addressable before its
|
|
378
412
|
// timed-out create transition has settled. That exact conflict is not a
|
|
@@ -416,9 +450,11 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
416
450
|
releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
|
|
417
451
|
nextProviderAttempt: () => number;
|
|
418
452
|
startedAt: number;
|
|
453
|
+
cancellationSignal?: AbortSignal;
|
|
419
454
|
}): Promise<DaytonaCreateResult> {
|
|
420
455
|
const errors: string[] = [];
|
|
421
456
|
for (const [index, delayMs] of DAYTONA_CREATE_RETRY_DELAYS_MS.entries()) {
|
|
457
|
+
input.cancellationSignal?.throwIfAborted();
|
|
422
458
|
const attempt = input.nextProviderAttempt();
|
|
423
459
|
if (delayMs > 0) {
|
|
424
460
|
input.emitStage('create:retry', {
|
|
@@ -427,7 +463,7 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
427
463
|
reason: 'bounded_backoff',
|
|
428
464
|
delayMs,
|
|
429
465
|
});
|
|
430
|
-
await
|
|
466
|
+
await waitForAcquisitionRetry(delayMs, input.cancellationSignal);
|
|
431
467
|
}
|
|
432
468
|
const attemptStartedAt = Date.now();
|
|
433
469
|
await input.observeCreateCall?.({
|
|
@@ -463,11 +499,9 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
463
499
|
if (!isDaytonaSandboxNotFound(error)) {
|
|
464
500
|
throw new DaytonaSandboxTransitionPendingError(sandboxName);
|
|
465
501
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
`Timed-out Daytona sandbox ${sandboxName} was confirmed absent before resume.`,
|
|
470
|
-
);
|
|
502
|
+
// A lookup 404 does not prove that an earlier sent create cannot still
|
|
503
|
+
// commit. Keep the durable identity reserved; never hedge its outcome.
|
|
504
|
+
throw new DaytonaSandboxTransitionPendingError(sandboxName);
|
|
471
505
|
}
|
|
472
506
|
}
|
|
473
507
|
let sandbox: DaytonaSandbox;
|
|
@@ -507,12 +541,16 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
507
541
|
});
|
|
508
542
|
const immediateFallbackReason =
|
|
509
543
|
resolveDaytonaSandboxAcquisitionUnavailableReason([message]);
|
|
510
|
-
if (
|
|
544
|
+
if (
|
|
545
|
+
error instanceof DaytonaLaunchOutcomeUnknownError ||
|
|
546
|
+
isDaytonaSandboxStartTimeout(message)
|
|
547
|
+
) {
|
|
511
548
|
let reconciliation: TimedOutDaytonaSandboxReconciliation;
|
|
512
549
|
try {
|
|
513
550
|
reconciliation = await reconcileAndDeleteTimedOutDaytonaSandbox({
|
|
514
551
|
daytona: input.daytona,
|
|
515
552
|
sandboxName,
|
|
553
|
+
cancellationSignal: input.cancellationSignal,
|
|
516
554
|
});
|
|
517
555
|
} catch (reconciliationError) {
|
|
518
556
|
await recordCreateFailure();
|
|
@@ -571,6 +609,9 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
571
609
|
`${message} Timed-out Daytona sandbox was reconciled and deleted before fallback.`,
|
|
572
610
|
);
|
|
573
611
|
}
|
|
612
|
+
if (reservation?.providerOperationToken) {
|
|
613
|
+
throw new DaytonaSandboxTransitionPendingError(sandboxName);
|
|
614
|
+
}
|
|
574
615
|
throw new Error(
|
|
575
616
|
`${message} Daytona did not return a cleanup-addressable sandbox identity; Modal fallback suppressed. The named ephemeral sandbox retains Daytona's auto-delete-on-stop backstop.`,
|
|
576
617
|
{ cause: error },
|
|
@@ -645,6 +686,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
645
686
|
nextProviderAttempt: () => number;
|
|
646
687
|
startedAt: number;
|
|
647
688
|
startAdmission?: DaytonaSandboxStartAdmission;
|
|
689
|
+
cancellationSignal?: AbortSignal;
|
|
648
690
|
}): Promise<AcquiredDaytonaSandbox> {
|
|
649
691
|
const limits = validatePlaySandboxRuntimeLimits(
|
|
650
692
|
input.context.sandboxRuntimeLimits ?? {
|
|
@@ -653,8 +695,9 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
653
695
|
);
|
|
654
696
|
input.emitStage('create:start');
|
|
655
697
|
const result = input.startAdmission
|
|
656
|
-
? await input.startAdmission.run(
|
|
657
|
-
createRetriedOneShotDaytonaSandbox(input),
|
|
698
|
+
? await input.startAdmission.run(
|
|
699
|
+
() => createRetriedOneShotDaytonaSandbox(input),
|
|
700
|
+
input.cancellationSignal,
|
|
658
701
|
)
|
|
659
702
|
: await createRetriedOneShotDaytonaSandbox(input);
|
|
660
703
|
// The runner's egress probe measures the sandbox-to-estate round trip; the
|
|
@@ -682,6 +725,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
682
725
|
? { leaseId: result.sandboxCapacityLeaseId }
|
|
683
726
|
: undefined,
|
|
684
727
|
input.releaseSandboxCapacity,
|
|
728
|
+
() => input.daytona.delete(result.sandbox, 30),
|
|
685
729
|
);
|
|
686
730
|
}
|
|
687
731
|
const configuredOrganizationId =
|
|
@@ -699,6 +743,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
699
743
|
? { leaseId: result.sandboxCapacityLeaseId }
|
|
700
744
|
: undefined,
|
|
701
745
|
input.releaseSandboxCapacity,
|
|
746
|
+
() => input.daytona.delete(result.sandbox, 30),
|
|
702
747
|
);
|
|
703
748
|
}
|
|
704
749
|
let lookupOrganizationId: string | null = null;
|
|
@@ -725,6 +770,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
725
770
|
? { leaseId: result.sandboxCapacityLeaseId }
|
|
726
771
|
: undefined,
|
|
727
772
|
input.releaseSandboxCapacity,
|
|
773
|
+
() => input.daytona.delete(result.sandbox, 30),
|
|
728
774
|
);
|
|
729
775
|
}
|
|
730
776
|
const billingStartedAt = Date.now();
|
|
@@ -745,6 +791,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
745
791
|
});
|
|
746
792
|
return {
|
|
747
793
|
sandbox,
|
|
794
|
+
deleteSandbox: () => input.daytona.delete(sandbox, 30),
|
|
748
795
|
daytonaOrganizationId,
|
|
749
796
|
billingStartedAt,
|
|
750
797
|
...(result.sandboxCapacityLeaseId
|
|
@@ -765,6 +812,7 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
|
765
812
|
}>;
|
|
766
813
|
releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
|
|
767
814
|
startAdmission?: DaytonaSandboxStartAdmission;
|
|
815
|
+
cancellationSignal?: AbortSignal;
|
|
768
816
|
startedAt?: number;
|
|
769
817
|
}): OneShotDaytonaSandboxLifecycle {
|
|
770
818
|
const orgId = validateDaytonaExecutionContext(input.context);
|
|
@@ -783,6 +831,7 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
|
783
831
|
reserveSandboxCapacity: input.reserveSandboxCapacity,
|
|
784
832
|
releaseSandboxCapacity: input.releaseSandboxCapacity,
|
|
785
833
|
startAdmission: input.startAdmission,
|
|
834
|
+
cancellationSignal: input.cancellationSignal,
|
|
786
835
|
nextProviderAttempt: () => {
|
|
787
836
|
providerAttempt += 1;
|
|
788
837
|
return providerAttempt;
|
|
@@ -812,7 +861,7 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
|
812
861
|
disposed = true;
|
|
813
862
|
try {
|
|
814
863
|
const acquired = await latestAcquiredSandboxPromise;
|
|
815
|
-
await acquired.
|
|
864
|
+
await acquired.deleteSandbox();
|
|
816
865
|
// `dispose` has provider-confirmed that the sandbox is gone. This is
|
|
817
866
|
// normally the narrow cancellation window before the scheduler has
|
|
818
867
|
// durably recorded the resource, so no cleanup job will release an
|
|
@@ -846,7 +895,7 @@ export function createDaytonaSandboxCleanupManager(): {
|
|
|
846
895
|
activate(acquired: AcquiredDaytonaSandbox): void;
|
|
847
896
|
currentSandbox(): DaytonaSandbox | null;
|
|
848
897
|
cleanupActiveSandboxForCancellation(): boolean;
|
|
849
|
-
stashActiveSandboxForRetry(): void
|
|
898
|
+
stashActiveSandboxForRetry(): Promise<void>;
|
|
850
899
|
withCleanup(
|
|
851
900
|
result: PlayRunnerResult,
|
|
852
901
|
input: { cancellationCleanupStarted: boolean },
|
|
@@ -855,6 +904,11 @@ export function createDaytonaSandboxCleanupManager(): {
|
|
|
855
904
|
let sandbox: DaytonaSandbox | null = null;
|
|
856
905
|
let sandboxBillingStartedAt: number | null = null;
|
|
857
906
|
const deletedSandboxIds = new Set<string>();
|
|
907
|
+
let deleteActive: (() => Promise<void>) | null = null;
|
|
908
|
+
let retirement: Promise<void> | null = null;
|
|
909
|
+
// Cancellation, terminal cleanup, and retry retirement share the same
|
|
910
|
+
// provider operation. A rejected/ambiguous delete still blocks replacement.
|
|
911
|
+
const retireActive = () => (retirement ??= deleteActive!());
|
|
858
912
|
|
|
859
913
|
const deleteSandboxOnce = (input: {
|
|
860
914
|
reason: string;
|
|
@@ -866,7 +920,7 @@ export function createDaytonaSandboxCleanupManager(): {
|
|
|
866
920
|
}
|
|
867
921
|
deletedSandboxIds.add(target.id);
|
|
868
922
|
const billingStartedAt = sandboxBillingStartedAt;
|
|
869
|
-
void
|
|
923
|
+
void retireActive().then(
|
|
870
924
|
() => {
|
|
871
925
|
console.info('[play-runner.daytona.cleanup_done]', {
|
|
872
926
|
reason: input.reason,
|
|
@@ -889,6 +943,8 @@ export function createDaytonaSandboxCleanupManager(): {
|
|
|
889
943
|
return {
|
|
890
944
|
activate(acquired) {
|
|
891
945
|
sandbox = acquired.sandbox;
|
|
946
|
+
deleteActive = acquired.deleteSandbox;
|
|
947
|
+
retirement = null;
|
|
892
948
|
sandboxBillingStartedAt = acquired.billingStartedAt;
|
|
893
949
|
},
|
|
894
950
|
currentSandbox() {
|
|
@@ -900,14 +956,15 @@ export function createDaytonaSandboxCleanupManager(): {
|
|
|
900
956
|
sandboxToDelete: sandbox,
|
|
901
957
|
});
|
|
902
958
|
},
|
|
903
|
-
stashActiveSandboxForRetry() {
|
|
904
|
-
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
959
|
+
async stashActiveSandboxForRetry() {
|
|
960
|
+
if (!sandbox || !deleteActive) return;
|
|
961
|
+
// A response lost after command acceptance can leave a runner alive.
|
|
962
|
+
// No replacement may start until provider-confirmed retirement.
|
|
963
|
+
const target = sandbox;
|
|
964
|
+
await retireActive();
|
|
965
|
+
deletedSandboxIds.add(target.id);
|
|
910
966
|
sandbox = null;
|
|
967
|
+
deleteActive = null;
|
|
911
968
|
sandboxBillingStartedAt = null;
|
|
912
969
|
},
|
|
913
970
|
withCleanup(result, input) {
|
|
@@ -358,6 +358,7 @@ export function prepareDetachedDaytonaRunner(input: {
|
|
|
358
358
|
sessionId: string;
|
|
359
359
|
readiness: DetachedRunnerReadinessPort;
|
|
360
360
|
cancellation?: Promise<never>;
|
|
361
|
+
runControl<T>(operation: (sandbox: DaytonaSandbox) => Promise<T>): Promise<T>;
|
|
361
362
|
/** Invoked immediately after Daytona accepts the detached command. */
|
|
362
363
|
onCommandAccepted?: (
|
|
363
364
|
state: Extract<DetachedDaytonaRunnerState, { phase: 'command_accepted' }>,
|
|
@@ -368,8 +369,8 @@ export function prepareDetachedDaytonaRunner(input: {
|
|
|
368
369
|
) => Promise<void> | void;
|
|
369
370
|
}): DetachedDaytonaRunnerSupervisor {
|
|
370
371
|
const cancellation = input.cancellation ?? new Promise<never>(() => {});
|
|
371
|
-
const sessionCreation = input
|
|
372
|
-
.createSession(input.sessionId)
|
|
372
|
+
const sessionCreation = input
|
|
373
|
+
.runControl((sandbox) => sandbox.process.createSession(input.sessionId))
|
|
373
374
|
.then(
|
|
374
375
|
() => ({ ok: true as const }),
|
|
375
376
|
(error: unknown) => ({ ok: false as const, error }),
|
|
@@ -399,10 +400,12 @@ export function prepareDetachedDaytonaRunner(input: {
|
|
|
399
400
|
const baseline = await Promise.race([readinessBaseline, cancellation]);
|
|
400
401
|
if (baseline.ok === false) throw baseline.error;
|
|
401
402
|
const response = await Promise.race([
|
|
402
|
-
input.sandbox
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
input.runControl((sandbox) =>
|
|
404
|
+
sandbox.process.executeSessionCommand(input.sessionId, {
|
|
405
|
+
command,
|
|
406
|
+
runAsync: true,
|
|
407
|
+
}),
|
|
408
|
+
),
|
|
406
409
|
cancellation,
|
|
407
410
|
]);
|
|
408
411
|
const cmdId = response?.cmdId;
|
|
@@ -427,6 +430,7 @@ export function prepareDetachedDaytonaRunner(input: {
|
|
|
427
430
|
startupDiagnosticPath,
|
|
428
431
|
readiness: input.readiness,
|
|
429
432
|
baselineHeartbeatAt: state.baselineHeartbeatAt,
|
|
433
|
+
runControl: input.runControl,
|
|
430
434
|
}),
|
|
431
435
|
cancellation,
|
|
432
436
|
]);
|
|
@@ -452,6 +456,9 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
|
|
|
452
456
|
startupDiagnosticPath?: string;
|
|
453
457
|
readiness: DetachedRunnerReadinessPort;
|
|
454
458
|
baselineHeartbeatAt: string | null;
|
|
459
|
+
runControl?: <T>(
|
|
460
|
+
operation: (sandbox: DaytonaSandbox) => Promise<T>,
|
|
461
|
+
) => Promise<T>;
|
|
455
462
|
timeoutMs?: number;
|
|
456
463
|
pollMs?: number;
|
|
457
464
|
now?: () => number;
|
|
@@ -488,13 +495,17 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
|
|
|
488
495
|
retryDelayMs = Math.min(1_000, retryDelayMs * 2);
|
|
489
496
|
}
|
|
490
497
|
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
+
const inspect = (sandbox: DaytonaSandbox) =>
|
|
499
|
+
inspectDetachedDaytonaStartup({
|
|
500
|
+
sandbox,
|
|
501
|
+
sessionId: input.sessionId,
|
|
502
|
+
cmdId: input.cmdId,
|
|
503
|
+
exitCodePath: input.exitCodePath,
|
|
504
|
+
startupDiagnosticPath: input.startupDiagnosticPath,
|
|
505
|
+
});
|
|
506
|
+
const diagnostic = await (input.runControl
|
|
507
|
+
? input.runControl(inspect)
|
|
508
|
+
: inspect(input.sandbox));
|
|
498
509
|
diagnostic.schedulerReadinessObserved = false;
|
|
499
510
|
diagnostic.schedulerReadinessResponses = schedulerReadinessResponses;
|
|
500
511
|
const oomIndicated =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RUNTIME_CAPACITY_POLICY } from '../../runtime-capacity-policy';
|
|
2
2
|
|
|
3
3
|
export type DaytonaSandboxStartAdmission = {
|
|
4
|
-
run<T>(operation: () => Promise<T
|
|
4
|
+
run<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -13,9 +13,8 @@ export type DaytonaSandboxStartAdmission = {
|
|
|
13
13
|
* is still transitioning, recreating the unsafe overlap this gate prevents.
|
|
14
14
|
*/
|
|
15
15
|
export function createDaytonaSandboxStartAdmission(
|
|
16
|
-
concurrency =
|
|
17
|
-
|
|
18
|
-
.daytonaConcurrentStartsPerProcess,
|
|
16
|
+
concurrency = RUNTIME_CAPACITY_POLICY.sandboxAcquisition
|
|
17
|
+
.daytonaConcurrentStartsPerProcess,
|
|
19
18
|
): DaytonaSandboxStartAdmission {
|
|
20
19
|
if (!Number.isInteger(concurrency) || concurrency <= 0) {
|
|
21
20
|
throw new Error(
|
|
@@ -43,7 +42,11 @@ export function createDaytonaSandboxStartAdmission(
|
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
return {
|
|
46
|
-
async run<T>(
|
|
45
|
+
async run<T>(
|
|
46
|
+
operation: () => Promise<T>,
|
|
47
|
+
signal?: AbortSignal,
|
|
48
|
+
): Promise<T> {
|
|
49
|
+
signal?.throwIfAborted();
|
|
47
50
|
// Preserve eager prepare semantics when capacity is free: the provider
|
|
48
51
|
// create begins in this turn, just as it did before admission existed.
|
|
49
52
|
// Only contended callers cross an asynchronous queue boundary.
|
|
@@ -53,8 +56,22 @@ export function createDaytonaSandboxStartAdmission(
|
|
|
53
56
|
active += 1;
|
|
54
57
|
return createRelease();
|
|
55
58
|
})()
|
|
56
|
-
: await new Promise<() => void>((resolve) =>
|
|
59
|
+
: await new Promise<() => void>((resolve, reject) => {
|
|
60
|
+
const accept = (release: () => void) => {
|
|
61
|
+
signal?.removeEventListener('abort', abort);
|
|
62
|
+
resolve(release);
|
|
63
|
+
};
|
|
64
|
+
const abort = () => {
|
|
65
|
+
const index = waiters.indexOf(accept);
|
|
66
|
+
if (index >= 0) waiters.splice(index, 1);
|
|
67
|
+
reject(signal?.reason);
|
|
68
|
+
};
|
|
69
|
+
waiters.push(accept);
|
|
70
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
71
|
+
if (signal?.aborted) abort();
|
|
72
|
+
});
|
|
57
73
|
try {
|
|
74
|
+
signal?.throwIfAborted();
|
|
58
75
|
return await operation();
|
|
59
76
|
} finally {
|
|
60
77
|
release();
|