deepline 0.1.83 → 0.1.88
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/cli/index.js +481 -79
- package/dist/cli/index.mjs +500 -92
- package/dist/index.d.mts +604 -28
- package/dist/index.d.ts +604 -28
- package/dist/index.js +274 -4
- package/dist/index.mjs +269 -4
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +1 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +345 -72
- package/dist/repo/sdk/src/client.ts +155 -1
- package/dist/repo/sdk/src/http.ts +11 -0
- package/dist/repo/sdk/src/index.ts +35 -1
- package/dist/repo/sdk/src/play.ts +254 -15
- package/dist/repo/sdk/src/plays/harness-stub.ts +2 -1
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/sdk/src/types.ts +65 -0
- package/dist/repo/sdk/src/worker-play-entry.ts +6 -3
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +179 -7
- package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +10 -0
- package/dist/repo/shared_libs/play-runtime/execution-plan.ts +3 -3
- package/dist/repo/shared_libs/play-runtime/extractor-targets.ts +106 -0
- package/dist/repo/shared_libs/play-runtime/providers.ts +28 -0
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +21 -1
- package/dist/repo/shared_libs/play-runtime/step-program-dataset-builder.ts +96 -6
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +82 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +23 -16
- package/dist/repo/shared_libs/plays/dataset.ts +2 -0
- package/dist/repo/shared_libs/plays/static-pipeline.ts +133 -24
- package/package.json +1 -1
|
@@ -163,11 +163,19 @@ import {
|
|
|
163
163
|
} from './runtime/tool-http-errors';
|
|
164
164
|
import {
|
|
165
165
|
StepProgramDatasetBuilder,
|
|
166
|
+
type StepProgramDatasetColumnInput,
|
|
166
167
|
type StepProgramDatasetOptions,
|
|
167
168
|
} from '../../../shared_libs/play-runtime/step-program-dataset-builder';
|
|
168
169
|
import {
|
|
169
170
|
DEEPLINE_CELL_META_FIELD,
|
|
171
|
+
normalizeCellStalenessPolicy,
|
|
172
|
+
previousCellFromValue,
|
|
173
|
+
resolveCompletedCellStalenessMeta,
|
|
170
174
|
shouldRecomputeCell,
|
|
175
|
+
type AuthoredCellStalenessPolicyByField,
|
|
176
|
+
type AuthoredStaleAfterSeconds,
|
|
177
|
+
type CellStalenessPolicyByField,
|
|
178
|
+
type PreviousCell,
|
|
171
179
|
} from '../../../shared_libs/play-runtime/cell-staleness';
|
|
172
180
|
|
|
173
181
|
// The play's default export. The bundler injects this — see bundle-play-file.ts.
|
|
@@ -592,7 +600,7 @@ type WorkerCtxCallbacks = {
|
|
|
592
600
|
nodeId: string;
|
|
593
601
|
progress: LiveNodeProgressSnapshot;
|
|
594
602
|
forceFlush?: boolean;
|
|
595
|
-
}) => void
|
|
603
|
+
}) => void | Promise<void>;
|
|
596
604
|
onMapStarted?: (nodeId: string, at?: number) => void;
|
|
597
605
|
onMapCompleted?: (nodeId: string, at?: number) => void;
|
|
598
606
|
onToolCalled?: (toolId: string, at?: number) => void;
|
|
@@ -717,10 +725,7 @@ function publicCsvStorageRow<T extends Record<string, unknown>>(row: T): T {
|
|
|
717
725
|
storageRow[fieldName] =
|
|
718
726
|
'value' in descriptor ? descriptor.value : publicRow[fieldName];
|
|
719
727
|
}
|
|
720
|
-
for (const runtimeField of [
|
|
721
|
-
'__deeplineRowKey',
|
|
722
|
-
'__deeplineCellMetaPatch',
|
|
723
|
-
]) {
|
|
728
|
+
for (const runtimeField of ['__deeplineRowKey', '__deeplineCellMetaPatch']) {
|
|
724
729
|
if (Object.prototype.hasOwnProperty.call(row, runtimeField)) {
|
|
725
730
|
storageRow[runtimeField] = row[runtimeField];
|
|
726
731
|
}
|
|
@@ -1587,6 +1592,7 @@ type WorkerToolBatchRequest = {
|
|
|
1587
1592
|
};
|
|
1588
1593
|
|
|
1589
1594
|
const WORKER_TOOL_BATCH_GRACE_MS = 250;
|
|
1595
|
+
const MAP_EXECUTION_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
1590
1596
|
// Fallback batch-chunk parallelism when a tool declares no provider rate hints.
|
|
1591
1597
|
// Matches the prior hardcoded `Math.min(4, ...)` so undeclared providers keep
|
|
1592
1598
|
// their previous batching behavior; declared providers tighten via the
|
|
@@ -1594,6 +1600,10 @@ const WORKER_TOOL_BATCH_GRACE_MS = 250;
|
|
|
1594
1600
|
const WORKER_TOOL_BATCH_DEFAULT_PARALLELISM = 4;
|
|
1595
1601
|
const WORKER_RETRY_SAFE_5XX_TOOLS = new Set(['test_transient_500']);
|
|
1596
1602
|
|
|
1603
|
+
function sleepWorkerMs(ms: number): Promise<void> {
|
|
1604
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1597
1607
|
function stepProgramColumnName(parentField: string, stepId: string): string {
|
|
1598
1608
|
return sqlSafePlayColumnName(`${parentField}.${stepId}`);
|
|
1599
1609
|
}
|
|
@@ -1973,6 +1983,7 @@ type WorkerStepResolver = (
|
|
|
1973
1983
|
row: Record<string, unknown>,
|
|
1974
1984
|
ctx: unknown,
|
|
1975
1985
|
index: number,
|
|
1986
|
+
previousCell?: PreviousCell,
|
|
1976
1987
|
) => Promise<unknown> | unknown;
|
|
1977
1988
|
|
|
1978
1989
|
type WorkerConditionalStepResolver = {
|
|
@@ -1987,7 +1998,7 @@ type WorkerConditionalStepResolver = {
|
|
|
1987
1998
|
|
|
1988
1999
|
type WorkerStepProgramStep = {
|
|
1989
2000
|
name: string;
|
|
1990
|
-
staleAfterSeconds?:
|
|
2001
|
+
staleAfterSeconds?: AuthoredStaleAfterSeconds;
|
|
1991
2002
|
resolver:
|
|
1992
2003
|
| WorkerStepResolver
|
|
1993
2004
|
| WorkerConditionalStepResolver
|
|
@@ -2042,6 +2053,7 @@ async function executeWorkerStepResolver(
|
|
|
2042
2053
|
row: Record<string, unknown>,
|
|
2043
2054
|
ctx: unknown,
|
|
2044
2055
|
index: number,
|
|
2056
|
+
previousCell?: PreviousCell,
|
|
2045
2057
|
recorder?: {
|
|
2046
2058
|
parentField: string;
|
|
2047
2059
|
path: string[];
|
|
@@ -2051,7 +2063,14 @@ async function executeWorkerStepResolver(
|
|
|
2051
2063
|
if (isWorkerConditionalStepResolver(resolver)) {
|
|
2052
2064
|
const shouldRun = await resolver.when(row, index);
|
|
2053
2065
|
return shouldRun
|
|
2054
|
-
? await executeWorkerStepResolver(
|
|
2066
|
+
? await executeWorkerStepResolver(
|
|
2067
|
+
resolver.run,
|
|
2068
|
+
row,
|
|
2069
|
+
ctx,
|
|
2070
|
+
index,
|
|
2071
|
+
previousCell,
|
|
2072
|
+
recorder,
|
|
2073
|
+
)
|
|
2055
2074
|
: {
|
|
2056
2075
|
value: resolver.elseValue ?? null,
|
|
2057
2076
|
status: 'skipped',
|
|
@@ -2069,7 +2088,14 @@ async function executeWorkerStepResolver(
|
|
|
2069
2088
|
};
|
|
2070
2089
|
}
|
|
2071
2090
|
if (typeof resolver === 'function') {
|
|
2072
|
-
return {
|
|
2091
|
+
return {
|
|
2092
|
+
value: await (resolver as WorkerStepResolver)(
|
|
2093
|
+
row,
|
|
2094
|
+
ctx,
|
|
2095
|
+
index,
|
|
2096
|
+
previousCell,
|
|
2097
|
+
),
|
|
2098
|
+
};
|
|
2073
2099
|
}
|
|
2074
2100
|
return { value: resolver };
|
|
2075
2101
|
}
|
|
@@ -2095,6 +2121,7 @@ async function executeWorkerStepProgram(
|
|
|
2095
2121
|
currentRow,
|
|
2096
2122
|
ctx,
|
|
2097
2123
|
index,
|
|
2124
|
+
undefined,
|
|
2098
2125
|
recorder
|
|
2099
2126
|
? {
|
|
2100
2127
|
...recorder,
|
|
@@ -3005,7 +3032,7 @@ async function prepareMapRows(input: {
|
|
|
3005
3032
|
tableNamespace: string;
|
|
3006
3033
|
rows: Record<string, unknown>[];
|
|
3007
3034
|
outputFields: string[];
|
|
3008
|
-
cellPolicies?:
|
|
3035
|
+
cellPolicies?: CellStalenessPolicyByField;
|
|
3009
3036
|
}): Promise<{
|
|
3010
3037
|
inserted: number;
|
|
3011
3038
|
skipped: number;
|
|
@@ -3161,18 +3188,21 @@ function createCoordinatorRatePort(req: RunRequest): CoordinatorRatePort {
|
|
|
3161
3188
|
if (!coordinatorUrl) {
|
|
3162
3189
|
throw new Error('Coordinator rate acquire is unavailable.');
|
|
3163
3190
|
}
|
|
3164
|
-
const res = await fetch(
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
'
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3191
|
+
const res = await fetch(
|
|
3192
|
+
`${coordinatorUrl.replace(/\/$/, '')}/rate-acquire`,
|
|
3193
|
+
{
|
|
3194
|
+
method: 'POST',
|
|
3195
|
+
headers: {
|
|
3196
|
+
'x-deepline-request-id': makeRequestId(),
|
|
3197
|
+
...coordinatorRequestHeaders({
|
|
3198
|
+
runId: req.runId,
|
|
3199
|
+
contentType: 'application/json',
|
|
3200
|
+
internalToken: req.coordinatorInternalToken,
|
|
3201
|
+
}),
|
|
3202
|
+
},
|
|
3203
|
+
body: JSON.stringify(input),
|
|
3173
3204
|
},
|
|
3174
|
-
|
|
3175
|
-
});
|
|
3205
|
+
);
|
|
3176
3206
|
if (!res.ok) {
|
|
3177
3207
|
const text = await res.text().catch(() => '');
|
|
3178
3208
|
throw new Error(
|
|
@@ -3494,7 +3524,8 @@ function createMinimalWorkerCtx(
|
|
|
3494
3524
|
index: number,
|
|
3495
3525
|
) => Promise<unknown> | unknown)
|
|
3496
3526
|
>,
|
|
3497
|
-
cellPolicies?:
|
|
3527
|
+
cellPolicies?: CellStalenessPolicyByField,
|
|
3528
|
+
authoredCellPolicies?: AuthoredCellStalenessPolicyByField,
|
|
3498
3529
|
opts?: WorkerMapOptions,
|
|
3499
3530
|
): Promise<unknown> => {
|
|
3500
3531
|
const mapStartedAt = nowMs();
|
|
@@ -3516,8 +3547,11 @@ function createMinimalWorkerCtx(
|
|
|
3516
3547
|
softWorkflowStepBudget: plan?.chunkPlan.softWorkflowStepBudget,
|
|
3517
3548
|
});
|
|
3518
3549
|
const outputFields = fieldEntries.map(([field]) => field);
|
|
3519
|
-
const updateMapProgress = (
|
|
3520
|
-
|
|
3550
|
+
const updateMapProgress = (
|
|
3551
|
+
progress: LiveNodeProgressSnapshot,
|
|
3552
|
+
options?: { forceFlush?: boolean },
|
|
3553
|
+
): void | Promise<void> => {
|
|
3554
|
+
return callbacks?.onNodeProgress?.({
|
|
3521
3555
|
nodeId: mapNodeId,
|
|
3522
3556
|
progress: {
|
|
3523
3557
|
artifactTableNamespace: name,
|
|
@@ -3525,19 +3559,62 @@ function createMinimalWorkerCtx(
|
|
|
3525
3559
|
...progress,
|
|
3526
3560
|
updatedAt: progress.updatedAt ?? nowMs(),
|
|
3527
3561
|
},
|
|
3528
|
-
forceFlush: true,
|
|
3562
|
+
forceFlush: options?.forceFlush ?? true,
|
|
3529
3563
|
});
|
|
3530
3564
|
};
|
|
3531
3565
|
const formatMapProgressMessage = (completed: number, total?: number) =>
|
|
3532
3566
|
typeof total === 'number' && Number.isFinite(total) && total > 0
|
|
3533
3567
|
? `${completed.toLocaleString()} / ${total.toLocaleString()} rows processed`
|
|
3534
3568
|
: `${completed.toLocaleString()} rows processed`;
|
|
3569
|
+
const formatMapPreparingMessage = (total?: number) =>
|
|
3570
|
+
typeof total === 'number' && Number.isFinite(total) && total > 0
|
|
3571
|
+
? `Preparing ${total.toLocaleString()} rows`
|
|
3572
|
+
: 'Preparing rows';
|
|
3573
|
+
const formatMapQueuedMessage = (input: {
|
|
3574
|
+
completed: number;
|
|
3575
|
+
queued: number;
|
|
3576
|
+
total?: number;
|
|
3577
|
+
}) => {
|
|
3578
|
+
const completed = Math.max(0, input.completed);
|
|
3579
|
+
const queued = Math.max(0, input.queued);
|
|
3580
|
+
if (completed > 0 && queued > 0) {
|
|
3581
|
+
return `${completed.toLocaleString()} already satisfied, ${queued.toLocaleString()} queued`;
|
|
3582
|
+
}
|
|
3583
|
+
if (queued > 0) {
|
|
3584
|
+
return `${queued.toLocaleString()} rows queued`;
|
|
3585
|
+
}
|
|
3586
|
+
return formatMapProgressMessage(completed, input.total);
|
|
3587
|
+
};
|
|
3588
|
+
const formatMapProcessingMessage = (rowsToExecute: number) =>
|
|
3589
|
+
rowsToExecute > 0
|
|
3590
|
+
? `Processing ${rowsToExecute.toLocaleString()} rows`
|
|
3591
|
+
: null;
|
|
3592
|
+
const formatMapExecutionHeartbeatMessage = (input: {
|
|
3593
|
+
rowsToExecute: number;
|
|
3594
|
+
startedRows: number;
|
|
3595
|
+
activeRows: number;
|
|
3596
|
+
completedRows: number;
|
|
3597
|
+
}) => {
|
|
3598
|
+
const rowsToExecute = Math.max(0, input.rowsToExecute);
|
|
3599
|
+
const startedRows = Math.max(0, input.startedRows);
|
|
3600
|
+
const activeRows = Math.max(0, input.activeRows);
|
|
3601
|
+
const completedRows = Math.max(0, input.completedRows);
|
|
3602
|
+
const waitingRows = Math.max(0, rowsToExecute - startedRows);
|
|
3603
|
+
const parts = [
|
|
3604
|
+
activeRows > 0 ? `${activeRows.toLocaleString()} active` : null,
|
|
3605
|
+
waitingRows > 0 ? `${waitingRows.toLocaleString()} waiting` : null,
|
|
3606
|
+
completedRows > 0 ? `${completedRows.toLocaleString()} done` : null,
|
|
3607
|
+
].filter((part): part is string => Boolean(part));
|
|
3608
|
+
const base =
|
|
3609
|
+
formatMapProcessingMessage(rowsToExecute) ?? 'Processing rows';
|
|
3610
|
+
return parts.length > 0 ? `${base} (${parts.join(', ')})` : base;
|
|
3611
|
+
};
|
|
3535
3612
|
callbacks?.onMapStarted?.(mapNodeId, mapStartedAt);
|
|
3536
|
-
updateMapProgress({
|
|
3613
|
+
await updateMapProgress({
|
|
3537
3614
|
completed: 0,
|
|
3538
3615
|
total: rowCountHint ?? undefined,
|
|
3539
3616
|
startedAt: mapStartedAt,
|
|
3540
|
-
message:
|
|
3617
|
+
message: formatMapPreparingMessage(rowCountHint ?? undefined),
|
|
3541
3618
|
});
|
|
3542
3619
|
const explicitRowKeysSeen =
|
|
3543
3620
|
opts?.key === undefined ? null : new Map<string, number>();
|
|
@@ -3611,6 +3688,8 @@ function createMinimalWorkerCtx(
|
|
|
3611
3688
|
}
|
|
3612
3689
|
};
|
|
3613
3690
|
|
|
3691
|
+
let totalRowsWritten = 0;
|
|
3692
|
+
|
|
3614
3693
|
const processChunk = async (
|
|
3615
3694
|
chunkRows: T[],
|
|
3616
3695
|
chunkStart: number,
|
|
@@ -3655,17 +3734,20 @@ function createMinimalWorkerCtx(
|
|
|
3655
3734
|
completedRows: prepared.completedRows.length,
|
|
3656
3735
|
},
|
|
3657
3736
|
});
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3737
|
+
const progressTotalRows = rowCountHint ?? chunkRows.length;
|
|
3738
|
+
const preparedCompletedRows = Math.min(
|
|
3739
|
+
progressTotalRows,
|
|
3740
|
+
totalRowsWritten + prepared.completedRows.length,
|
|
3741
|
+
);
|
|
3742
|
+
await updateMapProgress({
|
|
3743
|
+
completed: preparedCompletedRows,
|
|
3744
|
+
total: progressTotalRows,
|
|
3661
3745
|
startedAt: mapStartedAt,
|
|
3662
|
-
message:
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
chunkRows.length,
|
|
3668
|
-
),
|
|
3746
|
+
message: formatMapQueuedMessage({
|
|
3747
|
+
completed: preparedCompletedRows,
|
|
3748
|
+
queued: prepared.pendingRows.length,
|
|
3749
|
+
total: progressTotalRows,
|
|
3750
|
+
}),
|
|
3669
3751
|
});
|
|
3670
3752
|
const pendingKeys = new Set<string>();
|
|
3671
3753
|
const pendingRowsByKey = new Map<string, Record<string, unknown>>();
|
|
@@ -3709,38 +3791,83 @@ function createMinimalWorkerCtx(
|
|
|
3709
3791
|
new Set(chunkEntries.map((entry) => entry.rowKey)).size,
|
|
3710
3792
|
);
|
|
3711
3793
|
const rowsToExecute = uniqueRowsToExecuteEntries.map(({ row }) => row);
|
|
3794
|
+
const processingMessage = formatMapProcessingMessage(
|
|
3795
|
+
rowsToExecute.length,
|
|
3796
|
+
);
|
|
3797
|
+
if (processingMessage) {
|
|
3798
|
+
await updateMapProgress({
|
|
3799
|
+
completed: preparedCompletedRows,
|
|
3800
|
+
total: progressTotalRows,
|
|
3801
|
+
startedAt: mapStartedAt,
|
|
3802
|
+
message: processingMessage,
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3712
3805
|
const rowsInserted = prepared.inserted + missingPreparedRows.length;
|
|
3713
3806
|
const rowsSkipped = Math.max(
|
|
3714
3807
|
0,
|
|
3715
3808
|
prepared.skipped - missingPreparedRows.length,
|
|
3716
3809
|
);
|
|
3717
|
-
let
|
|
3718
|
-
let
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3810
|
+
let completedExecutedRows = 0;
|
|
3811
|
+
let startedExecutedRows = 0;
|
|
3812
|
+
let activeExecutedRows = 0;
|
|
3813
|
+
let lastChunkProgressAt = 0;
|
|
3814
|
+
let lastExecutionHeartbeatAt = nowMs();
|
|
3815
|
+
const completedRowsForProgress = () =>
|
|
3816
|
+
Math.min(
|
|
3817
|
+
progressTotalRows,
|
|
3818
|
+
totalRowsWritten +
|
|
3819
|
+
prepared.completedRows.length +
|
|
3820
|
+
completedExecutedRows,
|
|
3821
|
+
);
|
|
3822
|
+
const reportExecutionHeartbeat = (force = false) => {
|
|
3722
3823
|
const now = nowMs();
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3824
|
+
if (
|
|
3825
|
+
!force &&
|
|
3826
|
+
now - lastExecutionHeartbeatAt < MAP_EXECUTION_HEARTBEAT_INTERVAL_MS
|
|
3827
|
+
) {
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
lastExecutionHeartbeatAt = now;
|
|
3831
|
+
void updateMapProgress(
|
|
3832
|
+
{
|
|
3833
|
+
completed: completedRowsForProgress(),
|
|
3834
|
+
total: progressTotalRows,
|
|
3835
|
+
startedAt: mapStartedAt,
|
|
3836
|
+
message: formatMapExecutionHeartbeatMessage({
|
|
3837
|
+
rowsToExecute: rowsToExecute.length,
|
|
3838
|
+
startedRows: startedExecutedRows,
|
|
3839
|
+
activeRows: activeExecutedRows,
|
|
3840
|
+
completedRows: completedExecutedRows,
|
|
3841
|
+
}),
|
|
3842
|
+
},
|
|
3843
|
+
{ forceFlush: force },
|
|
3726
3844
|
);
|
|
3727
|
-
|
|
3845
|
+
};
|
|
3846
|
+
const reportChunkProgress = (force = false) => {
|
|
3847
|
+
const now = nowMs();
|
|
3848
|
+
const completed = completedRowsForProgress();
|
|
3849
|
+
const isTerminalEstimate = completed >= progressTotalRows;
|
|
3728
3850
|
if (
|
|
3851
|
+
!force &&
|
|
3729
3852
|
!isTerminalEstimate &&
|
|
3730
|
-
now -
|
|
3853
|
+
now - lastChunkProgressAt < RUN_LEDGER_FLUSH_INTERVAL_MS
|
|
3731
3854
|
) {
|
|
3732
3855
|
return;
|
|
3733
3856
|
}
|
|
3734
|
-
|
|
3735
|
-
updateMapProgress(
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3857
|
+
lastChunkProgressAt = now;
|
|
3858
|
+
void updateMapProgress(
|
|
3859
|
+
{
|
|
3860
|
+
completed,
|
|
3861
|
+
total: progressTotalRows,
|
|
3862
|
+
startedAt: mapStartedAt,
|
|
3863
|
+
message: formatMapProgressMessage(completed, progressTotalRows),
|
|
3864
|
+
},
|
|
3865
|
+
{ forceFlush: force },
|
|
3866
|
+
);
|
|
3867
|
+
};
|
|
3868
|
+
const reportSettledToolRequests = (count: number) => {
|
|
3869
|
+
if (count <= 0) return;
|
|
3870
|
+
reportChunkProgress(false);
|
|
3744
3871
|
};
|
|
3745
3872
|
// Row concurrency comes from the Governor: an explicit map concurrency is
|
|
3746
3873
|
// clamped to the policy row-max, otherwise the policy default. Each row
|
|
@@ -3760,6 +3887,8 @@ function createMinimalWorkerCtx(
|
|
|
3760
3887
|
reused?: boolean;
|
|
3761
3888
|
runId?: string;
|
|
3762
3889
|
completedAt?: number;
|
|
3890
|
+
staleAt?: number | null;
|
|
3891
|
+
staleAfterSeconds?: number | null;
|
|
3763
3892
|
}
|
|
3764
3893
|
>
|
|
3765
3894
|
| undefined
|
|
@@ -3784,12 +3913,25 @@ function createMinimalWorkerCtx(
|
|
|
3784
3913
|
const rowSlot = await governor.acquireRowSlot({
|
|
3785
3914
|
signal: abortSignal,
|
|
3786
3915
|
});
|
|
3916
|
+
let rowMarkedActive = false;
|
|
3787
3917
|
try {
|
|
3918
|
+
startedExecutedRows += 1;
|
|
3919
|
+
activeExecutedRows += 1;
|
|
3920
|
+
rowMarkedActive = true;
|
|
3921
|
+
reportExecutionHeartbeat(false);
|
|
3788
3922
|
const entry = uniqueRowsToExecuteEntries[myIndex]!;
|
|
3789
|
-
const
|
|
3923
|
+
const pendingRow = pendingRowsByKey.get(entry.rowKey);
|
|
3924
|
+
const row = pendingRow
|
|
3790
3925
|
? ({
|
|
3791
3926
|
...entry.row,
|
|
3792
|
-
...publicCsvInputRow(
|
|
3927
|
+
...publicCsvInputRow(pendingRow),
|
|
3928
|
+
...(pendingRow[DEEPLINE_CELL_META_FIELD] &&
|
|
3929
|
+
typeof pendingRow[DEEPLINE_CELL_META_FIELD] === 'object'
|
|
3930
|
+
? {
|
|
3931
|
+
[DEEPLINE_CELL_META_FIELD]:
|
|
3932
|
+
pendingRow[DEEPLINE_CELL_META_FIELD],
|
|
3933
|
+
}
|
|
3934
|
+
: {}),
|
|
3793
3935
|
} as T & Record<string, unknown>)
|
|
3794
3936
|
: entry.row;
|
|
3795
3937
|
const absoluteIndex = entry.absoluteIndex;
|
|
@@ -3804,6 +3946,8 @@ function createMinimalWorkerCtx(
|
|
|
3804
3946
|
reused?: boolean;
|
|
3805
3947
|
runId?: string;
|
|
3806
3948
|
completedAt?: number;
|
|
3949
|
+
staleAt?: number | null;
|
|
3950
|
+
staleAfterSeconds?: number | null;
|
|
3807
3951
|
}
|
|
3808
3952
|
> = {};
|
|
3809
3953
|
const waterfallOutputs: RecordedWaterfallOutput[] = [];
|
|
@@ -3857,10 +4001,25 @@ function createMinimalWorkerCtx(
|
|
|
3857
4001
|
? (rawCellMeta as {
|
|
3858
4002
|
status?: string;
|
|
3859
4003
|
completedAt?: number;
|
|
4004
|
+
staleAt?: number | null;
|
|
4005
|
+
staleAfterSeconds?: number | null;
|
|
3860
4006
|
})
|
|
3861
4007
|
: null,
|
|
3862
4008
|
policy: cellPolicies?.[key],
|
|
3863
4009
|
});
|
|
4010
|
+
const previousCell = previousCellFromValue({
|
|
4011
|
+
hasValue: isCompletedWorkerFieldValue(enriched[key]),
|
|
4012
|
+
value: enriched[key],
|
|
4013
|
+
meta:
|
|
4014
|
+
rawCellMeta && typeof rawCellMeta === 'object'
|
|
4015
|
+
? (rawCellMeta as {
|
|
4016
|
+
status?: string;
|
|
4017
|
+
completedAt?: number;
|
|
4018
|
+
staleAt?: number | null;
|
|
4019
|
+
staleAfterSeconds?: number | null;
|
|
4020
|
+
})
|
|
4021
|
+
: null,
|
|
4022
|
+
});
|
|
3864
4023
|
if (reuseDecision.action === 'reuse') {
|
|
3865
4024
|
cellMetaPatch[key] = {
|
|
3866
4025
|
status: 'cached',
|
|
@@ -3875,6 +4034,7 @@ function createMinimalWorkerCtx(
|
|
|
3875
4034
|
enriched,
|
|
3876
4035
|
rowCtx,
|
|
3877
4036
|
absoluteIndex,
|
|
4037
|
+
previousCell,
|
|
3878
4038
|
isWorkerStepProgram(value)
|
|
3879
4039
|
? {
|
|
3880
4040
|
parentField: key,
|
|
@@ -3892,11 +4052,18 @@ function createMinimalWorkerCtx(
|
|
|
3892
4052
|
runId: req.runId,
|
|
3893
4053
|
};
|
|
3894
4054
|
} else {
|
|
4055
|
+
const completedAt = nowMs();
|
|
4056
|
+
const stalenessMeta = resolveCompletedCellStalenessMeta({
|
|
4057
|
+
policy: authoredCellPolicies?.[key],
|
|
4058
|
+
value: resolved.value,
|
|
4059
|
+
completedAt,
|
|
4060
|
+
});
|
|
3895
4061
|
cellMetaPatch[key] = {
|
|
3896
4062
|
status: 'completed',
|
|
3897
4063
|
stage: key,
|
|
3898
4064
|
runId: req.runId,
|
|
3899
|
-
completedAt
|
|
4065
|
+
completedAt,
|
|
4066
|
+
...stalenessMeta,
|
|
3900
4067
|
};
|
|
3901
4068
|
}
|
|
3902
4069
|
}
|
|
@@ -3924,7 +4091,13 @@ function createMinimalWorkerCtx(
|
|
|
3924
4091
|
? cellMetaPatch
|
|
3925
4092
|
: undefined;
|
|
3926
4093
|
executedRows[myIndex] = enriched as T & Record<string, unknown>;
|
|
4094
|
+
completedExecutedRows += 1;
|
|
4095
|
+
reportChunkProgress(false);
|
|
3927
4096
|
} finally {
|
|
4097
|
+
if (rowMarkedActive) {
|
|
4098
|
+
activeExecutedRows = Math.max(0, activeExecutedRows - 1);
|
|
4099
|
+
reportExecutionHeartbeat(false);
|
|
4100
|
+
}
|
|
3928
4101
|
rowSlot.release();
|
|
3929
4102
|
}
|
|
3930
4103
|
}
|
|
@@ -3970,7 +4143,27 @@ function createMinimalWorkerCtx(
|
|
|
3970
4143
|
});
|
|
3971
4144
|
};
|
|
3972
4145
|
const workersStartedAt = nowMs();
|
|
3973
|
-
|
|
4146
|
+
// Track completion with a boolean flag rather than narrowing a
|
|
4147
|
+
// closure-assigned `| null` variable: TypeScript's control-flow analysis
|
|
4148
|
+
// does not see the assignment inside `.then(...)`, so a
|
|
4149
|
+
// `while (results === null)` loop would narrow it to `never` afterwards.
|
|
4150
|
+
let workerSettled = false;
|
|
4151
|
+
const workerResultsPromise = Promise.allSettled(workers).then(
|
|
4152
|
+
(results) => {
|
|
4153
|
+
workerSettled = true;
|
|
4154
|
+
return results;
|
|
4155
|
+
},
|
|
4156
|
+
);
|
|
4157
|
+
while (!workerSettled) {
|
|
4158
|
+
await Promise.race([
|
|
4159
|
+
workerResultsPromise,
|
|
4160
|
+
sleepWorkerMs(MAP_EXECUTION_HEARTBEAT_INTERVAL_MS),
|
|
4161
|
+
]);
|
|
4162
|
+
if (!workerSettled) {
|
|
4163
|
+
reportExecutionHeartbeat(false);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
const workerResults = await workerResultsPromise;
|
|
3974
4167
|
recordRunnerPerfTrace({
|
|
3975
4168
|
req,
|
|
3976
4169
|
phase: 'runner.map_chunk.execute_workers',
|
|
@@ -4137,7 +4330,7 @@ function createMinimalWorkerCtx(
|
|
|
4137
4330
|
`inserted=${totalRowsInserted} skipped=${totalRowsSkipped}`;
|
|
4138
4331
|
const completedAt = nowMs();
|
|
4139
4332
|
callbacks?.onMapCompleted?.(mapNodeId, completedAt);
|
|
4140
|
-
updateMapProgress({
|
|
4333
|
+
void updateMapProgress({
|
|
4141
4334
|
completed: totalRowsWritten,
|
|
4142
4335
|
total: totalRowsWritten,
|
|
4143
4336
|
completedAt,
|
|
@@ -4188,7 +4381,6 @@ function createMinimalWorkerCtx(
|
|
|
4188
4381
|
});
|
|
4189
4382
|
};
|
|
4190
4383
|
|
|
4191
|
-
let totalRowsWritten = 0;
|
|
4192
4384
|
let chunkIndex = 0;
|
|
4193
4385
|
let chunkStart = 0;
|
|
4194
4386
|
for await (const chunkRows of iterDatasetChunks(inputRows, rowsPerChunk)) {
|
|
@@ -4202,7 +4394,7 @@ function createMinimalWorkerCtx(
|
|
|
4202
4394
|
totalRowsDuplicateReused += chunkResult.rowsDuplicateReused;
|
|
4203
4395
|
totalRowsInserted += chunkResult.rowsInserted;
|
|
4204
4396
|
totalRowsSkipped += chunkResult.rowsSkipped;
|
|
4205
|
-
updateMapProgress({
|
|
4397
|
+
await updateMapProgress({
|
|
4206
4398
|
completed: totalRowsWritten,
|
|
4207
4399
|
total: rowCountHint ?? undefined,
|
|
4208
4400
|
message: formatMapProgressMessage(
|
|
@@ -4266,14 +4458,31 @@ function createMinimalWorkerCtx(
|
|
|
4266
4458
|
program.steps.map((step) => [step.name, step.resolver]),
|
|
4267
4459
|
);
|
|
4268
4460
|
const cellPolicies = Object.fromEntries(
|
|
4461
|
+
program.steps.map((step) => [
|
|
4462
|
+
step.name,
|
|
4463
|
+
step.staleAfterSeconds === undefined
|
|
4464
|
+
? {}
|
|
4465
|
+
: normalizeCellStalenessPolicy({
|
|
4466
|
+
staleAfterSeconds: step.staleAfterSeconds,
|
|
4467
|
+
}),
|
|
4468
|
+
]),
|
|
4469
|
+
) as CellStalenessPolicyByField;
|
|
4470
|
+
const authoredCellPolicies = Object.fromEntries(
|
|
4269
4471
|
program.steps.map((step) => [
|
|
4270
4472
|
step.name,
|
|
4271
4473
|
step.staleAfterSeconds === undefined
|
|
4272
4474
|
? {}
|
|
4273
4475
|
: { staleAfterSeconds: step.staleAfterSeconds },
|
|
4274
4476
|
]),
|
|
4477
|
+
) as AuthoredCellStalenessPolicyByField;
|
|
4478
|
+
return runMap(
|
|
4479
|
+
this.name,
|
|
4480
|
+
this.rows,
|
|
4481
|
+
fields,
|
|
4482
|
+
cellPolicies,
|
|
4483
|
+
authoredCellPolicies,
|
|
4484
|
+
opts,
|
|
4275
4485
|
);
|
|
4276
|
-
return runMap(this.name, this.rows, fields, cellPolicies, opts);
|
|
4277
4486
|
},
|
|
4278
4487
|
{
|
|
4279
4488
|
emptyColumnName:
|
|
@@ -4288,7 +4497,9 @@ function createMinimalWorkerCtx(
|
|
|
4288
4497
|
|
|
4289
4498
|
withColumn(
|
|
4290
4499
|
name: string,
|
|
4291
|
-
resolver:
|
|
4500
|
+
resolver: StepProgramDatasetColumnInput<
|
|
4501
|
+
WorkerStepProgramStep['resolver']
|
|
4502
|
+
>,
|
|
4292
4503
|
options?: StepProgramDatasetOptions,
|
|
4293
4504
|
): this {
|
|
4294
4505
|
this.builder.withColumn(name, resolver, options);
|
|
@@ -4492,14 +4703,31 @@ function createMinimalWorkerCtx(
|
|
|
4492
4703
|
fieldsDef.steps.map((step) => [step.name, step.resolver]),
|
|
4493
4704
|
);
|
|
4494
4705
|
const cellPolicies = Object.fromEntries(
|
|
4706
|
+
fieldsDef.steps.map((step) => [
|
|
4707
|
+
step.name,
|
|
4708
|
+
step.staleAfterSeconds === undefined
|
|
4709
|
+
? {}
|
|
4710
|
+
: normalizeCellStalenessPolicy({
|
|
4711
|
+
staleAfterSeconds: step.staleAfterSeconds,
|
|
4712
|
+
}),
|
|
4713
|
+
]),
|
|
4714
|
+
) as CellStalenessPolicyByField;
|
|
4715
|
+
const authoredCellPolicies = Object.fromEntries(
|
|
4495
4716
|
fieldsDef.steps.map((step) => [
|
|
4496
4717
|
step.name,
|
|
4497
4718
|
step.staleAfterSeconds === undefined
|
|
4498
4719
|
? {}
|
|
4499
4720
|
: { staleAfterSeconds: step.staleAfterSeconds },
|
|
4500
4721
|
]),
|
|
4722
|
+
) as AuthoredCellStalenessPolicyByField;
|
|
4723
|
+
return runMap(
|
|
4724
|
+
name,
|
|
4725
|
+
rows,
|
|
4726
|
+
fields,
|
|
4727
|
+
cellPolicies,
|
|
4728
|
+
authoredCellPolicies,
|
|
4729
|
+
opts,
|
|
4501
4730
|
);
|
|
4502
|
-
return runMap(name, rows, fields, cellPolicies, opts);
|
|
4503
4731
|
}
|
|
4504
4732
|
throw new Error(
|
|
4505
4733
|
'ctx.dataset(key, rows, fields, options) is not supported. Use ctx.dataset(key, rows).withColumn(...).run(options).',
|
|
@@ -5277,6 +5505,10 @@ async function executeRunRequest(
|
|
|
5277
5505
|
];
|
|
5278
5506
|
let lastLedgerFlushAt = startedAt;
|
|
5279
5507
|
let ledgerFlushInFlight: Promise<void> = Promise.resolve();
|
|
5508
|
+
let ledgerFlushQueueDepth = 0;
|
|
5509
|
+
let lastCoordinatorProgressPublishAt = 0;
|
|
5510
|
+
let coordinatorProgressPublishInFlight: Promise<void> = Promise.resolve();
|
|
5511
|
+
let coordinatorProgressPublishQueueDepth = 0;
|
|
5280
5512
|
|
|
5281
5513
|
const appendRunLogLine = (line: string) => {
|
|
5282
5514
|
const trimmed = redactSecretsFromLogString(line.trim());
|
|
@@ -5399,6 +5631,36 @@ async function executeRunRequest(
|
|
|
5399
5631
|
});
|
|
5400
5632
|
};
|
|
5401
5633
|
|
|
5634
|
+
const flushCoordinatorProgressEvent = (force: boolean): Promise<void> => {
|
|
5635
|
+
const now = nowMs();
|
|
5636
|
+
if (
|
|
5637
|
+
!force &&
|
|
5638
|
+
now - lastCoordinatorProgressPublishAt <
|
|
5639
|
+
MAP_EXECUTION_HEARTBEAT_INTERVAL_MS
|
|
5640
|
+
) {
|
|
5641
|
+
return Promise.resolve();
|
|
5642
|
+
}
|
|
5643
|
+
if (!force && coordinatorProgressPublishQueueDepth > 0) {
|
|
5644
|
+
return Promise.resolve();
|
|
5645
|
+
}
|
|
5646
|
+
lastCoordinatorProgressPublishAt = now;
|
|
5647
|
+
coordinatorProgressPublishQueueDepth += 1;
|
|
5648
|
+
coordinatorProgressPublishInFlight = coordinatorProgressPublishInFlight
|
|
5649
|
+
.catch(() => undefined)
|
|
5650
|
+
.then(async () => {
|
|
5651
|
+
try {
|
|
5652
|
+
await publishCoordinatorProgressEvent(now);
|
|
5653
|
+
} finally {
|
|
5654
|
+
coordinatorProgressPublishQueueDepth = Math.max(
|
|
5655
|
+
0,
|
|
5656
|
+
coordinatorProgressPublishQueueDepth - 1,
|
|
5657
|
+
);
|
|
5658
|
+
}
|
|
5659
|
+
})
|
|
5660
|
+
.catch(() => undefined);
|
|
5661
|
+
return force ? coordinatorProgressPublishInFlight : Promise.resolve();
|
|
5662
|
+
};
|
|
5663
|
+
|
|
5402
5664
|
const appendStepLifecycleEvent = (event: PlayStepLifecycleEvent) => {
|
|
5403
5665
|
updateStepProgress({
|
|
5404
5666
|
nodeId: event.nodeId,
|
|
@@ -5498,15 +5760,19 @@ async function executeRunRequest(
|
|
|
5498
5760
|
return events;
|
|
5499
5761
|
};
|
|
5500
5762
|
|
|
5501
|
-
const flushLedgerEvents = (force: boolean): void => {
|
|
5502
|
-
if (!options?.persistResultDatasets) return;
|
|
5763
|
+
const flushLedgerEvents = (force: boolean): Promise<void> => {
|
|
5764
|
+
if (!options?.persistResultDatasets) return Promise.resolve();
|
|
5503
5765
|
const now = nowMs();
|
|
5504
5766
|
if (!force && now - lastLedgerFlushAt < RUN_LEDGER_FLUSH_INTERVAL_MS) {
|
|
5505
|
-
return;
|
|
5767
|
+
return Promise.resolve();
|
|
5768
|
+
}
|
|
5769
|
+
if (!force && ledgerFlushQueueDepth > 0) {
|
|
5770
|
+
return Promise.resolve();
|
|
5506
5771
|
}
|
|
5507
5772
|
const events = drainPendingLedgerEvents(now);
|
|
5508
|
-
if (events.length === 0) return;
|
|
5773
|
+
if (events.length === 0) return Promise.resolve();
|
|
5509
5774
|
lastLedgerFlushAt = now;
|
|
5775
|
+
ledgerFlushQueueDepth += 1;
|
|
5510
5776
|
ledgerFlushInFlight = ledgerFlushInFlight
|
|
5511
5777
|
.catch(() => undefined)
|
|
5512
5778
|
.then(async () => {
|
|
@@ -5519,10 +5785,12 @@ async function executeRunRequest(
|
|
|
5519
5785
|
} catch {
|
|
5520
5786
|
pendingLedgerEvents = [...events, ...pendingLedgerEvents];
|
|
5521
5787
|
throw new Error('runtime run-ledger append failed');
|
|
5788
|
+
} finally {
|
|
5789
|
+
ledgerFlushQueueDepth = Math.max(0, ledgerFlushQueueDepth - 1);
|
|
5522
5790
|
}
|
|
5523
|
-
await publishCoordinatorProgressEvent(now).catch(() => undefined);
|
|
5524
5791
|
})
|
|
5525
5792
|
.catch(() => undefined);
|
|
5793
|
+
return force ? ledgerFlushInFlight : Promise.resolve();
|
|
5526
5794
|
};
|
|
5527
5795
|
|
|
5528
5796
|
const flushTerminalLedgerEvents = async (
|
|
@@ -5564,7 +5832,12 @@ async function executeRunRequest(
|
|
|
5564
5832
|
const workerCallbacks: WorkerCtxCallbacks = {
|
|
5565
5833
|
onNodeProgress: (input) => {
|
|
5566
5834
|
updateStepProgress(input);
|
|
5567
|
-
|
|
5835
|
+
const force = Boolean(input.forceFlush);
|
|
5836
|
+
const ledgerFlush = flushLedgerEvents(force);
|
|
5837
|
+
const progressFlush = flushCoordinatorProgressEvent(force);
|
|
5838
|
+
return force
|
|
5839
|
+
? Promise.all([ledgerFlush, progressFlush]).then(() => undefined)
|
|
5840
|
+
: Promise.resolve();
|
|
5568
5841
|
},
|
|
5569
5842
|
onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
|
|
5570
5843
|
onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
|