deepline 0.3.128 → 0.3.129
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 +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- 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/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -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 +65 -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
|
@@ -13,13 +13,31 @@ export const RUNTIME_CAPACITY_POLICY = {
|
|
|
13
13
|
/** 32 Machines x 8 claim slots permits 256 concurrent launch/resume legs. */
|
|
14
14
|
maxActiveLaneMachines: 32,
|
|
15
15
|
workerSlotsPerMachine: 8,
|
|
16
|
-
/**
|
|
17
|
-
scaleUpQueueAgeMs:
|
|
16
|
+
/** React within one control interval when a due backlog is not draining. */
|
|
17
|
+
scaleUpQueueAgeMs: 1_000,
|
|
18
18
|
/** Bound one Fly reconciliation without turning backlog into a stampede. */
|
|
19
19
|
maxScaleUpMachinesPerReconcile: 4,
|
|
20
20
|
/** Do not tear down burst capacity on the first empty observation. */
|
|
21
21
|
scaleDownQuietMs: 120_000,
|
|
22
22
|
},
|
|
23
|
+
executionRelay: {
|
|
24
|
+
/** Keep two relay Machines available for deploys and one-machine faults. */
|
|
25
|
+
minimumMachines: 2,
|
|
26
|
+
/** Bound relay spend and the blast radius of a bad demand signal. */
|
|
27
|
+
maxMachines: 16,
|
|
28
|
+
/** The local admission gate's per-Machine upstream permit ceiling. */
|
|
29
|
+
upstreamSlotsPerMachine: 192,
|
|
30
|
+
/** Grow before the local queue reaches its hard shed boundary. */
|
|
31
|
+
scaleUpUtilization: 0.75,
|
|
32
|
+
/** Retain a larger safety margin before removing burst capacity. */
|
|
33
|
+
scaleDownUtilization: 0.35,
|
|
34
|
+
/** One observation may add only a small bounded number of Machines. */
|
|
35
|
+
maxScaleUpMachinesPerReconcile: 2,
|
|
36
|
+
/** Reclaim burst capacity gradually after a long quiet window. */
|
|
37
|
+
maxScaleDownMachinesPerReconcile: 2,
|
|
38
|
+
/** Empty relay capacity must be quiet before it is reclaimed. */
|
|
39
|
+
scaleDownQuietMs: 120_000,
|
|
40
|
+
},
|
|
23
41
|
receiptGateway: {
|
|
24
42
|
admissionTimeoutMs: 10_000,
|
|
25
43
|
/** End identical receipt-row work before the caller can overlap a retry. */
|
|
@@ -33,6 +51,9 @@ export const RUNTIME_CAPACITY_POLICY = {
|
|
|
33
51
|
* once started, all eight execution slots may run concurrently.
|
|
34
52
|
*/
|
|
35
53
|
daytonaConcurrentStartsPerProcess: 1,
|
|
54
|
+
/** Fleet-wide AIMD start pacing, separate from execution-slot scaling. */
|
|
55
|
+
daytonaInitialStartsPerMinute: 120,
|
|
56
|
+
daytonaMaximumStartsPerMinute: 600,
|
|
36
57
|
},
|
|
37
58
|
} as const;
|
|
38
59
|
|
|
@@ -95,6 +116,55 @@ export function desiredAbsurdLaneMachines(input: {
|
|
|
95
116
|
return desired;
|
|
96
117
|
}
|
|
97
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Execution-relay demand is the exact upstream admission state, not request
|
|
121
|
+
* count. Active permits consume capacity and waiting requests are an early
|
|
122
|
+
* backpressure signal. Scale up before the local gate begins shedding, but
|
|
123
|
+
* only by a bounded amount per controller observation. Scale down only after
|
|
124
|
+
* the aggregate relay is empty for the quiet window; the controller applies
|
|
125
|
+
* its own per-pass stop bound when it materializes this target.
|
|
126
|
+
*/
|
|
127
|
+
export function desiredExecutionRelayMachines(input: {
|
|
128
|
+
currentMachines?: number;
|
|
129
|
+
inFlight: number;
|
|
130
|
+
waiting: number;
|
|
131
|
+
idleForMs?: number;
|
|
132
|
+
}): number {
|
|
133
|
+
const policy = RUNTIME_CAPACITY_POLICY.executionRelay;
|
|
134
|
+
const currentMachines = Math.max(
|
|
135
|
+
policy.minimumMachines,
|
|
136
|
+
nonNegativeInteger(input.currentMachines ?? policy.minimumMachines),
|
|
137
|
+
);
|
|
138
|
+
const inFlight = nonNegativeInteger(input.inFlight);
|
|
139
|
+
const waiting = nonNegativeInteger(input.waiting);
|
|
140
|
+
const targetSlots =
|
|
141
|
+
policy.upstreamSlotsPerMachine * policy.scaleUpUtilization;
|
|
142
|
+
let desired = Math.max(
|
|
143
|
+
policy.minimumMachines,
|
|
144
|
+
Math.ceil((inFlight + waiting) / targetSlots),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// A waiter means at least one Machine is already at its local admission
|
|
148
|
+
// boundary. Aggregate active work may still look below the fleet target
|
|
149
|
+
// when the other Machine(s) are quiet, so give the queue one extra slot.
|
|
150
|
+
if (waiting > 0) desired = Math.max(desired, currentMachines + 1);
|
|
151
|
+
|
|
152
|
+
desired = Math.min(
|
|
153
|
+
desired,
|
|
154
|
+
currentMachines + policy.maxScaleUpMachinesPerReconcile,
|
|
155
|
+
policy.maxMachines,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
inFlight === 0 &&
|
|
160
|
+
waiting === 0 &&
|
|
161
|
+
nonNegativeNumber(input.idleForMs ?? 0) < policy.scaleDownQuietMs
|
|
162
|
+
) {
|
|
163
|
+
desired = Math.max(desired, currentMachines);
|
|
164
|
+
}
|
|
165
|
+
return desired;
|
|
166
|
+
}
|
|
167
|
+
|
|
98
168
|
function nonNegativeInteger(value: number): number {
|
|
99
169
|
return Math.max(0, Math.floor(nonNegativeNumber(value)));
|
|
100
170
|
}
|
|
@@ -116,6 +186,19 @@ export function assertRuntimeCapacityPolicy(): void {
|
|
|
116
186
|
'Daytona sandbox-start concurrency must be a positive integer.',
|
|
117
187
|
);
|
|
118
188
|
}
|
|
189
|
+
const sandboxAcquisition = RUNTIME_CAPACITY_POLICY.sandboxAcquisition;
|
|
190
|
+
if (
|
|
191
|
+
!Number.isInteger(sandboxAcquisition.daytonaInitialStartsPerMinute) ||
|
|
192
|
+
!Number.isInteger(sandboxAcquisition.daytonaMaximumStartsPerMinute) ||
|
|
193
|
+
sandboxAcquisition.daytonaInitialStartsPerMinute <= 0 ||
|
|
194
|
+
sandboxAcquisition.daytonaInitialStartsPerMinute >
|
|
195
|
+
sandboxAcquisition.daytonaMaximumStartsPerMinute ||
|
|
196
|
+
sandboxAcquisition.daytonaMaximumStartsPerMinute > 600
|
|
197
|
+
) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'Daytona sandbox-start AIMD rates must be positive integers with initial <= maximum <= 600/minute.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
119
202
|
if (
|
|
120
203
|
RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs <=
|
|
121
204
|
RUNTIME_CAPACITY_POLICY.receiptGateway.admissionTimeoutMs
|
|
@@ -140,6 +223,19 @@ export function assertRuntimeCapacityPolicy(): void {
|
|
|
140
223
|
'Active-lane minimum Machines cannot exceed the active-lane maximum.',
|
|
141
224
|
);
|
|
142
225
|
}
|
|
226
|
+
const relay = RUNTIME_CAPACITY_POLICY.executionRelay;
|
|
227
|
+
if (
|
|
228
|
+
relay.minimumMachines > relay.maxMachines ||
|
|
229
|
+
relay.upstreamSlotsPerMachine <= 0 ||
|
|
230
|
+
relay.scaleUpUtilization <= 0 ||
|
|
231
|
+
relay.scaleUpUtilization > 1 ||
|
|
232
|
+
relay.scaleDownUtilization <= 0 ||
|
|
233
|
+
relay.scaleDownUtilization > relay.scaleUpUtilization
|
|
234
|
+
) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
'Execution-relay capacity policy has invalid floor, ceiling, or utilization bounds.',
|
|
237
|
+
);
|
|
238
|
+
}
|
|
143
239
|
}
|
|
144
240
|
|
|
145
241
|
assertRuntimeCapacityPolicy();
|
|
@@ -58,10 +58,11 @@ export function resolveLegacyRuntimeReceiptWireFormat(
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function runtimeReceiptCompletionResponse<
|
|
61
|
-
T extends { output?: unknown },
|
|
61
|
+
T extends { output?: unknown; outputRef?: unknown },
|
|
62
62
|
>(wireFormat: LegacyRuntimeReceiptWireFormat, receipt: T | null): T | null {
|
|
63
63
|
if (!receipt || wireFormat < 3) return receipt;
|
|
64
|
-
const { output, ...ack } = receipt;
|
|
64
|
+
const { output, outputRef, ...ack } = receipt;
|
|
65
65
|
void output;
|
|
66
|
+
void outputRef;
|
|
66
67
|
return ack as T;
|
|
67
68
|
}
|
|
@@ -10,9 +10,13 @@ export type PlayRuntimeSelection = {
|
|
|
10
10
|
namespace: string;
|
|
11
11
|
/** Explicit managed-sandbox executor. Omission preserves Daytona compatibility. */
|
|
12
12
|
backend?: Extract<PlayRuntimeBackendId, 'daytona' | 'modal'>;
|
|
13
|
+
/** Durable placement contract for the selected Preview worker lane. */
|
|
14
|
+
sandboxPlacementPolicy?: string;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
const PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
|
|
18
|
+
const PLAY_RUNTIME_SANDBOX_PLACEMENT_POLICY_PATTERN =
|
|
19
|
+
/^[a-z][a-z0-9_]*@[1-9][0-9]*$/;
|
|
16
20
|
|
|
17
21
|
export function normalizePlayRuntimeNamespace(value: unknown): string | null {
|
|
18
22
|
if (typeof value !== 'string') return null;
|
|
@@ -20,6 +24,21 @@ export function normalizePlayRuntimeNamespace(value: unknown): string | null {
|
|
|
20
24
|
return PLAY_RUNTIME_NAMESPACE_PATTERN.test(normalized) ? normalized : null;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Policy membership is owned by the server-side policy catalogue. The shared
|
|
29
|
+
* selector only accepts a stable versioned id shape, so unknown policies fail
|
|
30
|
+
* loudly at the authority that knows which worker releases support them.
|
|
31
|
+
*/
|
|
32
|
+
export function normalizePlayRuntimeSandboxPlacementPolicy(
|
|
33
|
+
value: unknown,
|
|
34
|
+
): string | null {
|
|
35
|
+
if (typeof value !== 'string') return null;
|
|
36
|
+
const normalized = value.trim();
|
|
37
|
+
return PLAY_RUNTIME_SANDBOX_PLACEMENT_POLICY_PATTERN.test(normalized)
|
|
38
|
+
? normalized
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
/** Validate the selector as one value so the SDK and server cannot drift. */
|
|
24
43
|
export function normalizePlayRuntimeSelection(
|
|
25
44
|
value: unknown,
|
|
@@ -29,7 +48,10 @@ export function normalizePlayRuntimeSelection(
|
|
|
29
48
|
if (
|
|
30
49
|
Object.keys(record).some(
|
|
31
50
|
(key) =>
|
|
32
|
-
key !== 'environment' &&
|
|
51
|
+
key !== 'environment' &&
|
|
52
|
+
key !== 'namespace' &&
|
|
53
|
+
key !== 'backend' &&
|
|
54
|
+
key !== 'sandboxPlacementPolicy',
|
|
33
55
|
) ||
|
|
34
56
|
record.environment !== 'preview'
|
|
35
57
|
) {
|
|
@@ -37,16 +59,31 @@ export function normalizePlayRuntimeSelection(
|
|
|
37
59
|
}
|
|
38
60
|
const namespace = normalizePlayRuntimeNamespace(record.namespace);
|
|
39
61
|
if (!namespace) return null;
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
const sandboxPlacementPolicy =
|
|
63
|
+
record.sandboxPlacementPolicy === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: normalizePlayRuntimeSandboxPlacementPolicy(
|
|
66
|
+
record.sandboxPlacementPolicy,
|
|
67
|
+
);
|
|
68
|
+
if (
|
|
69
|
+
record.sandboxPlacementPolicy !== undefined &&
|
|
70
|
+
sandboxPlacementPolicy === null
|
|
71
|
+
) {
|
|
72
|
+
return null;
|
|
42
73
|
}
|
|
43
74
|
if (
|
|
75
|
+
record.backend !== undefined &&
|
|
44
76
|
record.backend !== PLAY_RUNTIME_BACKENDS.daytona &&
|
|
45
77
|
record.backend !== PLAY_RUNTIME_BACKENDS.modal
|
|
46
78
|
) {
|
|
47
79
|
return null;
|
|
48
80
|
}
|
|
49
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
environment: 'preview',
|
|
83
|
+
namespace,
|
|
84
|
+
...(record.backend === undefined ? {} : { backend: record.backend }),
|
|
85
|
+
...(sandboxPlacementPolicy ? { sandboxPlacementPolicy } : {}),
|
|
86
|
+
};
|
|
50
87
|
}
|
|
51
88
|
|
|
52
89
|
export function normalizePlayRuntimeEnvironment(
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private runtime admission composition.
|
|
3
|
+
*
|
|
4
|
+
* A flow is deliberately smaller than a queue: it owns no payloads, retries,
|
|
5
|
+
* workers, or durable state. It acquires the capacity already owned by its
|
|
6
|
+
* stages, and releases every acquired stage in reverse order. That gives a
|
|
7
|
+
* map row, provider dispatch, or result handoff one idiomatic permit without
|
|
8
|
+
* inventing a second runtime scheduler.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type RuntimeFlowLease = {
|
|
12
|
+
release(): void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type RuntimeFlowStage<Input> = {
|
|
16
|
+
/** Stable diagnostic name. It never changes execution behavior. */
|
|
17
|
+
name: string;
|
|
18
|
+
acquire(input: Input): Promise<RuntimeFlowLease>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RuntimeFlow<Input> = {
|
|
22
|
+
/**
|
|
23
|
+
* Admit and run one logical unit through every capacity boundary in order.
|
|
24
|
+
*
|
|
25
|
+
* A later boundary rejecting returns earlier permits immediately. The permits
|
|
26
|
+
* stay private to the callback and return in reverse order on every exit.
|
|
27
|
+
*/
|
|
28
|
+
run<Output>(
|
|
29
|
+
input: Input,
|
|
30
|
+
work: () => Output | Promise<Output>,
|
|
31
|
+
): Promise<Output>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function createRuntimeFlow<Input>(input: {
|
|
35
|
+
stages: readonly RuntimeFlowStage<Input>[];
|
|
36
|
+
}): RuntimeFlow<Input> {
|
|
37
|
+
const stages = [...input.stages];
|
|
38
|
+
if (stages.length === 0) {
|
|
39
|
+
throw new Error('Runtime flow requires at least one admission stage.');
|
|
40
|
+
}
|
|
41
|
+
const names = new Set<string>();
|
|
42
|
+
for (const stage of stages) {
|
|
43
|
+
const name = stage.name.trim();
|
|
44
|
+
if (!name) throw new Error('Runtime flow stages require a name.');
|
|
45
|
+
if (names.has(name)) {
|
|
46
|
+
throw new Error(`Runtime flow stage "${name}" was registered twice.`);
|
|
47
|
+
}
|
|
48
|
+
names.add(name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const admit = async (value: Input): Promise<RuntimeFlowLease[]> => {
|
|
52
|
+
const leases: RuntimeFlowLease[] = [];
|
|
53
|
+
try {
|
|
54
|
+
for (const stage of stages) {
|
|
55
|
+
leases.push(await stage.acquire(value));
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
releaseRuntimeFlowLeases(leases);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return leases;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
async run<Output>(
|
|
67
|
+
value: Input,
|
|
68
|
+
work: () => Output | Promise<Output>,
|
|
69
|
+
): Promise<Output> {
|
|
70
|
+
const leases = await admit(value);
|
|
71
|
+
try {
|
|
72
|
+
return await work();
|
|
73
|
+
} finally {
|
|
74
|
+
releaseRuntimeFlowLeases(leases);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A provider permit has a distinct pre-dispatch cancellation path. `park`,
|
|
82
|
+
* when present, returns a due permit that lost the local execution-slot race
|
|
83
|
+
* to its source for immediate local re-draw instead of refunding it.
|
|
84
|
+
*/
|
|
85
|
+
export type RuntimeDispatchProviderPermit = RuntimeFlowLease & {
|
|
86
|
+
cancel(): Promise<void>;
|
|
87
|
+
park?(): Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type RuntimeDispatchFlowInput = {
|
|
91
|
+
providerResourceKey?: string | null;
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
/** Fixtures consume provider capacity but acquire app transport after delay. */
|
|
94
|
+
requiresTransportPermit: boolean;
|
|
95
|
+
/** Emits the request-side transport wait observed by the caller's metrics. */
|
|
96
|
+
onTransportAdmitted?: (waitMs: number) => void;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
type RuntimeDispatchPermit<
|
|
100
|
+
ProviderPermit extends RuntimeDispatchProviderPermit,
|
|
101
|
+
> = {
|
|
102
|
+
providerPermit: ProviderPermit;
|
|
103
|
+
executionSlot: RuntimeFlowLease;
|
|
104
|
+
integrationRequestLease: RuntimeFlowLease | null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The only capability exposed while a provider-dispatch flow is admitted.
|
|
109
|
+
*
|
|
110
|
+
* `request` brackets the physical HTTP exchange, including consuming its
|
|
111
|
+
* body. It never exposes the provider, execution, or transport leases, so a
|
|
112
|
+
* caller cannot accidentally retain capacity after that exchange or return a
|
|
113
|
+
* provider permit before it knows whether the request began.
|
|
114
|
+
*/
|
|
115
|
+
export type RuntimeDispatchScope = {
|
|
116
|
+
/**
|
|
117
|
+
* Run the one physical request. `beforeRelease` executes after the response
|
|
118
|
+
* is known but while the provider lease is still valid, so a caller can
|
|
119
|
+
* durably record response-bound feedback that the lease authorizes.
|
|
120
|
+
*/
|
|
121
|
+
request<Output>(
|
|
122
|
+
work: () => Output | Promise<Output>,
|
|
123
|
+
beforeRelease?: () => void | Promise<void>,
|
|
124
|
+
): Promise<Output>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type RuntimeDispatchFlow<
|
|
128
|
+
ProviderPermit extends RuntimeDispatchProviderPermit,
|
|
129
|
+
> = {
|
|
130
|
+
/**
|
|
131
|
+
* Admit and run exactly one physical provider request.
|
|
132
|
+
*
|
|
133
|
+
* Work that exits before `request` returns the provider permit through its
|
|
134
|
+
* cancellation path. Once `request` starts, the permit is released after
|
|
135
|
+
* the request body is consumed, even if that body cannot be verified. This
|
|
136
|
+
* preserves the ambiguity boundary while making resource lifetime private.
|
|
137
|
+
*/
|
|
138
|
+
run<Output>(
|
|
139
|
+
input: RuntimeDispatchFlowInput,
|
|
140
|
+
work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
|
|
141
|
+
): Promise<Output>;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Compose provider, sandbox-execution, and app-transport capacity at the
|
|
146
|
+
* dispatch edge. A provider permit is only useful when the local execution
|
|
147
|
+
* slot (and, for a live call, app transport) is immediately available. If a
|
|
148
|
+
* later stage is full, this flow returns every acquired permit and observes
|
|
149
|
+
* that stage's next availability. An execution slot won that way is kept
|
|
150
|
+
* only if a provider permit is already due locally (`tryAcquireProviderPermit`);
|
|
151
|
+
* otherwise the flow releases it and admits from the provider boundary
|
|
152
|
+
* again. No execution slot is ever held across a blocking provider acquire.
|
|
153
|
+
*
|
|
154
|
+
* It is not a queue and retains no tool input or result. The caller receives
|
|
155
|
+
* only a scoped request callback; Flow releases or cancels every lease based
|
|
156
|
+
* on whether that callback actually began.
|
|
157
|
+
*/
|
|
158
|
+
export function createRuntimeDispatchFlow<
|
|
159
|
+
ProviderPermit extends RuntimeDispatchProviderPermit,
|
|
160
|
+
>(input: {
|
|
161
|
+
acquireProviderPermit: (input: {
|
|
162
|
+
signal?: AbortSignal;
|
|
163
|
+
}) => Promise<ProviderPermit>;
|
|
164
|
+
/** Synchronous local-only admission; must never block or go remote. */
|
|
165
|
+
tryAcquireProviderPermit?: () => ProviderPermit | null;
|
|
166
|
+
tryAcquireExecutionSlot: (input: {
|
|
167
|
+
providerResourceKey?: string | null;
|
|
168
|
+
}) => RuntimeFlowLease | null;
|
|
169
|
+
waitForExecutionSlot: (input: {
|
|
170
|
+
providerResourceKey?: string | null;
|
|
171
|
+
signal?: AbortSignal;
|
|
172
|
+
}) => Promise<RuntimeFlowLease>;
|
|
173
|
+
tryAcquireTransportSlot: () => RuntimeFlowLease | null;
|
|
174
|
+
waitForTransportSlot: (input: {
|
|
175
|
+
signal?: AbortSignal;
|
|
176
|
+
}) => Promise<RuntimeFlowLease>;
|
|
177
|
+
}): RuntimeDispatchFlow<ProviderPermit> {
|
|
178
|
+
const admit = async (
|
|
179
|
+
admission: RuntimeDispatchFlowInput,
|
|
180
|
+
): Promise<RuntimeDispatchPermit<ProviderPermit>> => {
|
|
181
|
+
while (true) {
|
|
182
|
+
let providerPermit: ProviderPermit | null = null;
|
|
183
|
+
let executionSlot: RuntimeFlowLease | null = null;
|
|
184
|
+
const releaseExecutionSlot = (): void => {
|
|
185
|
+
const slot = executionSlot;
|
|
186
|
+
executionSlot = null;
|
|
187
|
+
slot?.release();
|
|
188
|
+
};
|
|
189
|
+
const cancelProviderPermit = async (): Promise<void> => {
|
|
190
|
+
const permit = providerPermit;
|
|
191
|
+
providerPermit = null;
|
|
192
|
+
await permit?.cancel();
|
|
193
|
+
};
|
|
194
|
+
// The execution-slot wait is brief and the permit is already due, so
|
|
195
|
+
// keep it local for the retry instead of refunding it to the store.
|
|
196
|
+
const parkProviderPermit = async (): Promise<void> => {
|
|
197
|
+
const permit = providerPermit;
|
|
198
|
+
providerPermit = null;
|
|
199
|
+
if (!permit) return;
|
|
200
|
+
if (permit.park) await permit.park();
|
|
201
|
+
else await permit.cancel();
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
providerPermit = await input.acquireProviderPermit({
|
|
205
|
+
signal: admission.signal,
|
|
206
|
+
});
|
|
207
|
+
executionSlot = input.tryAcquireExecutionSlot({
|
|
208
|
+
providerResourceKey: admission.providerResourceKey,
|
|
209
|
+
});
|
|
210
|
+
if (!executionSlot) {
|
|
211
|
+
await parkProviderPermit();
|
|
212
|
+
executionSlot = await input.waitForExecutionSlot({
|
|
213
|
+
providerResourceKey: admission.providerResourceKey,
|
|
214
|
+
signal: admission.signal,
|
|
215
|
+
});
|
|
216
|
+
// Releasing this slot would hand it to the next FIFO waiter, so
|
|
217
|
+
// keep it if the parked (or any due) permit can be re-drawn without
|
|
218
|
+
// blocking. Otherwise the blocking provider acquire must not run
|
|
219
|
+
// while a slot is held.
|
|
220
|
+
providerPermit = input.tryAcquireProviderPermit
|
|
221
|
+
? input.tryAcquireProviderPermit()
|
|
222
|
+
: null;
|
|
223
|
+
if (!providerPermit) {
|
|
224
|
+
releaseExecutionSlot();
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!admission.requiresTransportPermit) {
|
|
230
|
+
return {
|
|
231
|
+
providerPermit,
|
|
232
|
+
executionSlot,
|
|
233
|
+
integrationRequestLease: null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const integrationRequestLease = input.tryAcquireTransportSlot();
|
|
238
|
+
if (integrationRequestLease) {
|
|
239
|
+
return {
|
|
240
|
+
providerPermit,
|
|
241
|
+
executionSlot,
|
|
242
|
+
integrationRequestLease,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
releaseExecutionSlot();
|
|
247
|
+
await cancelProviderPermit();
|
|
248
|
+
const availability = await input.waitForTransportSlot({
|
|
249
|
+
signal: admission.signal,
|
|
250
|
+
});
|
|
251
|
+
availability.release();
|
|
252
|
+
} catch (error) {
|
|
253
|
+
await cleanupRuntimeDispatchAdmissionFailure({
|
|
254
|
+
cause: error,
|
|
255
|
+
executionSlot,
|
|
256
|
+
providerPermit,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
async run<Output>(
|
|
264
|
+
admission: RuntimeDispatchFlowInput,
|
|
265
|
+
work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
|
|
266
|
+
): Promise<Output> {
|
|
267
|
+
const permit = await admit(admission);
|
|
268
|
+
let transportLease = permit.integrationRequestLease;
|
|
269
|
+
let requestStarted = false;
|
|
270
|
+
let requestUsed = false;
|
|
271
|
+
let scopeOpen = true;
|
|
272
|
+
let requestPromise: Promise<unknown> | null = null;
|
|
273
|
+
let permitReleased = false;
|
|
274
|
+
const releasePermit = async (): Promise<void> => {
|
|
275
|
+
if (permitReleased) return;
|
|
276
|
+
permitReleased = true;
|
|
277
|
+
await releaseRuntimeDispatchPermit({
|
|
278
|
+
transportLease,
|
|
279
|
+
executionSlot: permit.executionSlot,
|
|
280
|
+
providerPermit: permit.providerPermit,
|
|
281
|
+
requestStarted,
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
try {
|
|
285
|
+
const output = await work({
|
|
286
|
+
request: <RequestOutput>(
|
|
287
|
+
request: () => RequestOutput | Promise<RequestOutput>,
|
|
288
|
+
beforeRelease?: () => void | Promise<void>,
|
|
289
|
+
): Promise<RequestOutput> => {
|
|
290
|
+
if (!scopeOpen) {
|
|
291
|
+
return Promise.reject(
|
|
292
|
+
new Error('Runtime dispatch flow scope has already closed.'),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (requestUsed) {
|
|
296
|
+
return Promise.reject(
|
|
297
|
+
new Error(
|
|
298
|
+
'Runtime dispatch flow permits exactly one physical request.',
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
requestUsed = true;
|
|
303
|
+
requestPromise = (async () => {
|
|
304
|
+
try {
|
|
305
|
+
if (transportLease) {
|
|
306
|
+
admission.onTransportAdmitted?.(0);
|
|
307
|
+
} else {
|
|
308
|
+
const transportStartedAt = Date.now();
|
|
309
|
+
transportLease = await input.waitForTransportSlot({
|
|
310
|
+
signal: admission.signal,
|
|
311
|
+
});
|
|
312
|
+
admission.onTransportAdmitted?.(
|
|
313
|
+
Date.now() - transportStartedAt,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
requestStarted = true;
|
|
317
|
+
return await request();
|
|
318
|
+
} finally {
|
|
319
|
+
// Feedback is response-bound, but it is not allowed to strand
|
|
320
|
+
// the capacity that made the response possible. Preserve the
|
|
321
|
+
// ordering (feedback while the provider lease is valid) and
|
|
322
|
+
// always release all leases even when feedback persistence
|
|
323
|
+
// fails.
|
|
324
|
+
try {
|
|
325
|
+
if (requestStarted) await beforeRelease?.();
|
|
326
|
+
} finally {
|
|
327
|
+
await releasePermit();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
})();
|
|
331
|
+
return requestPromise as Promise<RequestOutput>;
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
// A caller normally returns `await request(...)`. Waiting here also
|
|
335
|
+
// makes an accidental fire-and-forget request drain before its scope
|
|
336
|
+
// can close and prevents an early release of its capacity.
|
|
337
|
+
await requestPromise;
|
|
338
|
+
return output;
|
|
339
|
+
} finally {
|
|
340
|
+
scopeOpen = false;
|
|
341
|
+
// `work` may throw after it deliberately started a fire-and-forget
|
|
342
|
+
// request. That request has already claimed this permit's ownership,
|
|
343
|
+
// even when it is still waiting to acquire transport. Drain it before
|
|
344
|
+
// the outer cleanup: releasing first would make the request's own
|
|
345
|
+
// finally a no-op and leak a transport lease it acquires afterwards.
|
|
346
|
+
// Its rejection cannot replace the original work failure, but its
|
|
347
|
+
// cleanup must finish before any permit is returned.
|
|
348
|
+
if (requestPromise) {
|
|
349
|
+
try {
|
|
350
|
+
await requestPromise;
|
|
351
|
+
} catch {
|
|
352
|
+
// The normal path above already surfaces request failures. This
|
|
353
|
+
// path only preserves an earlier failure from `work`.
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
await releasePermit();
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function cleanupRuntimeDispatchAdmissionFailure<
|
|
363
|
+
ProviderPermit extends RuntimeDispatchProviderPermit,
|
|
364
|
+
>(input: {
|
|
365
|
+
cause: unknown;
|
|
366
|
+
executionSlot: RuntimeFlowLease | null;
|
|
367
|
+
providerPermit: ProviderPermit | null;
|
|
368
|
+
}): Promise<never> {
|
|
369
|
+
const failures = [input.cause];
|
|
370
|
+
try {
|
|
371
|
+
input.executionSlot?.release();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
failures.push(error);
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
await input.providerPermit?.cancel();
|
|
377
|
+
} catch (error) {
|
|
378
|
+
failures.push(error);
|
|
379
|
+
}
|
|
380
|
+
if (failures.length === 1) {
|
|
381
|
+
throw input.cause;
|
|
382
|
+
}
|
|
383
|
+
throw new AggregateError(
|
|
384
|
+
failures,
|
|
385
|
+
'Runtime dispatch admission and cleanup both failed.',
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function releaseRuntimeFlowLeases(leases: readonly RuntimeFlowLease[]): void {
|
|
390
|
+
let failure: unknown = null;
|
|
391
|
+
let hasFailure = false;
|
|
392
|
+
for (let index = leases.length - 1; index >= 0; index -= 1) {
|
|
393
|
+
try {
|
|
394
|
+
leases[index]?.release();
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (!hasFailure) {
|
|
397
|
+
failure = error;
|
|
398
|
+
hasFailure = true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (hasFailure) {
|
|
403
|
+
throw failure;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function releaseRuntimeDispatchPermit<
|
|
408
|
+
ProviderPermit extends RuntimeDispatchProviderPermit,
|
|
409
|
+
>(input: {
|
|
410
|
+
transportLease: RuntimeFlowLease | null;
|
|
411
|
+
executionSlot: RuntimeFlowLease;
|
|
412
|
+
providerPermit: ProviderPermit;
|
|
413
|
+
requestStarted: boolean;
|
|
414
|
+
}): Promise<void> {
|
|
415
|
+
let failure: unknown = null;
|
|
416
|
+
let hasFailure = false;
|
|
417
|
+
const release = (work: () => void): void => {
|
|
418
|
+
try {
|
|
419
|
+
work();
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (!hasFailure) {
|
|
422
|
+
failure = error;
|
|
423
|
+
hasFailure = true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const releaseAsync = async (work: () => Promise<void>): Promise<void> => {
|
|
428
|
+
try {
|
|
429
|
+
await work();
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (!hasFailure) {
|
|
432
|
+
failure = error;
|
|
433
|
+
hasFailure = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
release(() => input.transportLease?.release());
|
|
439
|
+
release(() => input.executionSlot.release());
|
|
440
|
+
if (input.requestStarted) {
|
|
441
|
+
release(() => input.providerPermit.release());
|
|
442
|
+
} else {
|
|
443
|
+
await releaseAsync(async () => await input.providerPermit.cancel());
|
|
444
|
+
}
|
|
445
|
+
if (hasFailure) {
|
|
446
|
+
throw failure;
|
|
447
|
+
}
|
|
448
|
+
}
|