deepline 0.1.294 → 0.1.296
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/context.ts +237 -167
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +15 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +83 -54
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/pacing.ts +11 -1
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +3 -94
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +191 -22
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +662 -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
|
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
|
|
|
155
155
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
156
156
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
157
157
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
158
|
-
version: '0.1.
|
|
158
|
+
version: '0.1.296',
|
|
159
159
|
contracts: {
|
|
160
160
|
api: {
|
|
161
161
|
name: 'sdk-http-api',
|
|
@@ -35,6 +35,7 @@ import type { PlayQueueHint } from './governor/rate-state-backend';
|
|
|
35
35
|
import type { MapRowOutcome } from './durability-store';
|
|
36
36
|
import { stringifyPostgresJson } from './postgres-json';
|
|
37
37
|
import { RUNTIME_ROW_OUTPUT_NOT_JSON_SERIALIZABLE } from './runtime-sheet-row-transition';
|
|
38
|
+
import { RuntimeSheetRowWriter } from './runtime-sheet-row-writer';
|
|
38
39
|
import type { WorkReceiptFailureKind } from './work-receipts';
|
|
39
40
|
import {
|
|
40
41
|
completedMapRowOutcome,
|
|
@@ -300,9 +301,8 @@ const MAP_FRAME_FLUSH_ROW_INTERVAL = 100;
|
|
|
300
301
|
/** Executed-row sheet persistence chunking: rows AND bytes, whichever first. */
|
|
301
302
|
const MAP_PERSIST_CHUNK_ROWS = 2_000;
|
|
302
303
|
const MAP_PERSIST_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 100;
|
|
304
|
+
const MAP_ROW_WRITER_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
305
|
+
const MAP_ROW_WRITER_MAX_FLUSH_MS = 100;
|
|
306
306
|
const MAP_FRAME_FLUSH_INTERVAL_MS = 250;
|
|
307
307
|
// Tool scheduling lanes wait at most this long for same-lane row continuations.
|
|
308
308
|
// The window is fixed from the first item and never resets on later arrivals.
|
|
@@ -778,123 +778,117 @@ type FieldMapRunResult = {
|
|
|
778
778
|
failedRows: PersistableMapRow[];
|
|
779
779
|
};
|
|
780
780
|
|
|
781
|
-
type
|
|
782
|
-
persistRows: (rows: PersistableMapRow[]) =>
|
|
781
|
+
type RuntimeMapRowPersistence = {
|
|
782
|
+
persistRows: (rows: PersistableMapRow[]) => {
|
|
783
|
+
admitted: Promise<void>;
|
|
784
|
+
committed: Promise<void>;
|
|
785
|
+
};
|
|
783
786
|
isPersisted: (row: PersistableMapRow) => boolean;
|
|
787
|
+
checkpoint: (updates: PlayRowUpdate[]) => Promise<void>;
|
|
784
788
|
flush: () => Promise<void>;
|
|
785
789
|
};
|
|
786
790
|
|
|
787
|
-
function
|
|
791
|
+
function createRuntimeMapRowPersistence(
|
|
788
792
|
persistRows: (rows: PersistableMapRow[]) => Promise<void>,
|
|
789
|
-
|
|
793
|
+
options?: {
|
|
794
|
+
persistCheckpoint?: (updates: PlayRowUpdate[]) => Promise<void>;
|
|
795
|
+
onFailure?: (error: unknown) => void;
|
|
796
|
+
},
|
|
797
|
+
): RuntimeMapRowPersistence {
|
|
790
798
|
const persisted = new Set<string>();
|
|
791
799
|
const queued = new Set<string>();
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
800
|
+
const writer = new RuntimeSheetRowWriter<PersistableMapRow, PlayRowUpdate>({
|
|
801
|
+
terminalKey: (row) => {
|
|
802
|
+
const identity = persistableMapRowIdentity(row);
|
|
803
|
+
if (!identity) {
|
|
804
|
+
throw new Error('Runtime Sheet terminal row is missing an identity.');
|
|
805
|
+
}
|
|
806
|
+
return identity;
|
|
807
|
+
},
|
|
808
|
+
checkpointKey: (update) => `key:${update.key}`,
|
|
809
|
+
mergeCheckpointUpdates: (current, incoming) => ({
|
|
810
|
+
...current,
|
|
811
|
+
...incoming,
|
|
812
|
+
dataPatch: {
|
|
813
|
+
...(current.dataPatch ?? {}),
|
|
814
|
+
...(incoming.dataPatch ?? {}),
|
|
815
|
+
},
|
|
816
|
+
cellMetaPatch: {
|
|
817
|
+
...(current.cellMetaPatch ?? {}),
|
|
818
|
+
...(incoming.cellMetaPatch ?? {}),
|
|
819
|
+
},
|
|
820
|
+
}),
|
|
821
|
+
estimateTerminalBytes: persistableMapRowBytes,
|
|
822
|
+
estimateCheckpointBytes: (update) => JSON.stringify(update).length,
|
|
823
|
+
maxBatchRows: MAP_PERSIST_CHUNK_ROWS,
|
|
824
|
+
maxBatchBytes: MAP_PERSIST_CHUNK_BYTES,
|
|
825
|
+
maxBufferedBytes: MAP_ROW_WRITER_BUFFER_BYTES,
|
|
826
|
+
maxFlushMs: MAP_ROW_WRITER_MAX_FLUSH_MS,
|
|
827
|
+
onFailure: options?.onFailure,
|
|
828
|
+
writeBatch: async (batch) => {
|
|
829
|
+
if (batch.kind === 'terminal') {
|
|
830
|
+
await persistRows([...batch.rows]);
|
|
831
|
+
return { committed: batch.rows.length };
|
|
832
|
+
}
|
|
833
|
+
if (batch.updates.length > 0) {
|
|
834
|
+
await options?.persistCheckpoint?.([...batch.updates]);
|
|
835
|
+
}
|
|
836
|
+
return { committed: batch.updates.length };
|
|
837
|
+
},
|
|
838
|
+
});
|
|
807
839
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
if (rows.length === 0) {
|
|
816
|
-
resolvers.forEach(({ resolve }) => resolve());
|
|
817
|
-
return flushChain;
|
|
818
|
-
}
|
|
819
|
-
flushChain = flushChain
|
|
820
|
-
.then(async () => {
|
|
821
|
-
await persistRows(rows);
|
|
822
|
-
for (const row of rows) {
|
|
823
|
-
const identity = persistableMapRowIdentity(row);
|
|
824
|
-
if (identity) {
|
|
825
|
-
persisted.add(identity);
|
|
826
|
-
queued.delete(identity);
|
|
827
|
-
}
|
|
840
|
+
return {
|
|
841
|
+
persistRows: (rows) => {
|
|
842
|
+
const admissions: Promise<void>[] = [];
|
|
843
|
+
const commits: Promise<void>[] = [];
|
|
844
|
+
const clearQueuedState = (identity: string | null) => {
|
|
845
|
+
if (identity) {
|
|
846
|
+
queued.delete(identity);
|
|
828
847
|
}
|
|
829
|
-
}
|
|
830
|
-
.then(
|
|
831
|
-
() => {
|
|
832
|
-
resolvers.forEach(({ resolve }) => resolve());
|
|
833
|
-
},
|
|
834
|
-
(error) => {
|
|
835
|
-
for (const row of rows) {
|
|
836
|
-
const identity = persistableMapRowIdentity(row);
|
|
837
|
-
if (identity) queued.delete(identity);
|
|
838
|
-
}
|
|
839
|
-
resolvers.forEach(({ reject }) => reject(error));
|
|
840
|
-
throw error;
|
|
841
|
-
},
|
|
842
|
-
);
|
|
843
|
-
return flushChain;
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
const scheduleFlush = (): void => {
|
|
847
|
-
if (scheduledTimer) return;
|
|
848
|
-
scheduledTimer = setTimeout(() => {
|
|
849
|
-
scheduledTimer = null;
|
|
850
|
-
void flushPendingRows().catch(() => {
|
|
851
|
-
// Each queued caller is rejected by flushPendingRows; this scheduled
|
|
852
|
-
// fire-and-forget path must not create a second unhandled rejection.
|
|
853
|
-
});
|
|
854
|
-
}, MAP_INCREMENTAL_PERSIST_INTERVAL_MS);
|
|
855
|
-
};
|
|
848
|
+
};
|
|
856
849
|
|
|
857
|
-
return {
|
|
858
|
-
persistRows: async (rows) => {
|
|
859
|
-
const freshRows: PersistableMapRow[] = [];
|
|
860
850
|
for (const row of rows) {
|
|
861
851
|
const identity = persistableMapRowIdentity(row);
|
|
862
852
|
if (identity && (persisted.has(identity) || queued.has(identity))) {
|
|
863
853
|
continue;
|
|
864
854
|
}
|
|
865
855
|
if (identity) queued.add(identity);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
(total, row) => total + persistableMapRowBytes(row),
|
|
873
|
-
0,
|
|
856
|
+
const settlement = writer.settle(row);
|
|
857
|
+
admissions.push(
|
|
858
|
+
settlement.admitted.catch((error) => {
|
|
859
|
+
clearQueuedState(identity);
|
|
860
|
+
throw error;
|
|
861
|
+
}),
|
|
874
862
|
);
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
863
|
+
commits.push(
|
|
864
|
+
settlement.committed.then(
|
|
865
|
+
() => {
|
|
866
|
+
if (identity) {
|
|
867
|
+
persisted.add(identity);
|
|
868
|
+
clearQueuedState(identity);
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
(error) => {
|
|
872
|
+
clearQueuedState(identity);
|
|
873
|
+
throw error;
|
|
874
|
+
},
|
|
875
|
+
),
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
admitted: Promise.all(admissions).then(() => {}),
|
|
880
|
+
committed: Promise.all(commits).then(() => {}),
|
|
881
|
+
};
|
|
887
882
|
},
|
|
888
883
|
isPersisted: (row) => {
|
|
889
884
|
const identity = persistableMapRowIdentity(row);
|
|
890
885
|
return identity ? persisted.has(identity) : false;
|
|
891
886
|
},
|
|
887
|
+
checkpoint: async (updates) => {
|
|
888
|
+
await writer.checkpoint(updates);
|
|
889
|
+
},
|
|
892
890
|
flush: async () => {
|
|
893
|
-
|
|
894
|
-
clearScheduledTimer();
|
|
895
|
-
await flushPendingRows();
|
|
896
|
-
}
|
|
897
|
-
await flushChain;
|
|
891
|
+
await writer.finish();
|
|
898
892
|
},
|
|
899
893
|
};
|
|
900
894
|
}
|
|
@@ -1310,6 +1304,13 @@ export class PlayContextImpl {
|
|
|
1310
1304
|
* completed/cached cell meta — cross-run reuse decisions read that meta.
|
|
1311
1305
|
*/
|
|
1312
1306
|
private activeMapCellMeta: Map<string, Record<string, unknown>> | null = null;
|
|
1307
|
+
/**
|
|
1308
|
+
* Latest partial row patch per key for the active map. Terminal rows are
|
|
1309
|
+
* removed after their Runtime Sheet settlement commits; any remainder is the
|
|
1310
|
+
* exact partial-cell checkpoint that must cross the durability barrier before
|
|
1311
|
+
* an integration-event suspension is published.
|
|
1312
|
+
*/
|
|
1313
|
+
private activeMapCheckpointUpdates: Map<string, PlayRowUpdate> | null = null;
|
|
1313
1314
|
private lastProgressHeartbeatAt = 0;
|
|
1314
1315
|
private pendingRowEventBoundaries: Array<{
|
|
1315
1316
|
boundaryId: string;
|
|
@@ -1642,6 +1643,33 @@ export class PlayContextImpl {
|
|
|
1642
1643
|
: { ...update.cellMetaPatch },
|
|
1643
1644
|
);
|
|
1644
1645
|
}
|
|
1646
|
+
if (key && this.activeMapCheckpointUpdates) {
|
|
1647
|
+
const checkpointKey = `${tableNamespace ?? ''}\u0000${key}`;
|
|
1648
|
+
const existing = this.activeMapCheckpointUpdates.get(checkpointKey);
|
|
1649
|
+
const next: PlayRowUpdate = {
|
|
1650
|
+
...(existing ?? {
|
|
1651
|
+
key,
|
|
1652
|
+
rowId: update.rowId,
|
|
1653
|
+
tableNamespace,
|
|
1654
|
+
}),
|
|
1655
|
+
key,
|
|
1656
|
+
rowId: update.rowId,
|
|
1657
|
+
tableNamespace,
|
|
1658
|
+
...(update.status !== undefined ? { status: update.status } : {}),
|
|
1659
|
+
...(update.stage !== undefined ? { stage: update.stage } : {}),
|
|
1660
|
+
...(update.provider !== undefined ? { provider: update.provider } : {}),
|
|
1661
|
+
...(update.error !== undefined ? { error: update.error } : {}),
|
|
1662
|
+
dataPatch: {
|
|
1663
|
+
...(existing?.dataPatch ?? {}),
|
|
1664
|
+
...(update.dataPatch ?? {}),
|
|
1665
|
+
},
|
|
1666
|
+
cellMetaPatch: {
|
|
1667
|
+
...(existing?.cellMetaPatch ?? {}),
|
|
1668
|
+
...(update.cellMetaPatch ?? {}),
|
|
1669
|
+
},
|
|
1670
|
+
};
|
|
1671
|
+
this.activeMapCheckpointUpdates.set(checkpointKey, next);
|
|
1672
|
+
}
|
|
1645
1673
|
if (!key || !this.#options.onRowUpdate) {
|
|
1646
1674
|
return;
|
|
1647
1675
|
}
|
|
@@ -1652,6 +1680,13 @@ export class PlayContextImpl {
|
|
|
1652
1680
|
});
|
|
1653
1681
|
}
|
|
1654
1682
|
|
|
1683
|
+
private clearActiveMapCheckpointUpdate(
|
|
1684
|
+
key: string,
|
|
1685
|
+
tableNamespace: string,
|
|
1686
|
+
): void {
|
|
1687
|
+
this.activeMapCheckpointUpdates?.delete(`${tableNamespace}\u0000${key}`);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1655
1690
|
private emitExecutionEvent(event: PlayExecutionEvent): void {
|
|
1656
1691
|
if (!this.#options.onExecutionEvent) {
|
|
1657
1692
|
return;
|
|
@@ -3969,32 +4004,23 @@ export class PlayContextImpl {
|
|
|
3969
4004
|
const flushChunk = async () => {
|
|
3970
4005
|
if (chunk.length === 0) return;
|
|
3971
4006
|
try {
|
|
3972
|
-
const sheetFlushLease =
|
|
3973
|
-
await this.resourceGovernor.acquireSheetFlush({
|
|
3974
|
-
estimatedBytes: chunkBytes,
|
|
3975
|
-
rowCount: chunk.length,
|
|
3976
|
-
});
|
|
3977
4007
|
const flushStartedAt = Date.now();
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
});
|
|
3995
|
-
} finally {
|
|
3996
|
-
sheetFlushLease.release();
|
|
3997
|
-
}
|
|
4008
|
+
await this.#options.onMapRowsCompleted!({
|
|
4009
|
+
playName: this.currentPlayName,
|
|
4010
|
+
playId: this.currentExecutionScope.logical.playId,
|
|
4011
|
+
runId: this.currentRunId,
|
|
4012
|
+
executorToken: this.#options.executorToken,
|
|
4013
|
+
tableNamespace: resolvedTableNamespace,
|
|
4014
|
+
rows: chunk,
|
|
4015
|
+
outputFields: datasetColumnNames.filter((field) =>
|
|
4016
|
+
shouldPersistMapCellField(field),
|
|
4017
|
+
),
|
|
4018
|
+
staticPipeline: this.currentStaticPipeline ?? null,
|
|
4019
|
+
});
|
|
4020
|
+
this.resourceGovernor.observe({
|
|
4021
|
+
sheetFlushBytes: chunkBytes,
|
|
4022
|
+
sheetFlushLatencyMs: Date.now() - flushStartedAt,
|
|
4023
|
+
});
|
|
3998
4024
|
} catch (error) {
|
|
3999
4025
|
tripRuntimePersistenceLatch(this.persistenceLatch, error);
|
|
4000
4026
|
throw error;
|
|
@@ -4142,10 +4168,28 @@ export class PlayContextImpl {
|
|
|
4142
4168
|
const rowsToExecute = rowsToExecuteEntries.map((entry) => entry.row);
|
|
4143
4169
|
const rowsToExecuteMemoryEstimate =
|
|
4144
4170
|
estimateRuntimeMapRowsMemory(rowsToExecute);
|
|
4145
|
-
const incrementalPersistence =
|
|
4146
|
-
|
|
4171
|
+
const incrementalPersistence = createRuntimeMapRowPersistence(
|
|
4172
|
+
persistMapRows,
|
|
4173
|
+
{
|
|
4174
|
+
persistCheckpoint: this.#options.onMapRowsCheckpoint
|
|
4175
|
+
? (updates) =>
|
|
4176
|
+
this.#options.onMapRowsCheckpoint!({
|
|
4177
|
+
playName: this.currentPlayName,
|
|
4178
|
+
playId: this.currentExecutionScope.logical.playId,
|
|
4179
|
+
runId: this.currentRunId,
|
|
4180
|
+
executorToken: this.#options.executorToken,
|
|
4181
|
+
tableNamespace: resolvedTableNamespace,
|
|
4182
|
+
updates,
|
|
4183
|
+
staticPipeline: this.currentStaticPipeline ?? null,
|
|
4184
|
+
})
|
|
4185
|
+
: undefined,
|
|
4186
|
+
onFailure: (error) =>
|
|
4187
|
+
tripRuntimePersistenceLatch(this.persistenceLatch, error),
|
|
4188
|
+
},
|
|
4189
|
+
);
|
|
4147
4190
|
|
|
4148
4191
|
this.activeMapCellMeta = new Map();
|
|
4192
|
+
this.activeMapCheckpointUpdates = new Map();
|
|
4149
4193
|
this.setMapFrame({
|
|
4150
4194
|
mapInvocationId: mapScope.mapInvocationId,
|
|
4151
4195
|
mapNodeId: mapScope.mapNodeId ?? null,
|
|
@@ -4207,9 +4251,11 @@ export class PlayContextImpl {
|
|
|
4207
4251
|
);
|
|
4208
4252
|
await persistMapRows(unpersistedRows);
|
|
4209
4253
|
this.activeMapCellMeta = null;
|
|
4254
|
+
this.activeMapCheckpointUpdates = null;
|
|
4210
4255
|
throw error.cause;
|
|
4211
4256
|
}
|
|
4212
4257
|
this.activeMapCellMeta = null;
|
|
4258
|
+
this.activeMapCheckpointUpdates = null;
|
|
4213
4259
|
throw error;
|
|
4214
4260
|
}
|
|
4215
4261
|
|
|
@@ -4238,6 +4284,7 @@ export class PlayContextImpl {
|
|
|
4238
4284
|
);
|
|
4239
4285
|
await persistMapRows(persistRows);
|
|
4240
4286
|
this.activeMapCellMeta = null;
|
|
4287
|
+
this.activeMapCheckpointUpdates = null;
|
|
4241
4288
|
|
|
4242
4289
|
for (const row of mapResult.completedRows) {
|
|
4243
4290
|
const materializedRow = this.toMaterializedOutputRow(row.data);
|
|
@@ -4669,6 +4716,7 @@ export class PlayContextImpl {
|
|
|
4669
4716
|
);
|
|
4670
4717
|
|
|
4671
4718
|
this.activeMapCellMeta = new Map();
|
|
4719
|
+
this.activeMapCheckpointUpdates = new Map();
|
|
4672
4720
|
const staleCompletionKeys = new Set<string>();
|
|
4673
4721
|
const persistMapRows = async (rows: PersistableMapRow[]) => {
|
|
4674
4722
|
if (!this.#options.onMapRowsCompleted || rows.length === 0) {
|
|
@@ -4685,38 +4733,28 @@ export class PlayContextImpl {
|
|
|
4685
4733
|
const flushChunk = async () => {
|
|
4686
4734
|
if (chunk.length === 0) return;
|
|
4687
4735
|
try {
|
|
4688
|
-
const sheetFlushLease = await this.resourceGovernor.acquireSheetFlush(
|
|
4689
|
-
{
|
|
4690
|
-
estimatedBytes: chunkBytes,
|
|
4691
|
-
rowCount: chunk.length,
|
|
4692
|
-
},
|
|
4693
|
-
);
|
|
4694
4736
|
const flushStartedAt = Date.now();
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
staleCompletionKeys.add(key);
|
|
4711
|
-
}
|
|
4737
|
+
const writeResult = await this.#options.onMapRowsCompleted!({
|
|
4738
|
+
playName: this.currentPlayName,
|
|
4739
|
+
playId: this.currentExecutionScope.logical.playId,
|
|
4740
|
+
runId: this.currentRunId,
|
|
4741
|
+
executorToken: this.#options.executorToken,
|
|
4742
|
+
tableNamespace: resolvedTableNamespace,
|
|
4743
|
+
rows: chunk,
|
|
4744
|
+
outputFields: datasetColumnNames.filter((field) =>
|
|
4745
|
+
shouldPersistMapCellField(field),
|
|
4746
|
+
),
|
|
4747
|
+
staticPipeline: this.currentStaticPipeline ?? null,
|
|
4748
|
+
});
|
|
4749
|
+
if (writeResult) {
|
|
4750
|
+
for (const key of writeResult.staleDroppedKeys ?? []) {
|
|
4751
|
+
staleCompletionKeys.add(key);
|
|
4712
4752
|
}
|
|
4713
|
-
this.resourceGovernor.observe({
|
|
4714
|
-
sheetFlushBytes: chunkBytes,
|
|
4715
|
-
sheetFlushLatencyMs: Date.now() - flushStartedAt,
|
|
4716
|
-
});
|
|
4717
|
-
} finally {
|
|
4718
|
-
sheetFlushLease.release();
|
|
4719
4753
|
}
|
|
4754
|
+
this.resourceGovernor.observe({
|
|
4755
|
+
sheetFlushBytes: chunkBytes,
|
|
4756
|
+
sheetFlushLatencyMs: Date.now() - flushStartedAt,
|
|
4757
|
+
});
|
|
4720
4758
|
} catch (error) {
|
|
4721
4759
|
// Output-sheet flush failed: trip the breaker so the dispatch loops
|
|
4722
4760
|
// stop dispatching new provider calls for the rest of this map.
|
|
@@ -4740,7 +4778,22 @@ export class PlayContextImpl {
|
|
|
4740
4778
|
await flushChunk();
|
|
4741
4779
|
};
|
|
4742
4780
|
const incrementalPersistence = this.#options.onMapRowsCompleted
|
|
4743
|
-
?
|
|
4781
|
+
? createRuntimeMapRowPersistence(persistMapRows, {
|
|
4782
|
+
persistCheckpoint: this.#options.onMapRowsCheckpoint
|
|
4783
|
+
? (updates) =>
|
|
4784
|
+
this.#options.onMapRowsCheckpoint!({
|
|
4785
|
+
playName: this.currentPlayName,
|
|
4786
|
+
playId: this.currentExecutionScope.logical.playId,
|
|
4787
|
+
runId: this.currentRunId,
|
|
4788
|
+
executorToken: this.#options.executorToken,
|
|
4789
|
+
tableNamespace: resolvedTableNamespace,
|
|
4790
|
+
updates,
|
|
4791
|
+
staticPipeline: this.currentStaticPipeline ?? null,
|
|
4792
|
+
})
|
|
4793
|
+
: undefined,
|
|
4794
|
+
onFailure: (error) =>
|
|
4795
|
+
tripRuntimePersistenceLatch(this.persistenceLatch, error),
|
|
4796
|
+
})
|
|
4744
4797
|
: null;
|
|
4745
4798
|
|
|
4746
4799
|
let mapResult: FieldMapRunResult;
|
|
@@ -4785,9 +4838,11 @@ export class PlayContextImpl {
|
|
|
4785
4838
|
);
|
|
4786
4839
|
}
|
|
4787
4840
|
this.activeMapCellMeta = null;
|
|
4841
|
+
this.activeMapCheckpointUpdates = null;
|
|
4788
4842
|
throw error.cause;
|
|
4789
4843
|
}
|
|
4790
4844
|
this.activeMapCellMeta = null;
|
|
4845
|
+
this.activeMapCheckpointUpdates = null;
|
|
4791
4846
|
throw error;
|
|
4792
4847
|
}
|
|
4793
4848
|
const resultsByKey = new Map<string, Record<string, unknown>>();
|
|
@@ -4852,6 +4907,7 @@ export class PlayContextImpl {
|
|
|
4852
4907
|
}
|
|
4853
4908
|
}
|
|
4854
4909
|
this.activeMapCellMeta = null;
|
|
4910
|
+
this.activeMapCheckpointUpdates = null;
|
|
4855
4911
|
|
|
4856
4912
|
if (staleCompletionKeys.size > 0) {
|
|
4857
4913
|
results = results.filter((row, index) => {
|
|
@@ -5083,7 +5139,7 @@ export class PlayContextImpl {
|
|
|
5083
5139
|
progressCompletedOffset?: number;
|
|
5084
5140
|
progressFailedOffset?: number;
|
|
5085
5141
|
progressTotalRows?: number;
|
|
5086
|
-
incrementalPersistence?:
|
|
5142
|
+
incrementalPersistence?: RuntimeMapRowPersistence | null;
|
|
5087
5143
|
},
|
|
5088
5144
|
): Promise<FieldMapRunResult> {
|
|
5089
5145
|
const fieldEntries = Object.entries(definition);
|
|
@@ -5135,18 +5191,17 @@ export class PlayContextImpl {
|
|
|
5135
5191
|
const enqueueIncrementalPersist = (
|
|
5136
5192
|
row: PersistableMapRow,
|
|
5137
5193
|
onCommitted: () => void,
|
|
5138
|
-
): void => {
|
|
5194
|
+
): Promise<void> => {
|
|
5139
5195
|
if (!incrementalPersistence) {
|
|
5140
5196
|
onCommitted();
|
|
5141
|
-
return;
|
|
5197
|
+
return Promise.resolve();
|
|
5142
5198
|
}
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
.
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
});
|
|
5199
|
+
const settlement = incrementalPersistence.persistRows([row]);
|
|
5200
|
+
void settlement.committed.then(onCommitted).catch(() => {
|
|
5201
|
+
// The terminal map barrier awaits the writer and surfaces the same
|
|
5202
|
+
// latched failure. This continuation only publishes durable progress.
|
|
5203
|
+
});
|
|
5204
|
+
return settlement.admitted;
|
|
5150
5205
|
};
|
|
5151
5206
|
|
|
5152
5207
|
if (completedRows > 0 || pendingRows !== totalRows) {
|
|
@@ -5512,7 +5567,11 @@ export class PlayContextImpl {
|
|
|
5512
5567
|
error: formattedError,
|
|
5513
5568
|
dataPatch: {},
|
|
5514
5569
|
});
|
|
5515
|
-
enqueueIncrementalPersist(failedRow, () => {
|
|
5570
|
+
await enqueueIncrementalPersist(failedRow, () => {
|
|
5571
|
+
this.clearActiveMapCheckpointUpdate(
|
|
5572
|
+
rowKey,
|
|
5573
|
+
normalizedTableNamespace,
|
|
5574
|
+
);
|
|
5516
5575
|
updateMapFrameProgress({ failedRowKey: rowKey });
|
|
5517
5576
|
});
|
|
5518
5577
|
return FAILED_ROW;
|
|
@@ -5591,7 +5650,8 @@ export class PlayContextImpl {
|
|
|
5591
5650
|
});
|
|
5592
5651
|
retainedRowsMemoryTracker.track(completedRow);
|
|
5593
5652
|
completedRowsToPersist.push(completedRow);
|
|
5594
|
-
enqueueIncrementalPersist(completedRow, () => {
|
|
5653
|
+
await enqueueIncrementalPersist(completedRow, () => {
|
|
5654
|
+
this.clearActiveMapCheckpointUpdate(rowKey, normalizedTableNamespace);
|
|
5595
5655
|
updateMapFrameProgress({ completedRowKey: rowKey });
|
|
5596
5656
|
});
|
|
5597
5657
|
return publicRow;
|
|
@@ -5739,8 +5799,18 @@ export class PlayContextImpl {
|
|
|
5739
5799
|
]),
|
|
5740
5800
|
).values(),
|
|
5741
5801
|
];
|
|
5742
|
-
|
|
5802
|
+
const checkpointUpdates = [
|
|
5803
|
+
...(this.activeMapCheckpointUpdates?.values() ?? []),
|
|
5804
|
+
].filter(
|
|
5805
|
+
(update) =>
|
|
5806
|
+
(update.tableNamespace ?? normalizedTableNamespace) ===
|
|
5807
|
+
normalizedTableNamespace,
|
|
5808
|
+
);
|
|
5809
|
+
// A suspension is not observable until both terminal rows and partial
|
|
5810
|
+
// completed-cell patches cross the same writer's durability barrier.
|
|
5811
|
+
await incrementalPersistence?.checkpoint(checkpointUpdates);
|
|
5743
5812
|
await incrementalPersistence?.flush();
|
|
5813
|
+
this.pendingRowEventBoundaries = [];
|
|
5744
5814
|
this.#options.onBatchComplete?.(this.checkpoint);
|
|
5745
5815
|
throw new PlayExecutionSuspendedError({
|
|
5746
5816
|
kind: 'integration_event_batch',
|
|
@@ -604,6 +604,20 @@ export interface ContextOptions {
|
|
|
604
604
|
updated: number;
|
|
605
605
|
staleDroppedKeys?: string[];
|
|
606
606
|
}>;
|
|
607
|
+
/**
|
|
608
|
+
* Persists coalesced, non-terminal row patches before a map suspends.
|
|
609
|
+
* Terminal rows use onMapRowsCompleted; this narrow seam only preserves
|
|
610
|
+
* cells that completed before an integration-event boundary.
|
|
611
|
+
*/
|
|
612
|
+
onMapRowsCheckpoint?: (input: {
|
|
613
|
+
playName?: string;
|
|
614
|
+
playId?: string;
|
|
615
|
+
runId: string;
|
|
616
|
+
executorToken?: string;
|
|
617
|
+
tableNamespace: string;
|
|
618
|
+
updates: PlayRowUpdate[];
|
|
619
|
+
staticPipeline?: PlayStaticPipeline | null;
|
|
620
|
+
}) => Promise<void>;
|
|
607
621
|
playId?: string;
|
|
608
622
|
runId?: string;
|
|
609
623
|
/** Physical executor run that owns leases for an inline child context. */
|
|
@@ -1030,6 +1044,7 @@ export interface PlayRowUpdate {
|
|
|
1030
1044
|
attemptOwnerRunId?: string | null;
|
|
1031
1045
|
attemptSeq?: number | null;
|
|
1032
1046
|
attemptExpiresAt?: string | null;
|
|
1047
|
+
writeVersion?: number | null;
|
|
1033
1048
|
status?: 'running' | 'completed' | 'failed';
|
|
1034
1049
|
stage?: string | null;
|
|
1035
1050
|
provider?: string | null;
|