deepline 0.1.291 → 0.1.293
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/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +81 -3
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +4 -46
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +0 -14
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +159 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +50 -29
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +6 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +5 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +76 -11
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +410 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +494 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +0 -343
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +0 -69
- package/dist/bundling-sources/shared_libs/plays/legacy-webhook-drain.ts +0 -110
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One
|
|
2
|
+
* One capacity policy for the Absurd runtime.
|
|
3
3
|
*
|
|
4
|
-
* Postgres owns backlog.
|
|
5
|
-
*
|
|
6
|
-
* together so the worker,
|
|
7
|
-
* independently invent concurrency or timeout budgets.
|
|
4
|
+
* Postgres owns backlog. The active lane adds workers from durable claimable
|
|
5
|
+
* demand and oldest-claimable age; historical lanes stay cold unless they own
|
|
6
|
+
* runnable work. Keep these values together so the worker, scaler, gateway,
|
|
7
|
+
* and tests cannot independently invent concurrency or timeout budgets.
|
|
8
8
|
*/
|
|
9
9
|
export const RUNTIME_CAPACITY_POLICY = {
|
|
10
10
|
absurd: {
|
|
11
|
+
/** Warm floor for availability and deploy rollovers. */
|
|
11
12
|
activeLaneMachines: 2,
|
|
13
|
+
/** 32 Machines x 8 claim slots permits 256 concurrent launch/resume legs. */
|
|
14
|
+
maxActiveLaneMachines: 32,
|
|
12
15
|
workerSlotsPerMachine: 8,
|
|
13
16
|
perOrgConcurrency: 4,
|
|
17
|
+
/** Queue age is an escape hatch for small-but-stuck backlogs. */
|
|
18
|
+
scaleUpQueueAgeMs: 10_000,
|
|
19
|
+
/** Bound one Fly reconciliation without turning backlog into a stampede. */
|
|
20
|
+
maxScaleUpMachinesPerReconcile: 4,
|
|
21
|
+
/** Do not tear down burst capacity on the first empty observation. */
|
|
22
|
+
scaleDownQuietMs: 120_000,
|
|
14
23
|
},
|
|
15
24
|
receiptGateway: {
|
|
16
25
|
admissionTimeoutMs: 10_000,
|
|
@@ -19,22 +28,70 @@ export const RUNTIME_CAPACITY_POLICY = {
|
|
|
19
28
|
} as const;
|
|
20
29
|
|
|
21
30
|
export const ABSURD_GLOBAL_RUN_CONCURRENCY =
|
|
22
|
-
RUNTIME_CAPACITY_POLICY.absurd.
|
|
31
|
+
RUNTIME_CAPACITY_POLICY.absurd.maxActiveLaneMachines *
|
|
23
32
|
RUNTIME_CAPACITY_POLICY.absurd.workerSlotsPerMachine;
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
|
-
* Active releases
|
|
27
|
-
*
|
|
35
|
+
* Active releases use pg-boss-style burst semantics: claimable count sets the
|
|
36
|
+
* target, an aged claimable queue forces continued growth, and growth per
|
|
37
|
+
* observation is bounded. A short/empty observation ends burst growth; the
|
|
38
|
+
* caller supplies idleForMs to hold excess capacity through a quiet window.
|
|
39
|
+
*
|
|
40
|
+
* Historical releases stay cold until they have immediately runnable or
|
|
41
|
+
* currently claimed work.
|
|
28
42
|
*/
|
|
29
43
|
export function desiredAbsurdLaneMachines(input: {
|
|
30
44
|
active: boolean;
|
|
31
45
|
claimableRuns: number;
|
|
32
46
|
runningRuns?: number;
|
|
47
|
+
currentMachines?: number;
|
|
48
|
+
oldestClaimableAgeMs?: number;
|
|
49
|
+
idleForMs?: number;
|
|
33
50
|
}): number {
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
const claimableRuns = nonNegativeInteger(input.claimableRuns);
|
|
52
|
+
const runningRuns = nonNegativeInteger(input.runningRuns ?? 0);
|
|
53
|
+
if (!input.active) return claimableRuns > 0 || runningRuns > 0 ? 1 : 0;
|
|
54
|
+
|
|
55
|
+
const policy = RUNTIME_CAPACITY_POLICY.absurd;
|
|
56
|
+
const currentMachines = Math.max(
|
|
57
|
+
policy.activeLaneMachines,
|
|
58
|
+
nonNegativeInteger(input.currentMachines ?? policy.activeLaneMachines),
|
|
59
|
+
);
|
|
60
|
+
const demandedMachines = Math.ceil(
|
|
61
|
+
(claimableRuns + runningRuns) / policy.workerSlotsPerMachine,
|
|
62
|
+
);
|
|
63
|
+
let desired = Math.max(policy.activeLaneMachines, demandedMachines);
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
claimableRuns > 0 &&
|
|
67
|
+
nonNegativeNumber(input.oldestClaimableAgeMs ?? 0) >=
|
|
68
|
+
policy.scaleUpQueueAgeMs
|
|
69
|
+
) {
|
|
70
|
+
desired = Math.max(desired, currentMachines + 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
desired = Math.min(
|
|
74
|
+
desired,
|
|
75
|
+
currentMachines + policy.maxScaleUpMachinesPerReconcile,
|
|
76
|
+
policy.maxActiveLaneMachines,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
claimableRuns === 0 &&
|
|
81
|
+
runningRuns === 0 &&
|
|
82
|
+
nonNegativeNumber(input.idleForMs ?? 0) < policy.scaleDownQuietMs
|
|
83
|
+
) {
|
|
84
|
+
desired = Math.max(desired, currentMachines);
|
|
36
85
|
}
|
|
37
|
-
return
|
|
86
|
+
return desired;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function nonNegativeInteger(value: number): number {
|
|
90
|
+
return Math.max(0, Math.floor(nonNegativeNumber(value)));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function nonNegativeNumber(value: number): number {
|
|
94
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
38
95
|
}
|
|
39
96
|
|
|
40
97
|
export function assertRuntimeCapacityPolicy(): void {
|
|
@@ -54,6 +111,14 @@ export function assertRuntimeCapacityPolicy(): void {
|
|
|
54
111
|
'Per-org runtime concurrency cannot exceed global runtime concurrency.',
|
|
55
112
|
);
|
|
56
113
|
}
|
|
114
|
+
if (
|
|
115
|
+
RUNTIME_CAPACITY_POLICY.absurd.activeLaneMachines >
|
|
116
|
+
RUNTIME_CAPACITY_POLICY.absurd.maxActiveLaneMachines
|
|
117
|
+
) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'Active-lane minimum Machines cannot exceed the active-lane maximum.',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
57
122
|
}
|
|
58
123
|
|
|
59
124
|
assertRuntimeCapacityPolicy();
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AcquireRuntimeReceiptExecutionLockInput,
|
|
3
|
+
ClaimRuntimeStepReceiptInput,
|
|
4
|
+
CompleteRuntimeStepReceiptInput,
|
|
5
|
+
ContextOptions,
|
|
6
|
+
FailRuntimeStepReceiptInput,
|
|
7
|
+
ReleaseRuntimeReceiptExecutionLockInput,
|
|
8
|
+
ReleaseRuntimeStepReceiptInput,
|
|
9
|
+
RuntimeStepReceipt,
|
|
10
|
+
SkipRuntimeStepReceiptInput,
|
|
11
|
+
} from './ctx-types';
|
|
12
|
+
import {
|
|
13
|
+
acquireRuntimeReceiptExecutionLockViaAppRuntime,
|
|
14
|
+
AppRuntimeApiResponseError,
|
|
15
|
+
AppRuntimeApiTransportError,
|
|
16
|
+
claimRuntimeStepReceiptsViaAppRuntime,
|
|
17
|
+
completeRuntimeStepReceiptsViaAppRuntime,
|
|
18
|
+
failRuntimeStepReceiptsViaAppRuntime,
|
|
19
|
+
getRuntimeStepReceiptsViaAppRuntime,
|
|
20
|
+
heartbeatRuntimeStepReceiptsViaAppRuntime,
|
|
21
|
+
isAppRuntimeApiCapacityError,
|
|
22
|
+
releaseRuntimeReceiptExecutionLockViaAppRuntime,
|
|
23
|
+
releaseRuntimeStepReceiptViaAppRuntime,
|
|
24
|
+
skipRuntimeStepReceiptViaAppRuntime,
|
|
25
|
+
type WorkerRuntimeApiContext,
|
|
26
|
+
} from './app-runtime-api';
|
|
27
|
+
import { RuntimeReceiptWriter } from './runtime-receipt-writer';
|
|
28
|
+
|
|
29
|
+
type ReceiptOutput =
|
|
30
|
+
| RuntimeStepReceipt
|
|
31
|
+
| null
|
|
32
|
+
| boolean
|
|
33
|
+
| { ownerExecutionId: string; expiresAt: string };
|
|
34
|
+
|
|
35
|
+
type ReceiptCommand =
|
|
36
|
+
| {
|
|
37
|
+
kind: 'get';
|
|
38
|
+
input: { key: string; runId: string };
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
kind: 'claim';
|
|
42
|
+
input: ClaimRuntimeStepReceiptInput;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
kind: 'settle';
|
|
46
|
+
outcome: 'complete';
|
|
47
|
+
input: CompleteRuntimeStepReceiptInput;
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
kind: 'settle';
|
|
51
|
+
outcome: 'fail';
|
|
52
|
+
input: FailRuntimeStepReceiptInput;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
kind: 'heartbeat';
|
|
56
|
+
input: {
|
|
57
|
+
key: string;
|
|
58
|
+
runId: string;
|
|
59
|
+
runAttempt?: number | null;
|
|
60
|
+
leaseId: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
kind: 'settle';
|
|
65
|
+
outcome: 'release';
|
|
66
|
+
input: ReleaseRuntimeStepReceiptInput;
|
|
67
|
+
serial: number;
|
|
68
|
+
}
|
|
69
|
+
| {
|
|
70
|
+
kind: 'settle';
|
|
71
|
+
outcome: 'skip';
|
|
72
|
+
input: SkipRuntimeStepReceiptInput;
|
|
73
|
+
serial: number;
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
kind: 'acquire_execution_lock';
|
|
77
|
+
input: AcquireRuntimeReceiptExecutionLockInput;
|
|
78
|
+
serial: number;
|
|
79
|
+
}
|
|
80
|
+
| {
|
|
81
|
+
kind: 'release_execution_lock';
|
|
82
|
+
input: ReleaseRuntimeReceiptExecutionLockInput;
|
|
83
|
+
serial: number;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type ReceiptHandlerName =
|
|
87
|
+
| 'acquireRuntimeReceiptExecutionLock'
|
|
88
|
+
| 'releaseRuntimeReceiptExecutionLock'
|
|
89
|
+
| 'getRuntimeStepReceipt'
|
|
90
|
+
| 'getRuntimeStepReceipts'
|
|
91
|
+
| 'claimRuntimeStepReceipt'
|
|
92
|
+
| 'claimRuntimeStepReceipts'
|
|
93
|
+
| 'completeRuntimeStepReceipt'
|
|
94
|
+
| 'completeRuntimeStepReceipts'
|
|
95
|
+
| 'releaseRuntimeStepReceipt'
|
|
96
|
+
| 'failRuntimeStepReceipt'
|
|
97
|
+
| 'failRuntimeStepReceipts'
|
|
98
|
+
| 'heartbeatRuntimeStepReceipts'
|
|
99
|
+
| 'skipRuntimeStepReceipt';
|
|
100
|
+
|
|
101
|
+
export type RuntimeReceiptStoreHandlers = Required<
|
|
102
|
+
Pick<ContextOptions, ReceiptHandlerName>
|
|
103
|
+
>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The runner's single Work Receipt Adapter.
|
|
107
|
+
*
|
|
108
|
+
* Receipt-domain batching and gateway transport stay behind this seam. Every
|
|
109
|
+
* command shares one writer, so heterogeneous operations cannot produce
|
|
110
|
+
* concurrent gateway requests. The low-level runtime client is explicitly
|
|
111
|
+
* one-attempt; this Adapter is the sole retry owner.
|
|
112
|
+
*/
|
|
113
|
+
export class RuntimeReceiptStoreAdapter {
|
|
114
|
+
readonly handlers: RuntimeReceiptStoreHandlers;
|
|
115
|
+
readonly #writer: RuntimeReceiptWriter<ReceiptCommand, ReceiptOutput>;
|
|
116
|
+
#serial = 0;
|
|
117
|
+
|
|
118
|
+
constructor(options: {
|
|
119
|
+
context: WorkerRuntimeApiContext;
|
|
120
|
+
playName: string;
|
|
121
|
+
runId: string;
|
|
122
|
+
maxBatchSize?: number;
|
|
123
|
+
maxFlushMs?: number;
|
|
124
|
+
maxBufferedBytes?: number;
|
|
125
|
+
}) {
|
|
126
|
+
const context: WorkerRuntimeApiContext = {
|
|
127
|
+
...options.context,
|
|
128
|
+
retryPolicy: 'none',
|
|
129
|
+
};
|
|
130
|
+
this.#writer = new RuntimeReceiptWriter({
|
|
131
|
+
batchKey: receiptCommandBatchKey,
|
|
132
|
+
maxBatchSize: options.maxBatchSize,
|
|
133
|
+
maxFlushMs: options.maxFlushMs,
|
|
134
|
+
maxBufferedBytes: options.maxBufferedBytes,
|
|
135
|
+
classifyRetryableError: classifyReceiptRetry,
|
|
136
|
+
send: async (commands, { signal }) =>
|
|
137
|
+
await sendReceiptCommands({
|
|
138
|
+
context: { ...context, signal },
|
|
139
|
+
playName: options.playName,
|
|
140
|
+
commands,
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const write = <Output extends ReceiptOutput>(
|
|
145
|
+
command: ReceiptCommand,
|
|
146
|
+
): Promise<Output> =>
|
|
147
|
+
this.#writer.write(command, {
|
|
148
|
+
signal: options.context.signal,
|
|
149
|
+
}) as Promise<Output>;
|
|
150
|
+
const writeMany = <Output extends ReceiptOutput>(
|
|
151
|
+
commands: readonly ReceiptCommand[],
|
|
152
|
+
): Promise<Output[]> =>
|
|
153
|
+
Promise.all(commands.map((command) => write<Output>(command)));
|
|
154
|
+
|
|
155
|
+
this.handlers = {
|
|
156
|
+
acquireRuntimeReceiptExecutionLock: (input) =>
|
|
157
|
+
write({
|
|
158
|
+
kind: 'acquire_execution_lock',
|
|
159
|
+
input,
|
|
160
|
+
serial: this.#nextSerial(),
|
|
161
|
+
}),
|
|
162
|
+
releaseRuntimeReceiptExecutionLock: (input) =>
|
|
163
|
+
write({
|
|
164
|
+
kind: 'release_execution_lock',
|
|
165
|
+
input,
|
|
166
|
+
serial: this.#nextSerial(),
|
|
167
|
+
}),
|
|
168
|
+
getRuntimeStepReceipt: ({ key }) =>
|
|
169
|
+
write({
|
|
170
|
+
kind: 'get',
|
|
171
|
+
input: { key, runId: options.runId },
|
|
172
|
+
}),
|
|
173
|
+
getRuntimeStepReceipts: ({ keys }) =>
|
|
174
|
+
writeMany(
|
|
175
|
+
keys.map((key) => ({
|
|
176
|
+
kind: 'get' as const,
|
|
177
|
+
input: { key, runId: options.runId },
|
|
178
|
+
})),
|
|
179
|
+
),
|
|
180
|
+
claimRuntimeStepReceipt: (input) => write({ kind: 'claim', input }),
|
|
181
|
+
claimRuntimeStepReceipts: (input) =>
|
|
182
|
+
writeMany(
|
|
183
|
+
input.keys.map((key, index) => ({
|
|
184
|
+
kind: 'claim' as const,
|
|
185
|
+
input: {
|
|
186
|
+
key,
|
|
187
|
+
runId: input.runId,
|
|
188
|
+
runAttempt: input.runAttempt,
|
|
189
|
+
leaseAware: input.leaseAware,
|
|
190
|
+
reclaimRunning: input.reclaimRunning,
|
|
191
|
+
forceRefresh: input.forceRefresh,
|
|
192
|
+
forceFailedRefresh: input.forceFailedRefresh,
|
|
193
|
+
...(input.leaseIds ? { leaseId: input.leaseIds[index] } : {}),
|
|
194
|
+
},
|
|
195
|
+
})),
|
|
196
|
+
),
|
|
197
|
+
completeRuntimeStepReceipt: (input) =>
|
|
198
|
+
write({ kind: 'settle', outcome: 'complete', input }),
|
|
199
|
+
completeRuntimeStepReceipts: ({ receipts }) =>
|
|
200
|
+
writeMany(
|
|
201
|
+
receipts.map((input) => ({
|
|
202
|
+
kind: 'settle' as const,
|
|
203
|
+
outcome: 'complete' as const,
|
|
204
|
+
input,
|
|
205
|
+
})),
|
|
206
|
+
),
|
|
207
|
+
releaseRuntimeStepReceipt: (input) =>
|
|
208
|
+
write({
|
|
209
|
+
kind: 'settle',
|
|
210
|
+
outcome: 'release',
|
|
211
|
+
input,
|
|
212
|
+
serial: this.#nextSerial(),
|
|
213
|
+
}),
|
|
214
|
+
failRuntimeStepReceipt: (input) =>
|
|
215
|
+
write({ kind: 'settle', outcome: 'fail', input }),
|
|
216
|
+
failRuntimeStepReceipts: ({ receipts }) =>
|
|
217
|
+
writeMany(
|
|
218
|
+
receipts.map((input) => ({
|
|
219
|
+
kind: 'settle' as const,
|
|
220
|
+
outcome: 'fail' as const,
|
|
221
|
+
input,
|
|
222
|
+
})),
|
|
223
|
+
),
|
|
224
|
+
heartbeatRuntimeStepReceipts: (input) =>
|
|
225
|
+
writeMany(
|
|
226
|
+
input.keys.map((key) => ({
|
|
227
|
+
kind: 'heartbeat' as const,
|
|
228
|
+
input: {
|
|
229
|
+
key,
|
|
230
|
+
runId: input.runId,
|
|
231
|
+
runAttempt: input.runAttempt,
|
|
232
|
+
leaseId: input.leaseId,
|
|
233
|
+
},
|
|
234
|
+
})),
|
|
235
|
+
),
|
|
236
|
+
skipRuntimeStepReceipt: (input) =>
|
|
237
|
+
write({
|
|
238
|
+
kind: 'settle',
|
|
239
|
+
outcome: 'skip',
|
|
240
|
+
input,
|
|
241
|
+
serial: this.#nextSerial(),
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
flush(): Promise<void> {
|
|
247
|
+
return this.#writer.flush();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
close(reason?: unknown): Promise<void> {
|
|
251
|
+
return this.#writer.close(reason);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#nextSerial(): number {
|
|
255
|
+
return ++this.#serial;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function receiptCommandBatchKey(command: ReceiptCommand): string {
|
|
260
|
+
switch (command.kind) {
|
|
261
|
+
case 'get':
|
|
262
|
+
return JSON.stringify([command.kind, command.input.runId]);
|
|
263
|
+
case 'claim':
|
|
264
|
+
return JSON.stringify([
|
|
265
|
+
command.kind,
|
|
266
|
+
command.input.runId,
|
|
267
|
+
command.input.runAttempt ?? null,
|
|
268
|
+
command.input.leaseAware === true,
|
|
269
|
+
command.input.reclaimRunning === true,
|
|
270
|
+
command.input.forceRefresh === true,
|
|
271
|
+
command.input.forceFailedRefresh === true,
|
|
272
|
+
command.input.leaseId !== undefined,
|
|
273
|
+
]);
|
|
274
|
+
case 'settle':
|
|
275
|
+
return command.outcome === 'complete' || command.outcome === 'fail'
|
|
276
|
+
? JSON.stringify([command.kind, command.outcome, command.input.runId])
|
|
277
|
+
: `${command.kind}:${command.outcome}:${command.serial}`;
|
|
278
|
+
case 'heartbeat':
|
|
279
|
+
return JSON.stringify([
|
|
280
|
+
command.kind,
|
|
281
|
+
command.input.runId,
|
|
282
|
+
command.input.runAttempt ?? null,
|
|
283
|
+
command.input.leaseId,
|
|
284
|
+
]);
|
|
285
|
+
case 'acquire_execution_lock':
|
|
286
|
+
case 'release_execution_lock':
|
|
287
|
+
return `${command.kind}:${command.serial}`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function classifyReceiptRetry(error: unknown): { retryAfterMs: number } | null {
|
|
292
|
+
if (isAppRuntimeApiCapacityError(error)) {
|
|
293
|
+
return { retryAfterMs: error.retryAfterMs };
|
|
294
|
+
}
|
|
295
|
+
if (error instanceof AppRuntimeApiTransportError) {
|
|
296
|
+
return { retryAfterMs: 250 };
|
|
297
|
+
}
|
|
298
|
+
if (error instanceof AppRuntimeApiResponseError && error.retryable) {
|
|
299
|
+
return { retryAfterMs: 250 };
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function sendReceiptCommands(input: {
|
|
305
|
+
context: WorkerRuntimeApiContext;
|
|
306
|
+
playName: string;
|
|
307
|
+
commands: readonly ReceiptCommand[];
|
|
308
|
+
}): Promise<readonly ReceiptOutput[]> {
|
|
309
|
+
const first = input.commands[0];
|
|
310
|
+
if (!first) return [];
|
|
311
|
+
switch (first.kind) {
|
|
312
|
+
case 'get': {
|
|
313
|
+
const commands = input.commands as readonly Extract<
|
|
314
|
+
ReceiptCommand,
|
|
315
|
+
{ kind: 'get' }
|
|
316
|
+
>[];
|
|
317
|
+
return await getRuntimeStepReceiptsViaAppRuntime(input.context, {
|
|
318
|
+
playName: input.playName,
|
|
319
|
+
runId: first.input.runId,
|
|
320
|
+
keys: commands.map((command) => command.input.key),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
case 'claim': {
|
|
324
|
+
const commands = input.commands as readonly Extract<
|
|
325
|
+
ReceiptCommand,
|
|
326
|
+
{ kind: 'claim' }
|
|
327
|
+
>[];
|
|
328
|
+
return await claimRuntimeStepReceiptsViaAppRuntime(input.context, {
|
|
329
|
+
playName: input.playName,
|
|
330
|
+
runId: first.input.runId,
|
|
331
|
+
runAttempt: first.input.runAttempt,
|
|
332
|
+
keys: commands.map((command) => command.input.key),
|
|
333
|
+
leaseIds:
|
|
334
|
+
first.input.leaseId === undefined
|
|
335
|
+
? undefined
|
|
336
|
+
: commands.map((command) => command.input.leaseId!),
|
|
337
|
+
leaseAware: first.input.leaseAware,
|
|
338
|
+
reclaimRunning: first.input.reclaimRunning,
|
|
339
|
+
forceRefresh: first.input.forceRefresh,
|
|
340
|
+
forceFailedRefresh: first.input.forceFailedRefresh,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
case 'settle':
|
|
344
|
+
switch (first.outcome) {
|
|
345
|
+
case 'complete': {
|
|
346
|
+
const commands = input.commands as readonly Extract<
|
|
347
|
+
ReceiptCommand,
|
|
348
|
+
{ kind: 'settle'; outcome: 'complete' }
|
|
349
|
+
>[];
|
|
350
|
+
return await completeRuntimeStepReceiptsViaAppRuntime(input.context, {
|
|
351
|
+
playName: input.playName,
|
|
352
|
+
runId: first.input.runId,
|
|
353
|
+
receipts: commands.map((command) => command.input),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
case 'fail': {
|
|
357
|
+
const commands = input.commands as readonly Extract<
|
|
358
|
+
ReceiptCommand,
|
|
359
|
+
{ kind: 'settle'; outcome: 'fail' }
|
|
360
|
+
>[];
|
|
361
|
+
return await failRuntimeStepReceiptsViaAppRuntime(input.context, {
|
|
362
|
+
playName: input.playName,
|
|
363
|
+
runId: first.input.runId,
|
|
364
|
+
receipts: commands.map((command) => command.input),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
case 'release':
|
|
368
|
+
return [
|
|
369
|
+
await releaseRuntimeStepReceiptViaAppRuntime(input.context, {
|
|
370
|
+
playName: input.playName,
|
|
371
|
+
...first.input,
|
|
372
|
+
}),
|
|
373
|
+
];
|
|
374
|
+
case 'skip':
|
|
375
|
+
return [
|
|
376
|
+
await skipRuntimeStepReceiptViaAppRuntime(input.context, {
|
|
377
|
+
playName: input.playName,
|
|
378
|
+
...first.input,
|
|
379
|
+
}),
|
|
380
|
+
];
|
|
381
|
+
}
|
|
382
|
+
case 'heartbeat': {
|
|
383
|
+
const commands = input.commands as readonly Extract<
|
|
384
|
+
ReceiptCommand,
|
|
385
|
+
{ kind: 'heartbeat' }
|
|
386
|
+
>[];
|
|
387
|
+
return await heartbeatRuntimeStepReceiptsViaAppRuntime(input.context, {
|
|
388
|
+
playName: input.playName,
|
|
389
|
+
runId: first.input.runId,
|
|
390
|
+
runAttempt: first.input.runAttempt,
|
|
391
|
+
leaseId: first.input.leaseId,
|
|
392
|
+
keys: commands.map((command) => command.input.key),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
case 'acquire_execution_lock':
|
|
396
|
+
return [
|
|
397
|
+
await acquireRuntimeReceiptExecutionLockViaAppRuntime(input.context, {
|
|
398
|
+
playName: input.playName,
|
|
399
|
+
...first.input,
|
|
400
|
+
}),
|
|
401
|
+
];
|
|
402
|
+
case 'release_execution_lock':
|
|
403
|
+
return [
|
|
404
|
+
await releaseRuntimeReceiptExecutionLockViaAppRuntime(input.context, {
|
|
405
|
+
playName: input.playName,
|
|
406
|
+
...first.input,
|
|
407
|
+
}),
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
}
|