@indigoai-us/hq-cloud 6.18.35 → 6.18.38-alpha.0
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/bin/push-failure-backoff.d.ts +22 -0
- package/dist/bin/push-failure-backoff.d.ts.map +1 -0
- package/dist/bin/push-failure-backoff.js +27 -0
- package/dist/bin/push-failure-backoff.js.map +1 -0
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js +9 -2
- package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js.map +1 -1
- package/dist/bin/sync-runner-delete-snapshot.bench.test.js +3 -1
- package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +165 -34
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.test.js +46 -0
- package/dist/bin/sync-runner-watch-loop.test.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +11 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +23 -4
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +956 -4
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/lib/describe-error.d.ts +4 -0
- package/dist/lib/describe-error.d.ts.map +1 -1
- package/dist/lib/describe-error.js +37 -0
- package/dist/lib/describe-error.js.map +1 -1
- package/dist/lib/describe-error.test.js +18 -1
- package/dist/lib/describe-error.test.js.map +1 -1
- package/dist/outcome-telemetry.d.ts +16 -6
- package/dist/outcome-telemetry.d.ts.map +1 -1
- package/dist/outcome-telemetry.js +105 -11
- package/dist/outcome-telemetry.js.map +1 -1
- package/dist/outcome-telemetry.test.js +236 -0
- package/dist/outcome-telemetry.test.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +5 -0
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +115 -52
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +349 -2
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,8 @@ import { targetedJournalSessionCensusSizes } from "../cli/sync.js";
|
|
|
9
9
|
import { VaultClient } from "../index.js";
|
|
10
10
|
import { pickCanonicalPersonEntity } from "../vault-client.js";
|
|
11
11
|
import { acquireOperationLock, withOperationLock, withOperationLockSync, OperationLockedError, OPERATION_LOCKED_EXIT, } from "../operation-lock.js";
|
|
12
|
-
import { describeError } from "../lib/describe-error.js";
|
|
12
|
+
import { describeError, pushFailureTelemetryLabel, sanitizePushFailureText, } from "../lib/describe-error.js";
|
|
13
|
+
import { RoutePushFailureBackoff, systemPushRetryScheduler, } from "./push-failure-backoff.js";
|
|
13
14
|
import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
|
|
14
15
|
import { PARTIAL_SYNC_EXIT, UNRESOLVED_COMPANY_EXIT, } from "../lib/exit-codes.js";
|
|
15
16
|
import { getOrCreateMachineIdMetadata } from "../lib/machine-id.js";
|
|
@@ -889,6 +890,7 @@ export function stripTransientWatchScopeArgs(argv) {
|
|
|
889
890
|
const valueFlags = new Set([
|
|
890
891
|
"--poll-remote-ms",
|
|
891
892
|
"--owner",
|
|
893
|
+
"--watch-parent-pid",
|
|
892
894
|
"--scope-path",
|
|
893
895
|
"--scope-upsert-path",
|
|
894
896
|
"--delete-scope-path",
|
|
@@ -1690,7 +1692,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
1690
1692
|
refresh.unref?.();
|
|
1691
1693
|
}
|
|
1692
1694
|
const passScheduler = new CooperativePassScheduler(monotonicNow, CHORE_STARVATION_CAP_MS, { caps: fullPassCaps });
|
|
1693
|
-
const runPassWithLock = async (passArgvForRun, prepare, lane = "slow", workClass = "chore", checkpoint, telemetry) => {
|
|
1695
|
+
const runPassWithLock = async (passArgvForRun, prepare, lane = "slow", workClass = "chore", checkpoint, telemetry, pullSignal, receiverSignal) => {
|
|
1696
|
+
const passCheckpoint = pullSignal || receiverSignal
|
|
1697
|
+
? Object.assign(async () => checkpoint?.(), {
|
|
1698
|
+
shouldYieldToRealtime: checkpoint?.shouldYieldToRealtime,
|
|
1699
|
+
shouldAbort: () => pullSignal?.aborted === true ||
|
|
1700
|
+
receiverSignal?.aborted === true ||
|
|
1701
|
+
checkpoint?.shouldAbort?.() === true,
|
|
1702
|
+
runChoreNetworkTransfer: checkpoint?.runChoreNetworkTransfer,
|
|
1703
|
+
})
|
|
1704
|
+
: checkpoint;
|
|
1694
1705
|
const reportedAppliedUpserts = new Set();
|
|
1695
1706
|
const reportedAppliedDeletes = new Set();
|
|
1696
1707
|
const recordDurableAppliedUpserts = (upserts) => {
|
|
@@ -1722,9 +1733,9 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
1722
1733
|
// existing scheduler's cooperative hand-off checkpoint.
|
|
1723
1734
|
const onJournalsReady = () => activateEventPushSurfaces();
|
|
1724
1735
|
if (!runPassSupportsQmdLifecycle) {
|
|
1725
|
-
return runPass(passArgvForRun,
|
|
1736
|
+
return runPass(passArgvForRun, passCheckpoint, onJournalsReady, recordDurableAppliedUpserts, workClass);
|
|
1726
1737
|
}
|
|
1727
|
-
return runPass(passArgvForRun,
|
|
1738
|
+
return runPass(passArgvForRun, passCheckpoint, onJournalsReady, recordDurableAppliedUpserts, workClass, qmdShutdownController.signal, writeMaintenanceProgress, idleHeartbeatIntervalMs, recordDurableAppliedDeletes);
|
|
1728
1739
|
};
|
|
1729
1740
|
// Observe the heap at pass boundaries — a gated checkpoint immediately before
|
|
1730
1741
|
// admitting a pass and immediately after it returns. A pass that runs a long
|
|
@@ -1987,13 +1998,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
1987
1998
|
// chore ownership. Realtime normally waits for a chore hand-off; the
|
|
1988
1999
|
// watcher fast path explicitly opts into its established scoped exception.
|
|
1989
2000
|
passScheduler.submit(task, workClass, lane, allowFastRealtimeOvertake, signal, resourceCap, allowConcurrentHandoff, transferDirection, transferSizeClass);
|
|
1990
|
-
const runGuarded = (passArgvForRun, prepare, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false, signal, resourceCap = false, telemetry, allowConcurrentHandoff = false) => {
|
|
2001
|
+
const runGuarded = (passArgvForRun, prepare, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false, signal, resourceCap = false, telemetry, allowConcurrentHandoff = false, receiverSignal) => {
|
|
1991
2002
|
telemetry?.mark("operationGate");
|
|
2003
|
+
const admissionSignal = signal && receiverSignal
|
|
2004
|
+
? AbortSignal.any([signal, receiverSignal])
|
|
2005
|
+
: signal ?? receiverSignal;
|
|
1992
2006
|
return runGuardedTask((context) => {
|
|
1993
2007
|
telemetry?.mark("appendLock");
|
|
1994
2008
|
telemetry?.setOccupancy(passScheduler.activeRealtimeWorkerCount);
|
|
1995
|
-
return runPassWithLock(passArgvForRun, prepare, lane, workClass, workClass === "chore" ? context.checkpoint : undefined, telemetry);
|
|
1996
|
-
}, lane, workClass, allowFastRealtimeOvertake,
|
|
2009
|
+
return runPassWithLock(passArgvForRun, prepare, lane, workClass, workClass === "chore" ? context.checkpoint : undefined, telemetry, context.signal, receiverSignal);
|
|
2010
|
+
}, lane, workClass, allowFastRealtimeOvertake, admissionSignal, resourceCap, allowConcurrentHandoff, telemetry?.direction, telemetry?.sizeClass);
|
|
1997
2011
|
};
|
|
1998
2012
|
/*
|
|
1999
2013
|
* Area storage and the parallel coordinator default to AUTO. AUTO turns
|
|
@@ -2465,15 +2479,43 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
2465
2479
|
pendingWatcherBatchGeneration = null;
|
|
2466
2480
|
return { batch, bare, warmupCatchup, warmupCatchupFailed, generation };
|
|
2467
2481
|
};
|
|
2468
|
-
const restorePendingWatcherPaths = (paths, generation) => {
|
|
2482
|
+
const restorePendingWatcherPaths = (paths, generation, changes) => {
|
|
2469
2483
|
pendingWatcherBatchGeneration ??=
|
|
2470
2484
|
generation ?? ++nextWatcherBatchGeneration;
|
|
2471
2485
|
pendingWatcherOriginalBatch = null;
|
|
2472
2486
|
for (const [absolutePath, relativePath] of paths.entries()) {
|
|
2487
|
+
// A newer watcher event may already be queued for this path. Keep its
|
|
2488
|
+
// change record instead of restoring stale delete metadata from the
|
|
2489
|
+
// batch that just finished.
|
|
2490
|
+
if (pendingWatcherChangeSequences.has(absolutePath))
|
|
2491
|
+
continue;
|
|
2473
2492
|
pendingWatcherPaths.set(absolutePath, relativePath);
|
|
2474
2493
|
pendingWatcherChangeSequences.set(absolutePath, ++nextPendingWatcherChangeSequence);
|
|
2494
|
+
const change = changes?.get(absolutePath);
|
|
2495
|
+
if (change)
|
|
2496
|
+
pendingWatcherChanges.set(absolutePath, change);
|
|
2475
2497
|
}
|
|
2476
2498
|
};
|
|
2499
|
+
const routePushFailureBackoff = new RoutePushFailureBackoff();
|
|
2500
|
+
const pushRetryScheduler = deps.pushRetryScheduler ?? systemPushRetryScheduler;
|
|
2501
|
+
const routePushRetryTimerHandles = new Map();
|
|
2502
|
+
const clearRoutePushRetry = (route) => {
|
|
2503
|
+
const handle = routePushRetryTimerHandles.get(route);
|
|
2504
|
+
if (handle === undefined)
|
|
2505
|
+
return;
|
|
2506
|
+
pushRetryScheduler.clearTimeout(handle);
|
|
2507
|
+
routePushRetryTimerHandles.delete(route);
|
|
2508
|
+
};
|
|
2509
|
+
const scheduleRoutePushRetry = (route, delayMs) => {
|
|
2510
|
+
if (routePushRetryTimerHandles.has(route))
|
|
2511
|
+
return;
|
|
2512
|
+
const handle = pushRetryScheduler.setTimeout(() => {
|
|
2513
|
+
routePushRetryTimerHandles.delete(route);
|
|
2514
|
+
if (!stopped)
|
|
2515
|
+
interruptWait();
|
|
2516
|
+
}, delayMs);
|
|
2517
|
+
routePushRetryTimerHandles.set(route, handle);
|
|
2518
|
+
};
|
|
2477
2519
|
/**
|
|
2478
2520
|
* Journal roots the bulk-intent quarantine has refused for the current
|
|
2479
2521
|
* delete episode, keyed `slug\0journalKey`.
|
|
@@ -3332,7 +3374,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3332
3374
|
}
|
|
3333
3375
|
return false;
|
|
3334
3376
|
};
|
|
3335
|
-
|
|
3377
|
+
let emitScopedPushFailureTelemetry = null;
|
|
3378
|
+
const runScopedDrain = async (batch, watcherBatchGeneration, _wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
|
|
3336
3379
|
if (batch)
|
|
3337
3380
|
await resolveDeleteSnapshotCaptures(batch);
|
|
3338
3381
|
const grouped = batch && batch.paths.size > 0 ? groupBatchByRoute(batch) : null;
|
|
@@ -3343,6 +3386,52 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3343
3386
|
const relativePath = group.paths.get(absolutePath);
|
|
3344
3387
|
return relativePath !== undefined && admittedPaths.some((scope) => relativePath === scope || relativePath.startsWith(`${scope}/`));
|
|
3345
3388
|
}));
|
|
3389
|
+
const pushFailureText = (result, route, caughtError) => {
|
|
3390
|
+
if (caughtError !== undefined)
|
|
3391
|
+
return describeError(caughtError);
|
|
3392
|
+
if (typeof result === "number")
|
|
3393
|
+
return `sync pass exited ${result}`;
|
|
3394
|
+
const failures = [
|
|
3395
|
+
...(result.result?.companyFailures ?? []),
|
|
3396
|
+
...(result.result?.errors ?? []),
|
|
3397
|
+
...(result.result?.transient ?? []),
|
|
3398
|
+
];
|
|
3399
|
+
const routeFailure = route.kind === "company"
|
|
3400
|
+
? failures.find((failure) => failure.company.toLowerCase() === route.slug.toLowerCase())
|
|
3401
|
+
: undefined;
|
|
3402
|
+
return routeFailure?.message ?? failures[0]?.message ??
|
|
3403
|
+
`sync pass exited ${result.exitCode}`;
|
|
3404
|
+
};
|
|
3405
|
+
const recordPushFailure = (route, exitCode, message) => {
|
|
3406
|
+
const routeId = routeKey(route);
|
|
3407
|
+
const companyRoot = route.kind === "company"
|
|
3408
|
+
? path.join(hqRoot, "companies", route.slug)
|
|
3409
|
+
: hqRoot;
|
|
3410
|
+
const errorText = sanitizePushFailureText(message, companyRoot);
|
|
3411
|
+
const state = routePushFailureBackoff.failed(routeId, monotonicNow());
|
|
3412
|
+
scheduleRoutePushRetry(routeId, state.delayMs);
|
|
3413
|
+
if (!state.shouldReport)
|
|
3414
|
+
return;
|
|
3415
|
+
const event = {
|
|
3416
|
+
route: routeId,
|
|
3417
|
+
exitCode,
|
|
3418
|
+
consecutiveFailures: state.consecutiveFailures,
|
|
3419
|
+
nextRetryDelayMs: state.delayMs,
|
|
3420
|
+
errorSummary: pushFailureTelemetryLabel(errorText),
|
|
3421
|
+
};
|
|
3422
|
+
const diagnostic = {
|
|
3423
|
+
type: "scoped-push-failure",
|
|
3424
|
+
...event,
|
|
3425
|
+
error: errorText,
|
|
3426
|
+
};
|
|
3427
|
+
(deps.stderr ?? process.stderr).write(`${JSON.stringify(diagnostic)}\n`);
|
|
3428
|
+
if (deps.emitScopedPushFailureTelemetry) {
|
|
3429
|
+
deps.emitScopedPushFailureTelemetry(event);
|
|
3430
|
+
}
|
|
3431
|
+
else {
|
|
3432
|
+
emitScopedPushFailureTelemetry?.(event);
|
|
3433
|
+
}
|
|
3434
|
+
};
|
|
3346
3435
|
if (watcherDrainRunsPush && grouped) {
|
|
3347
3436
|
for (const group of grouped.values()) {
|
|
3348
3437
|
// Paused workspaces: drop the scoped push without uploading or
|
|
@@ -3350,6 +3439,14 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3350
3439
|
if (isPausedRoute(group.route)) {
|
|
3351
3440
|
continue;
|
|
3352
3441
|
}
|
|
3442
|
+
const routeId = routeKey(group.route);
|
|
3443
|
+
const retryState = routePushFailureBackoff.state(routeId);
|
|
3444
|
+
if (retryState && retryState.retryAt > monotonicNow()) {
|
|
3445
|
+
scheduleRoutePushRetry(routeId, retryState.retryAt - monotonicNow());
|
|
3446
|
+
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
clearRoutePushRetry(routeId);
|
|
3353
3450
|
try {
|
|
3354
3451
|
const sessionLogBackup = group.relPaths.every(isSessionLogsPushOnlyPath);
|
|
3355
3452
|
const lane = sessionLogBackup
|
|
@@ -3384,6 +3481,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3384
3481
|
rememberUploadedPaths(result);
|
|
3385
3482
|
const pushExit = passExitCode(result);
|
|
3386
3483
|
if (execution.accepted && pushExit === 0) {
|
|
3484
|
+
routePushFailureBackoff.succeeded(routeId);
|
|
3485
|
+
clearRoutePushRetry(routeId);
|
|
3387
3486
|
const pushPathResults = typeof result === "number" ? [] : (result.result?.pushPathResults ?? []);
|
|
3388
3487
|
const refusedPaths = new Set(pushPathResults
|
|
3389
3488
|
.filter((pathResult) => pathResult.status === "refused")
|
|
@@ -3431,21 +3530,25 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3431
3530
|
}
|
|
3432
3531
|
}
|
|
3433
3532
|
else if (pushExit === UNRESOLVED_COMPANY_EXIT) {
|
|
3533
|
+
routePushFailureBackoff.succeeded(routeId);
|
|
3534
|
+
clearRoutePushRetry(routeId);
|
|
3434
3535
|
if (group.route.kind === "company") {
|
|
3435
3536
|
logDroppedUnresolvedWatcherRoute(group.route.slug);
|
|
3436
3537
|
}
|
|
3437
3538
|
}
|
|
3438
3539
|
else {
|
|
3439
|
-
restorePendingWatcherPaths(group.paths, watcherBatchGeneration);
|
|
3440
|
-
if (
|
|
3540
|
+
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
3541
|
+
if (pushExit !== 0) {
|
|
3542
|
+
recordPushFailure(group.route, pushExit, pushFailureText(result, group.route));
|
|
3543
|
+
}
|
|
3544
|
+
else if (_wakeOnPushFailure) {
|
|
3441
3545
|
interruptWait();
|
|
3546
|
+
}
|
|
3442
3547
|
}
|
|
3443
3548
|
}
|
|
3444
3549
|
catch (err) {
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
if (wakeOnPushFailure)
|
|
3448
|
-
interruptWait();
|
|
3550
|
+
recordPushFailure(group.route, 1, pushFailureText(1, group.route, err));
|
|
3551
|
+
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
3449
3552
|
}
|
|
3450
3553
|
}
|
|
3451
3554
|
}
|
|
@@ -3534,6 +3637,11 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3534
3637
|
if (watcherDrainRunsPush) {
|
|
3535
3638
|
const push = await runGuarded(buildScopedPushArgv(route, [], passArgv));
|
|
3536
3639
|
const pushExit = scopedRouteDrainExit({ result: push, accepted: true });
|
|
3640
|
+
if (pushExit === 0) {
|
|
3641
|
+
const routeId = routeKey(route);
|
|
3642
|
+
routePushFailureBackoff.succeeded(routeId);
|
|
3643
|
+
clearRoutePushRetry(routeId);
|
|
3644
|
+
}
|
|
3537
3645
|
if (pushExit !== 0)
|
|
3538
3646
|
return push;
|
|
3539
3647
|
}
|
|
@@ -3562,10 +3670,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3562
3670
|
if (watcherDrainRunsPush) {
|
|
3563
3671
|
// Whole-route push (no --scope-path): the dropped paths are unknown,
|
|
3564
3672
|
// so the route's full walk is the narrowest sound scope.
|
|
3673
|
+
const push = await runGuarded(buildScopedPushArgv(route, [], passArgv));
|
|
3565
3674
|
const exit = scopedRouteDrainExit({
|
|
3566
|
-
result:
|
|
3675
|
+
result: push,
|
|
3567
3676
|
accepted: true,
|
|
3568
3677
|
});
|
|
3678
|
+
if (exit === 0) {
|
|
3679
|
+
const routeId = routeKey(route);
|
|
3680
|
+
routePushFailureBackoff.succeeded(routeId);
|
|
3681
|
+
clearRoutePushRetry(routeId);
|
|
3682
|
+
}
|
|
3569
3683
|
if (exit !== 0 && worstExit === 0)
|
|
3570
3684
|
worstExit = exit;
|
|
3571
3685
|
}
|
|
@@ -3631,6 +3745,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3631
3745
|
authToken: getAccessToken,
|
|
3632
3746
|
region: runtime.region,
|
|
3633
3747
|
});
|
|
3748
|
+
emitScopedPushFailureTelemetry = (event) => {
|
|
3749
|
+
void emitCloudTelemetry(watcherTelemetryClient, {
|
|
3750
|
+
eventName: "sync_runner_scoped_push_failed",
|
|
3751
|
+
source: "sync_runner_watch_loop",
|
|
3752
|
+
properties: event,
|
|
3753
|
+
}, {
|
|
3754
|
+
claims,
|
|
3755
|
+
log: (message) => (deps.stderr ?? process.stderr).write(`hq-sync-runner: ${message}\n`),
|
|
3756
|
+
});
|
|
3757
|
+
};
|
|
3634
3758
|
const createWatcher = deps.createWatcher ??
|
|
3635
3759
|
((opts) => new TreeWatcher({
|
|
3636
3760
|
hqRoot: opts.hqRoot,
|
|
@@ -3862,7 +3986,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3862
3986
|
// a known-small event can be slow solely because an earlier event exhausted
|
|
3863
3987
|
// the delivery-wide fast budget.
|
|
3864
3988
|
const receiverLaneByBatch = new WeakMap();
|
|
3865
|
-
const drainParallelReceiverLane = async (lane, laneEvents, receiveCountsByPath) => {
|
|
3989
|
+
const drainParallelReceiverLane = async (lane, laneEvents, receiveCountsByPath, settleEvents, receiverSignal) => {
|
|
3866
3990
|
const eventsByRoute = new Map();
|
|
3867
3991
|
for (const event of laneEvents) {
|
|
3868
3992
|
const route = routeChangeToTarget(event.relativePath);
|
|
@@ -3877,15 +4001,12 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3877
4001
|
// particular, a later route's fast receipt must never wait for an
|
|
3878
4002
|
// earlier route's slow pull to finish.
|
|
3879
4003
|
const routeGroups = [...eventsByRoute.values()];
|
|
3880
|
-
const
|
|
3881
|
-
|
|
4004
|
+
const settlements = await Promise.allSettled(routeGroups.map(async ({ route, events }) => {
|
|
4005
|
+
const execution = await runRealtimePass("receiver", "pull", events.map((event) => event.relativePath), lane, (slice, telemetry, coordinatorFenced) => {
|
|
3882
4006
|
const slicePaths = slice.paths.map((path) => path.canonicalPath);
|
|
3883
4007
|
const exactFileUpserts = exactUpsertPathsForCoordinatorSlice(events, slicePaths);
|
|
3884
|
-
return withScopeContentHashesFile(route, events, slicePaths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, slicePaths, exactFileUpserts, contentHashesFile), undefined, lane, "realtime", lane === "fast", slice.signal, false, telemetry, coordinatorFenced));
|
|
4008
|
+
return withScopeContentHashesFile(route, events, slicePaths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, slicePaths, exactFileUpserts, contentHashesFile), undefined, lane, "realtime", lane === "fast", slice.signal, false, telemetry, coordinatorFenced, receiverSignal));
|
|
3885
4009
|
});
|
|
3886
|
-
}));
|
|
3887
|
-
for (const [index, execution] of completed.entries()) {
|
|
3888
|
-
const { route, events } = routeGroups[index];
|
|
3889
4010
|
const pathCount = events.length;
|
|
3890
4011
|
const exitCode = passExitCode(execution.result);
|
|
3891
4012
|
if (shouldAcknowledgeParallelUnresolvedCompany(execution.accepted, exitCode)) {
|
|
@@ -3893,14 +4014,19 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3893
4014
|
throw targetedReceiverDispatchError(execution.result, execution.accepted, runtime.authRequiredPassExit);
|
|
3894
4015
|
}
|
|
3895
4016
|
warnUnresolvedReceiverCompany(route, pathCount);
|
|
3896
|
-
continue;
|
|
3897
4017
|
}
|
|
3898
|
-
if (!execution.accepted || exitCode !== 0) {
|
|
4018
|
+
else if (!execution.accepted || exitCode !== 0) {
|
|
3899
4019
|
throw targetedReceiverDispatchError(execution.result, execution.accepted, runtime.authRequiredPassExit);
|
|
3900
4020
|
}
|
|
3901
|
-
|
|
4021
|
+
// The receiver can acknowledge this tenant's receipts now, while
|
|
4022
|
+
// independent route groups continue to settle.
|
|
4023
|
+
await settleEvents?.(events);
|
|
4024
|
+
}));
|
|
4025
|
+
const failedSettlement = settlements.find((settlement) => settlement.status === "rejected");
|
|
4026
|
+
if (failedSettlement)
|
|
4027
|
+
throw failedSettlement.reason;
|
|
3902
4028
|
};
|
|
3903
|
-
const receiverSyncBatchFn = async ({ events, approximateReceiveCountsByPath, }) => {
|
|
4029
|
+
const receiverSyncBatchFn = async ({ events, signal, approximateReceiveCountsByPath, settleEvents, }) => {
|
|
3904
4030
|
if (stopped)
|
|
3905
4031
|
return;
|
|
3906
4032
|
// The injectable receiver seam and the production SQS receiver both
|
|
@@ -3918,19 +4044,19 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3918
4044
|
const lane = receiverLaneByBatch.get(events);
|
|
3919
4045
|
if (lane) {
|
|
3920
4046
|
receiverLaneByBatch.delete(events);
|
|
3921
|
-
await drainParallelReceiverLane(lane, events, approximateReceiveCountsByPath);
|
|
4047
|
+
await drainParallelReceiverLane(lane, events, approximateReceiveCountsByPath, settleEvents, signal);
|
|
3922
4048
|
return;
|
|
3923
4049
|
}
|
|
3924
4050
|
// Direct embedders may invoke the engine without first going through
|
|
3925
4051
|
// SqsPushReceiver. Preserve that seam while applying the same
|
|
3926
4052
|
// delivery-wide partition the receiver would expose.
|
|
3927
4053
|
const lanes = partitionByKnownSizes(events, receiverEventSize);
|
|
3928
|
-
await drainParallelReceiverLane("fast", lanes.fast, approximateReceiveCountsByPath);
|
|
3929
|
-
await drainParallelReceiverLane("slow", lanes.slow, approximateReceiveCountsByPath);
|
|
4054
|
+
await drainParallelReceiverLane("fast", lanes.fast, approximateReceiveCountsByPath, settleEvents, signal);
|
|
4055
|
+
await drainParallelReceiverLane("slow", lanes.slow, approximateReceiveCountsByPath, settleEvents, signal);
|
|
3930
4056
|
return;
|
|
3931
4057
|
}
|
|
3932
4058
|
const lanes = partitionByKnownSizes(events, receiverEventSize);
|
|
3933
|
-
const drainLane = async (lane, laneEvents, receiveCountsByPath) => {
|
|
4059
|
+
const drainLane = async (lane, laneEvents, receiveCountsByPath, receiverSignal) => {
|
|
3934
4060
|
const eventsByRoute = new Map();
|
|
3935
4061
|
for (const event of laneEvents) {
|
|
3936
4062
|
const route = routeChangeToTarget(event.relativePath);
|
|
@@ -3949,26 +4075,28 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3949
4075
|
}
|
|
3950
4076
|
for (const { route, events: routeEvents, upsertPaths } of eventsByRoute.values()) {
|
|
3951
4077
|
const paths = routeEvents.map((event) => event.relativePath);
|
|
3952
|
-
const result = await withScopeContentHashesFile(route, routeEvents, paths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, paths, upsertPaths, contentHashesFile), undefined, lane, "realtime"));
|
|
4078
|
+
const result = await withScopeContentHashesFile(route, routeEvents, paths, (contentHashesFile) => runGuarded(buildTargetedPullArgv(route, passArgv, paths, upsertPaths, contentHashesFile), undefined, lane, "realtime", false, receiverSignal));
|
|
3953
4079
|
const exitCode = passExitCode(result);
|
|
3954
4080
|
if (exitCode === UNRESOLVED_COMPANY_EXIT) {
|
|
3955
4081
|
if (!unresolvedReceiverMessagesWereRedelivered(routeEvents, receiveCountsByPath)) {
|
|
3956
4082
|
throw targetedReceiverDispatchError(result, true, runtime.authRequiredPassExit);
|
|
3957
4083
|
}
|
|
3958
4084
|
warnUnresolvedReceiverCompany(route, routeEvents.length);
|
|
4085
|
+
await settleEvents?.(routeEvents);
|
|
3959
4086
|
continue;
|
|
3960
4087
|
}
|
|
3961
4088
|
if (exitCode !== 0) {
|
|
3962
4089
|
throw targetedReceiverDispatchError(result, true, runtime.authRequiredPassExit);
|
|
3963
4090
|
}
|
|
4091
|
+
await settleEvents?.(routeEvents);
|
|
3964
4092
|
}
|
|
3965
4093
|
};
|
|
3966
4094
|
// Dispatch realtime work before awaiting its bulk residue. This lets an
|
|
3967
4095
|
// established slow pass continue while the small scoped pull uses its
|
|
3968
4096
|
// ref-counted lock carve-out; the residue remains serialized and cannot
|
|
3969
4097
|
// starve the slow lane.
|
|
3970
|
-
await drainLane("fast", lanes.fast, approximateReceiveCountsByPath);
|
|
3971
|
-
await drainLane("slow", lanes.slow, approximateReceiveCountsByPath);
|
|
4098
|
+
await drainLane("fast", lanes.fast, approximateReceiveCountsByPath, signal);
|
|
4099
|
+
await drainLane("slow", lanes.slow, approximateReceiveCountsByPath, signal);
|
|
3972
4100
|
};
|
|
3973
4101
|
receiverSyncBatchFn.partition = (events, maxPaths) => {
|
|
3974
4102
|
const lanes = partitionByKnownSizes(events, receiverEventSize);
|
|
@@ -4110,6 +4238,9 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4110
4238
|
if (stopped)
|
|
4111
4239
|
return shutdownPromise ?? Promise.resolve();
|
|
4112
4240
|
stopped = true;
|
|
4241
|
+
for (const route of routePushRetryTimerHandles.keys()) {
|
|
4242
|
+
clearRoutePushRetry(route);
|
|
4243
|
+
}
|
|
4113
4244
|
qmdShutdownController.abort();
|
|
4114
4245
|
eventSyncShutdownDeadlineMs =
|
|
4115
4246
|
Date.now() + EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS;
|