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