deepline 0.3.127 → 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 +254 -38
- package/dist/cli/index.mjs +254 -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
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway-side commit coalescing for runtime step receipt completions.
|
|
3
|
+
*
|
|
4
|
+
* A runner settles receipts continuously, so a Machine sees many small
|
|
5
|
+
* `complete_runtime_step_receipts` requests arrive within a few milliseconds of
|
|
6
|
+
* each other. Each one costs a heavy-admission slot, a pooled client, and its
|
|
7
|
+
* own storage statement, which is why the commit lane saturated long before
|
|
8
|
+
* Postgres did (measured: p50 1 ms / p99 66 ms per statement while the lane was
|
|
9
|
+
* the fleet-wide chokepoint).
|
|
10
|
+
*
|
|
11
|
+
* This Module holds concurrent requests for a short window and commits them as
|
|
12
|
+
* ONE storage statement under ONE slot, then hands each request exactly its own
|
|
13
|
+
* receipts' results, in its original order. It owns nothing else: the caller
|
|
14
|
+
* supplies the commit, which owns admission, the client, and the statement.
|
|
15
|
+
*
|
|
16
|
+
* Coalescing is only sound inside one commit key. The completion statement is
|
|
17
|
+
* org-scoped (`target.org_id = $6`) and schema-scoped by table name, and its
|
|
18
|
+
* response projection depends on the request's legacy wire format, so requests
|
|
19
|
+
* from different orgs, schemas, or wire formats are never merged. Run identity
|
|
20
|
+
* IS per input row in the statement, so requests from different runs of the
|
|
21
|
+
* same org share a statement safely.
|
|
22
|
+
*
|
|
23
|
+
* Windows are additionally split by payload size class. Merging is only worth
|
|
24
|
+
* doing between statements of comparable cost: folding a 9 KB batch into a
|
|
25
|
+
* window that is building a multi-megabyte statement makes the cheap batch wait
|
|
26
|
+
* for the expensive one to be built AND committed, which is the head-of-line
|
|
27
|
+
* blocking the size classes exist to remove. So a window's class is fixed when
|
|
28
|
+
* it opens, a light window a request would push over the threshold is committed
|
|
29
|
+
* before that request joins anything, and light and heavy windows of the same
|
|
30
|
+
* commit key are built and committed independently.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { BoundedBatchFunction } from '../bounded-batch-function';
|
|
34
|
+
|
|
35
|
+
/** The gateway admission adapter owns how these two classes acquire capacity. */
|
|
36
|
+
export type GatewayCommitSizeClass = 'light' | 'heavy';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Window for gathering concurrent completion requests. A completion is already
|
|
40
|
+
* asynchronous relative to the row that produced it, so a short hold costs the
|
|
41
|
+
* runner nothing while turning a burst into a single statement. Keep it well
|
|
42
|
+
* below the runner's own completion window so the gateway never becomes the
|
|
43
|
+
* dominant term.
|
|
44
|
+
*/
|
|
45
|
+
export const RUNTIME_RECEIPT_GATEWAY_COMPLETION_COALESCE_MS = 20;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Receipts one coalesced statement may carry.
|
|
49
|
+
*
|
|
50
|
+
* Mirrors the runner's `RUNTIME_STEP_RECEIPT_REQUEST_CHUNK_SIZE` (play-runtime
|
|
51
|
+
* `context.ts`), which already bounds a single completion request: one
|
|
52
|
+
* statement therefore never carries more than one runner request's worth of
|
|
53
|
+
* receipts, whatever the window collected. The coalescer test pins the two
|
|
54
|
+
* values together, since only the runner's constant is importable here without
|
|
55
|
+
* pulling the whole runtime into the gateway process.
|
|
56
|
+
*/
|
|
57
|
+
export const RUNTIME_RECEIPT_GATEWAY_COMPLETION_MAX_RECEIPTS = 1_000;
|
|
58
|
+
|
|
59
|
+
/** Identity that must match for two requests to share one statement. */
|
|
60
|
+
export type ReceiptCompletionCommitKey = {
|
|
61
|
+
orgId: string;
|
|
62
|
+
schema: string | null;
|
|
63
|
+
wireFormat: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function receiptCompletionCommitKeyId(
|
|
67
|
+
key: ReceiptCompletionCommitKey,
|
|
68
|
+
): string {
|
|
69
|
+
// Org ids and schema names never contain '|', so the id is unambiguous.
|
|
70
|
+
return `${key.orgId}|${key.schema ?? ''}|${key.wireFormat}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Identity of one open window: a commit key plus the size class of the
|
|
75
|
+
* statement it is building. Two windows of the same commit key but different
|
|
76
|
+
* size classes are legal and deliberate; each commits on its own.
|
|
77
|
+
*/
|
|
78
|
+
export function receiptCompletionWindowId(
|
|
79
|
+
key: ReceiptCompletionCommitKey,
|
|
80
|
+
sizeClass: GatewayCommitSizeClass,
|
|
81
|
+
): string {
|
|
82
|
+
return `${receiptCompletionCommitKeyId(key)}|${sizeClass}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** One committed statement, for the per-statement telemetry record. */
|
|
86
|
+
export type ReceiptCompletionStatementRecord = {
|
|
87
|
+
key: ReceiptCompletionCommitKey;
|
|
88
|
+
/** Requests merged into this statement. */
|
|
89
|
+
requestCount: number;
|
|
90
|
+
/** Receipts across those requests. */
|
|
91
|
+
receiptCount: number;
|
|
92
|
+
/** Caller-estimated request bytes across those requests. */
|
|
93
|
+
bytes: number;
|
|
94
|
+
/**
|
|
95
|
+
* On-the-wire body bytes of the merged requests that carried these receipts.
|
|
96
|
+
*
|
|
97
|
+
* `bytes` is the DECODED size the statement is admitted and classified on;
|
|
98
|
+
* this is what the network actually moved. They are equal when the requests
|
|
99
|
+
* arrived uncompressed (the caller supplies no wire size and it defaults to
|
|
100
|
+
* `bytes`), and `wireBytes / bytes` is the compression ratio the transport
|
|
101
|
+
* achieved when they arrived gzipped. Admission never reads it: a compressed
|
|
102
|
+
* request costs the statement exactly as much decoded work as an
|
|
103
|
+
* uncompressed one, so shrinking the wire must not shrink the size class.
|
|
104
|
+
*/
|
|
105
|
+
wireBytes: number;
|
|
106
|
+
/** Admission class this statement was committed under. */
|
|
107
|
+
sizeClass: GatewayCommitSizeClass;
|
|
108
|
+
/** Longest time any merged request waited before the statement started. */
|
|
109
|
+
waitMs: number;
|
|
110
|
+
/**
|
|
111
|
+
* Time the commit spent waiting for its admission slot, as reported by the
|
|
112
|
+
* commit itself. Zero when the caller's commit takes no admission.
|
|
113
|
+
*/
|
|
114
|
+
admissionWaitMs: number;
|
|
115
|
+
/** Wall time of the commit itself, including admission and client acquire. */
|
|
116
|
+
statementMs: number;
|
|
117
|
+
outcome: 'committed' | 'failed';
|
|
118
|
+
errorType: string | null;
|
|
119
|
+
/**
|
|
120
|
+
* Per-run share of the statement, so a readout can attribute gateway bytes
|
|
121
|
+
* and statement time to the runs that produced them. One coalesced
|
|
122
|
+
* statement may carry several runs of one org; the list is bounded and
|
|
123
|
+
* `runCount` says how many runs the statement really carried.
|
|
124
|
+
*/
|
|
125
|
+
runs: ReceiptCompletionRunShare[];
|
|
126
|
+
runCount: number;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type ReceiptCompletionRunShare = {
|
|
130
|
+
runId: string;
|
|
131
|
+
/**
|
|
132
|
+
* Transport attempt ids of the requests this run contributed, so a readout
|
|
133
|
+
* can pair the gateway's statement timing with the runner's flight for
|
|
134
|
+
* the same batch.
|
|
135
|
+
*/
|
|
136
|
+
transportAttemptIds: string[];
|
|
137
|
+
requestCount: number;
|
|
138
|
+
receiptCount: number;
|
|
139
|
+
bytes: number;
|
|
140
|
+
/** This run's share of the statement's on-the-wire body bytes. */
|
|
141
|
+
wireBytes: number;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** Runs listed per committed statement; the rest are counted, not listed. */
|
|
145
|
+
export const RUNTIME_RECEIPT_GATEWAY_COMMITTED_RUNS_MAX = 16;
|
|
146
|
+
|
|
147
|
+
type PendingRequest<TReceipt> = {
|
|
148
|
+
receipts: readonly TReceipt[];
|
|
149
|
+
transportAttemptId: string | null;
|
|
150
|
+
bytes: number;
|
|
151
|
+
wireBytes: number;
|
|
152
|
+
enqueuedAt: number;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
type ReceiptCompletionWindowKey = {
|
|
156
|
+
key: ReceiptCompletionCommitKey;
|
|
157
|
+
sizeClass: GatewayCommitSizeClass;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export class ReceiptCompletionResultCountError extends Error {
|
|
161
|
+
constructor(
|
|
162
|
+
readonly expected: number,
|
|
163
|
+
readonly received: number,
|
|
164
|
+
) {
|
|
165
|
+
super(
|
|
166
|
+
`Coalesced receipt completion returned ${received} results for ${expected} receipts.`,
|
|
167
|
+
);
|
|
168
|
+
this.name = 'ReceiptCompletionResultCountError';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class ReceiptCompletionCoalescer<TReceipt, TResult> {
|
|
173
|
+
readonly #commit: (input: {
|
|
174
|
+
key: ReceiptCompletionCommitKey;
|
|
175
|
+
receipts: TReceipt[];
|
|
176
|
+
sizeClass: GatewayCommitSizeClass;
|
|
177
|
+
bytes: number;
|
|
178
|
+
onAdmitted: () => void;
|
|
179
|
+
}) => Promise<TResult[]>;
|
|
180
|
+
readonly #lightMaxBytes: number;
|
|
181
|
+
readonly #onStatement:
|
|
182
|
+
| ((record: ReceiptCompletionStatementRecord) => void)
|
|
183
|
+
| null;
|
|
184
|
+
readonly #requestRunId:
|
|
185
|
+
| ((receipts: readonly TReceipt[]) => string | null)
|
|
186
|
+
| null;
|
|
187
|
+
readonly #now: () => number;
|
|
188
|
+
readonly #batches: BoundedBatchFunction<
|
|
189
|
+
PendingRequest<TReceipt>,
|
|
190
|
+
TResult[],
|
|
191
|
+
ReceiptCompletionWindowKey
|
|
192
|
+
>;
|
|
193
|
+
|
|
194
|
+
constructor(input: {
|
|
195
|
+
/**
|
|
196
|
+
* Commit these receipts as one statement. The caller owns heavy-request
|
|
197
|
+
* admission, the pooled client, and the storage call. Results MUST be
|
|
198
|
+
* index-aligned with the supplied receipts.
|
|
199
|
+
*/
|
|
200
|
+
commit: (input: {
|
|
201
|
+
key: ReceiptCompletionCommitKey;
|
|
202
|
+
receipts: TReceipt[];
|
|
203
|
+
/** Admission class the caller must commit this statement under. */
|
|
204
|
+
sizeClass: GatewayCommitSizeClass;
|
|
205
|
+
/** Caller-estimated bytes of the merged statement. */
|
|
206
|
+
bytes: number;
|
|
207
|
+
/**
|
|
208
|
+
* Call once the commit holds its admission slot. Reports
|
|
209
|
+
* `admissionWaitMs` for this statement; a commit that takes no admission
|
|
210
|
+
* simply never calls it.
|
|
211
|
+
*/
|
|
212
|
+
onAdmitted: () => void;
|
|
213
|
+
}) => Promise<TResult[]>;
|
|
214
|
+
/** Receipts one statement may carry; matches the runner's request chunk. */
|
|
215
|
+
maxReceipts: number;
|
|
216
|
+
/** Estimated request bytes one statement may carry. */
|
|
217
|
+
maxBytes: number;
|
|
218
|
+
/**
|
|
219
|
+
* Bytes at or above which a statement is heavy. A window stays strictly
|
|
220
|
+
* below this while it is light, so a light statement's cost stays bounded
|
|
221
|
+
* and its own admission lane keeps draining.
|
|
222
|
+
*/
|
|
223
|
+
lightMaxBytes: number;
|
|
224
|
+
windowMs?: number;
|
|
225
|
+
onStatement?: (record: ReceiptCompletionStatementRecord) => void;
|
|
226
|
+
/**
|
|
227
|
+
* Run identity of one request's receipts (every request carries receipts
|
|
228
|
+
* of exactly one run). Without it statements record no run shares.
|
|
229
|
+
*/
|
|
230
|
+
requestRunId?: (receipts: readonly TReceipt[]) => string | null;
|
|
231
|
+
now?: () => number;
|
|
232
|
+
/**
|
|
233
|
+
* Bounded retained requests across queued and active commit statements.
|
|
234
|
+
* Defaults keep four maximum receipt statements resident per Machine.
|
|
235
|
+
*/
|
|
236
|
+
maxRetainedRequests?: number;
|
|
237
|
+
maxInFlightStatements?: number;
|
|
238
|
+
}) {
|
|
239
|
+
if (!Number.isSafeInteger(input.maxReceipts) || input.maxReceipts < 1) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
'Receipt completion coalescer requires a positive maxReceipts.',
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (!Number.isSafeInteger(input.maxBytes) || input.maxBytes < 1) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
'Receipt completion coalescer requires a positive maxBytes.',
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (!Number.isSafeInteger(input.lightMaxBytes) || input.lightMaxBytes < 1) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
'Receipt completion coalescer requires a positive lightMaxBytes.',
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
this.#commit = input.commit;
|
|
255
|
+
this.#lightMaxBytes = input.lightMaxBytes;
|
|
256
|
+
this.#onStatement = input.onStatement ?? null;
|
|
257
|
+
this.#requestRunId = input.requestRunId ?? null;
|
|
258
|
+
this.#now = input.now ?? Date.now;
|
|
259
|
+
const maxRetainedRequests =
|
|
260
|
+
input.maxRetainedRequests ??
|
|
261
|
+
Math.max(input.maxReceipts, input.maxReceipts * 4);
|
|
262
|
+
const maxInFlightStatements = input.maxInFlightStatements ?? 4;
|
|
263
|
+
this.#batches = new BoundedBatchFunction({
|
|
264
|
+
keyId: (window) =>
|
|
265
|
+
receiptCompletionWindowId(window.key, window.sizeClass),
|
|
266
|
+
// `light` stays strictly below the heavy threshold. A request exactly at
|
|
267
|
+
// that threshold belongs to the heavy lane, matching the prior gateway
|
|
268
|
+
// coalescer contract.
|
|
269
|
+
maxBatchBytesForKey: (window) =>
|
|
270
|
+
window.sizeClass === 'light'
|
|
271
|
+
? Math.max(1, input.lightMaxBytes - 1)
|
|
272
|
+
: input.maxBytes,
|
|
273
|
+
estimateBytes: (request) => request.bytes,
|
|
274
|
+
units: (request) => request.receipts.length,
|
|
275
|
+
maxBatchItems: input.maxReceipts,
|
|
276
|
+
maxBatchBytes: input.maxBytes,
|
|
277
|
+
maxBatchUnits: input.maxReceipts,
|
|
278
|
+
maxRetainedItems: maxRetainedRequests,
|
|
279
|
+
maxRetainedBytes: input.maxBytes * 4,
|
|
280
|
+
maxRetainedUnits: input.maxReceipts * 4,
|
|
281
|
+
maxOpenGroups: Math.min(maxRetainedRequests, 128),
|
|
282
|
+
maxInFlightBatches: maxInFlightStatements,
|
|
283
|
+
flushMs: input.windowMs ?? RUNTIME_RECEIPT_GATEWAY_COMPLETION_COALESCE_MS,
|
|
284
|
+
now: this.#now,
|
|
285
|
+
execute: async ({ key, values }) =>
|
|
286
|
+
await this.#commitRequests(key, values),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Submit one request's receipts. Resolves with exactly this request's
|
|
292
|
+
* results, in the order its receipts were supplied. A statement failure
|
|
293
|
+
* rejects every merged request with that same error, so each caller sees the
|
|
294
|
+
* response it would have seen alone and its own retry stays safe.
|
|
295
|
+
*/
|
|
296
|
+
submit(input: {
|
|
297
|
+
key: ReceiptCompletionCommitKey;
|
|
298
|
+
receipts: readonly TReceipt[];
|
|
299
|
+
/** Caller-estimated request bytes, used only for the byte bound. */
|
|
300
|
+
bytes?: number;
|
|
301
|
+
/**
|
|
302
|
+
* On-the-wire body bytes of the request that carried these receipts.
|
|
303
|
+
* Telemetry only — it never affects the byte bound or the size class.
|
|
304
|
+
* Defaults to `bytes`, which is the truth for an uncompressed request.
|
|
305
|
+
*/
|
|
306
|
+
wireBytes?: number;
|
|
307
|
+
/** Runner transport identity, used only for the bounded telemetry share. */
|
|
308
|
+
transportAttemptId?: string | null;
|
|
309
|
+
}): Promise<TResult[]> {
|
|
310
|
+
if (input.receipts.length === 0) return Promise.resolve([]);
|
|
311
|
+
const bytes = Math.max(0, Math.floor(input.bytes ?? 0));
|
|
312
|
+
const wireBytes = Math.max(0, Math.floor(input.wireBytes ?? bytes));
|
|
313
|
+
// A request that is already heavy on its own can never make a light
|
|
314
|
+
// statement, so it goes straight to a heavy window instead of closing the
|
|
315
|
+
// light one that other shapes are still filling.
|
|
316
|
+
const sizeClass: GatewayCommitSizeClass =
|
|
317
|
+
bytes >= this.#lightMaxBytes ? 'heavy' : 'light';
|
|
318
|
+
return this.#batches.submit({
|
|
319
|
+
key: { key: input.key, sizeClass },
|
|
320
|
+
value: {
|
|
321
|
+
receipts: input.receipts,
|
|
322
|
+
transportAttemptId: input.transportAttemptId ?? null,
|
|
323
|
+
bytes,
|
|
324
|
+
wireBytes,
|
|
325
|
+
enqueuedAt: this.#now(),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Commit every open window now and wait for all in-flight statements. */
|
|
331
|
+
async drain(): Promise<void> {
|
|
332
|
+
await this.#batches.drain();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async #commitRequests(
|
|
336
|
+
window: ReceiptCompletionWindowKey,
|
|
337
|
+
requests: readonly PendingRequest<TReceipt>[],
|
|
338
|
+
): Promise<TResult[][]> {
|
|
339
|
+
const startedAt = this.#now();
|
|
340
|
+
const waitMs = requests.reduce(
|
|
341
|
+
(longest, request) =>
|
|
342
|
+
Math.max(longest, Math.max(0, startedAt - request.enqueuedAt)),
|
|
343
|
+
0,
|
|
344
|
+
);
|
|
345
|
+
const receipts = requests.flatMap((request) => [...request.receipts]);
|
|
346
|
+
let admissionWaitMs = 0;
|
|
347
|
+
const onAdmitted = () => {
|
|
348
|
+
admissionWaitMs = Math.max(0, this.#now() - startedAt);
|
|
349
|
+
};
|
|
350
|
+
const outcome: Pick<
|
|
351
|
+
ReceiptCompletionStatementRecord,
|
|
352
|
+
'waitMs' | 'admissionWaitMs' | 'statementMs' | 'outcome' | 'errorType'
|
|
353
|
+
> = {
|
|
354
|
+
waitMs,
|
|
355
|
+
admissionWaitMs,
|
|
356
|
+
statementMs: 0,
|
|
357
|
+
outcome: 'failed',
|
|
358
|
+
errorType: null,
|
|
359
|
+
};
|
|
360
|
+
try {
|
|
361
|
+
const results = await this.#commit({
|
|
362
|
+
key: window.key,
|
|
363
|
+
receipts,
|
|
364
|
+
sizeClass: window.sizeClass,
|
|
365
|
+
bytes: requests.reduce((total, request) => total + request.bytes, 0),
|
|
366
|
+
onAdmitted,
|
|
367
|
+
});
|
|
368
|
+
if (results.length !== receipts.length) {
|
|
369
|
+
throw new ReceiptCompletionResultCountError(
|
|
370
|
+
receipts.length,
|
|
371
|
+
results.length,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
let offset = 0;
|
|
375
|
+
const requestResults: TResult[][] = [];
|
|
376
|
+
for (const request of requests) {
|
|
377
|
+
const slice = results.slice(offset, offset + request.receipts.length);
|
|
378
|
+
offset += request.receipts.length;
|
|
379
|
+
requestResults.push(slice);
|
|
380
|
+
}
|
|
381
|
+
outcome.admissionWaitMs = admissionWaitMs;
|
|
382
|
+
outcome.statementMs = this.#now() - startedAt;
|
|
383
|
+
outcome.outcome = 'committed';
|
|
384
|
+
this.#record(window, requests, outcome);
|
|
385
|
+
return requestResults;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
outcome.admissionWaitMs = admissionWaitMs;
|
|
388
|
+
outcome.statementMs = this.#now() - startedAt;
|
|
389
|
+
outcome.errorType = error instanceof Error ? error.name : typeof error;
|
|
390
|
+
this.#record(window, requests, outcome);
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
#record(
|
|
396
|
+
window: ReceiptCompletionWindowKey,
|
|
397
|
+
requests: readonly PendingRequest<TReceipt>[],
|
|
398
|
+
outcome: Pick<
|
|
399
|
+
ReceiptCompletionStatementRecord,
|
|
400
|
+
'waitMs' | 'admissionWaitMs' | 'statementMs' | 'outcome' | 'errorType'
|
|
401
|
+
>,
|
|
402
|
+
): void {
|
|
403
|
+
if (!this.#onStatement) return;
|
|
404
|
+
try {
|
|
405
|
+
const shares = new Map<string, ReceiptCompletionRunShare>();
|
|
406
|
+
if (this.#requestRunId) {
|
|
407
|
+
for (const request of requests) {
|
|
408
|
+
const runId = this.#requestRunId(request.receipts);
|
|
409
|
+
if (!runId) continue;
|
|
410
|
+
const share = shares.get(runId) ?? {
|
|
411
|
+
runId,
|
|
412
|
+
transportAttemptIds: [],
|
|
413
|
+
requestCount: 0,
|
|
414
|
+
receiptCount: 0,
|
|
415
|
+
bytes: 0,
|
|
416
|
+
wireBytes: 0,
|
|
417
|
+
};
|
|
418
|
+
share.requestCount += 1;
|
|
419
|
+
if (request.transportAttemptId) {
|
|
420
|
+
share.transportAttemptIds.push(request.transportAttemptId);
|
|
421
|
+
}
|
|
422
|
+
share.receiptCount += request.receipts.length;
|
|
423
|
+
share.bytes += request.bytes;
|
|
424
|
+
share.wireBytes += request.wireBytes;
|
|
425
|
+
shares.set(runId, share);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
this.#onStatement({
|
|
429
|
+
key: window.key,
|
|
430
|
+
requestCount: requests.length,
|
|
431
|
+
receiptCount: requests.reduce(
|
|
432
|
+
(total, request) => total + request.receipts.length,
|
|
433
|
+
0,
|
|
434
|
+
),
|
|
435
|
+
bytes: requests.reduce((total, request) => total + request.bytes, 0),
|
|
436
|
+
wireBytes: requests.reduce(
|
|
437
|
+
(total, request) => total + request.wireBytes,
|
|
438
|
+
0,
|
|
439
|
+
),
|
|
440
|
+
sizeClass: window.sizeClass,
|
|
441
|
+
runs: [...shares.values()].slice(
|
|
442
|
+
0,
|
|
443
|
+
RUNTIME_RECEIPT_GATEWAY_COMMITTED_RUNS_MAX,
|
|
444
|
+
),
|
|
445
|
+
runCount: shares.size,
|
|
446
|
+
...outcome,
|
|
447
|
+
});
|
|
448
|
+
} catch {
|
|
449
|
+
// Telemetry must never affect durable delivery.
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|