@indigoai-us/hq-cloud 6.18.49 → 6.18.50-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/sync-runner-company.d.ts +5 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +24 -2
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts +85 -5
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +1062 -123
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +30 -11
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +133 -15
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/watcher-catchup-skip-store.d.ts +60 -0
- package/dist/bin/watcher-catchup-skip-store.d.ts.map +1 -0
- package/dist/bin/watcher-catchup-skip-store.js +282 -0
- package/dist/bin/watcher-catchup-skip-store.js.map +1 -0
- package/dist/cli/share.d.ts +12 -0
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +57 -4
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/sync.d.ts +3 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +27 -7
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/tombstones.d.ts.map +1 -1
- package/dist/cli/tombstones.js +7 -2
- package/dist/cli/tombstones.js.map +1 -1
- package/dist/ignore.d.ts +1 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +2 -2
- package/dist/ignore.js.map +1 -1
- package/dist/lib/cloud-authoritative.d.ts +9 -2
- package/dist/lib/cloud-authoritative.d.ts.map +1 -1
- package/dist/lib/cloud-authoritative.js +34 -2
- package/dist/lib/cloud-authoritative.js.map +1 -1
- package/dist/local-path-codec.d.ts.map +1 -1
- package/dist/local-path-codec.js +3 -2
- package/dist/local-path-codec.js.map +1 -1
- package/dist/personal-vault-exclusions.d.ts +2 -2
- package/dist/personal-vault-exclusions.d.ts.map +1 -1
- package/dist/personal-vault-exclusions.js +5 -3
- package/dist/personal-vault-exclusions.js.map +1 -1
- package/dist/personal-vault.d.ts +7 -0
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +10 -0
- package/dist/personal-vault.js.map +1 -1
- package/dist/symlink-target-policy.d.ts +11 -0
- package/dist/symlink-target-policy.d.ts.map +1 -1
- package/dist/symlink-target-policy.js +80 -0
- package/dist/symlink-target-policy.js.map +1 -1
- package/dist/sync/parallel-sync-coordinator.d.ts +29 -0
- package/dist/sync/parallel-sync-coordinator.d.ts.map +1 -1
- package/dist/sync/parallel-sync-coordinator.js +229 -16
- package/dist/sync/parallel-sync-coordinator.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 +32 -10
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/watcher.d.ts +16 -3
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +114 -21
- package/dist/watcher.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,86 @@ import fs from "node:fs";
|
|
|
4
4
|
export function personalSymlinkTargetLinkPath(relativePath) {
|
|
5
5
|
return relativePath;
|
|
6
6
|
}
|
|
7
|
+
/** Inspect a local link without relying on a previous push pass to resolve policy. */
|
|
8
|
+
export function inspectLocalSymlinkScope(input) {
|
|
9
|
+
let stat;
|
|
10
|
+
try {
|
|
11
|
+
stat = fs.lstatSync(input.absoluteLinkPath);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return { isSymlink: false, classification: "in-scope", refused: false, shape: "missing" };
|
|
15
|
+
}
|
|
16
|
+
if (!stat.isSymbolicLink()) {
|
|
17
|
+
return { isSymlink: false, classification: "in-scope", refused: false, shape: "regular" };
|
|
18
|
+
}
|
|
19
|
+
const refuse = (classification, shape) => ({
|
|
20
|
+
isSymlink: true,
|
|
21
|
+
classification,
|
|
22
|
+
refused: true,
|
|
23
|
+
shape,
|
|
24
|
+
});
|
|
25
|
+
let target;
|
|
26
|
+
try {
|
|
27
|
+
target = fs.readlinkSync(input.absoluteLinkPath);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return refuse("escapes-scope", "unreadable-link");
|
|
31
|
+
}
|
|
32
|
+
const targetIsAbsolute = path.isAbsolute(target) || path.win32.isAbsolute(target);
|
|
33
|
+
let lexicalClassification;
|
|
34
|
+
if (targetIsAbsolute && isLegacyAbsoluteKnowledgeLink(input.linkPath, target, input.scopeRoot, input.scope)) {
|
|
35
|
+
return { isSymlink: true, classification: "allowlisted-legacy", refused: false, shape: "allowlisted-legacy" };
|
|
36
|
+
}
|
|
37
|
+
if (!targetIsAbsolute) {
|
|
38
|
+
const lexical = inspectSymlinkTarget({
|
|
39
|
+
linkPath: input.linkPath,
|
|
40
|
+
target,
|
|
41
|
+
scope: input.scope,
|
|
42
|
+
refuse: true,
|
|
43
|
+
emit: () => undefined,
|
|
44
|
+
});
|
|
45
|
+
if (lexical.refused) {
|
|
46
|
+
return refuse(lexical.classification, redactShape(target, lexical.classification));
|
|
47
|
+
}
|
|
48
|
+
if (lexical.classification === "allowlisted-legacy") {
|
|
49
|
+
return { isSymlink: true, classification: lexical.classification, refused: false, shape: "allowlisted-legacy" };
|
|
50
|
+
}
|
|
51
|
+
lexicalClassification = lexical.classification;
|
|
52
|
+
}
|
|
53
|
+
let canonicalScope;
|
|
54
|
+
try {
|
|
55
|
+
canonicalScope = fs.realpathSync.native(input.scopeRoot);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return refuse("escapes-scope", "realpath-unavailable");
|
|
59
|
+
}
|
|
60
|
+
let canonicalTarget;
|
|
61
|
+
try {
|
|
62
|
+
canonicalTarget = fs.realpathSync.native(input.absoluteLinkPath);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error.code === "ENOENT" &&
|
|
66
|
+
lexicalClassification === "in-scope") {
|
|
67
|
+
return { isSymlink: true, classification: "in-scope", refused: false, shape: "in-scope-dangling" };
|
|
68
|
+
}
|
|
69
|
+
return refuse("escapes-scope", "realpath-unavailable");
|
|
70
|
+
}
|
|
71
|
+
const relative = path.relative(canonicalScope, canonicalTarget);
|
|
72
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
73
|
+
return refuse("escapes-scope", targetIsAbsolute ? "absolute-outside-scope" : "realpath-outside-scope");
|
|
74
|
+
}
|
|
75
|
+
return { isSymlink: true, classification: "in-scope", refused: false, shape: "in-scope" };
|
|
76
|
+
}
|
|
77
|
+
function isLegacyAbsoluteKnowledgeLink(linkPath, target, scopeRoot, scope) {
|
|
78
|
+
if (scope.kind !== "company")
|
|
79
|
+
return false;
|
|
80
|
+
const normalizedLink = normalizeVaultPath(linkPath);
|
|
81
|
+
if (normalizedLink !== `companies/${scope.companySlug}/knowledge`)
|
|
82
|
+
return false;
|
|
83
|
+
const hqRoot = path.resolve(scopeRoot, "..", "..");
|
|
84
|
+
const expectedTarget = path.resolve(hqRoot, "repos", "private", `knowledge-${scope.companySlug}`);
|
|
85
|
+
return path.resolve(target) === expectedTarget;
|
|
86
|
+
}
|
|
7
87
|
/** Compare a materialized path with the portable wire target across Windows aliases. */
|
|
8
88
|
export function materializedTargetMatchesWire(linkPath, localTarget, wireTarget, platform = process.platform) {
|
|
9
89
|
const paths = platform === "win32" ? path.win32 : path;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symlink-target-policy.js","sourceRoot":"","sources":["../src/symlink-target-policy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAazB,yEAAyE;AACzE,MAAM,UAAU,6BAA6B,CAAC,YAAoB;IAChE,OAAO,YAAY,CAAC;AACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"symlink-target-policy.js","sourceRoot":"","sources":["../src/symlink-target-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,27 @@ export type CoordinatorSnapshot = {
|
|
|
22
22
|
realtimeQueued: number;
|
|
23
23
|
choreQueued: number;
|
|
24
24
|
realtimeRunning: boolean;
|
|
25
|
+
realtimeLanes: {
|
|
26
|
+
fast: {
|
|
27
|
+
direction?: "push" | "pull";
|
|
28
|
+
source?: string;
|
|
29
|
+
paths: number;
|
|
30
|
+
} | null;
|
|
31
|
+
slow: {
|
|
32
|
+
direction?: "push" | "pull";
|
|
33
|
+
source?: string;
|
|
34
|
+
paths: number;
|
|
35
|
+
} | null;
|
|
36
|
+
overflowPush: {
|
|
37
|
+
direction?: "push" | "pull";
|
|
38
|
+
source?: string;
|
|
39
|
+
paths: number;
|
|
40
|
+
} | null;
|
|
41
|
+
};
|
|
25
42
|
choreRunning: boolean;
|
|
43
|
+
retainedPullYieldStates: number;
|
|
44
|
+
lastRealtimeAdmissionAt: number | null;
|
|
45
|
+
lastChoreAdmissionAt: number | null;
|
|
26
46
|
leases: ReturnType<PathLeaseManager["inspect"]>;
|
|
27
47
|
now: number;
|
|
28
48
|
};
|
|
@@ -48,12 +68,17 @@ export declare class DefaultParallelSyncCoordinator implements ParallelSyncCoord
|
|
|
48
68
|
private nextId;
|
|
49
69
|
private nextEpoch;
|
|
50
70
|
private readonly realtimeRunningByLane;
|
|
71
|
+
private realtimeOverflowPush?;
|
|
72
|
+
private readonly pullYieldStates;
|
|
73
|
+
private lastRealtimeAdmissionAt;
|
|
74
|
+
private lastChoreAdmissionAt;
|
|
51
75
|
private choreRunning?;
|
|
52
76
|
private stopping;
|
|
53
77
|
private shutdownPromise?;
|
|
54
78
|
constructor(options: ParallelSyncCoordinatorOptions);
|
|
55
79
|
submit(work: SyncWork): Promise<WorkOutcome>;
|
|
56
80
|
inspect(): CoordinatorSnapshot;
|
|
81
|
+
private snapshotRealtimeLane;
|
|
57
82
|
shutdown(): Promise<void>;
|
|
58
83
|
private createSubmission;
|
|
59
84
|
private pathsFor;
|
|
@@ -61,6 +86,8 @@ export declare class DefaultParallelSyncCoordinator implements ParallelSyncCoord
|
|
|
61
86
|
private hasInternallyConflictingPaths;
|
|
62
87
|
private preemptForRealtime;
|
|
63
88
|
private drain;
|
|
89
|
+
/** A single bounded push slot keeps disjoint watcher work moving under two held lanes. */
|
|
90
|
+
private startNextOverflowPush;
|
|
64
91
|
private startNext;
|
|
65
92
|
private requestsFor;
|
|
66
93
|
private execute;
|
|
@@ -68,6 +95,8 @@ export declare class DefaultParallelSyncCoordinator implements ParallelSyncCoord
|
|
|
68
95
|
private isStale;
|
|
69
96
|
private completeAfterWorkerFailure;
|
|
70
97
|
private setResult;
|
|
98
|
+
private expireInactivePullYieldStates;
|
|
99
|
+
private touchPullYieldStates;
|
|
71
100
|
private completeRemaining;
|
|
72
101
|
private finishIfEmpty;
|
|
73
102
|
private pruneEpochs;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parallel-sync-coordinator.d.ts","sourceRoot":"","sources":["../../src/sync/parallel-sync-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,gBAAgB,EAAwD,MAAM,kBAAkB,CAAC;AAC/H,OAAO,EACL,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,EAGlB,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAAE,GAAG,IAAI,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,8BAA8B,GAAG;IAC3C,4EAA4E;IAC5E,cAAc,EAAE,iBAAiB,CAAC;IAClC,sCAAsC;IACtC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,0EAA0E;IAC1E,YAAY,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,YAAY,CAAC;IACpD,0EAA0E;IAC1E,cAAc,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,aAAa,EAAE,CAAC;IAClE,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;IAChD,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,OAAO,IAAI,mBAAmB,CAAC;IAC/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"parallel-sync-coordinator.d.ts","sourceRoot":"","sources":["../../src/sync/parallel-sync-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { assertCanonicalPath, PathLeaseManager, pathsConflict } from "./path-leases.js";
|
|
2
|
+
const MAX_REALTIME_PULL_YIELDS = 3;
|
|
3
|
+
const MAX_REALTIME_PULL_YIELD_MS = 60_000;
|
|
4
|
+
const REALTIME_PULL_YIELD_STATE_TTL_MS = 10 * 60_000;
|
|
2
5
|
function identityKey(path) {
|
|
3
6
|
return `${path.targetUid}\u0000${path.areaId}\u0000${path.canonicalPath}`;
|
|
4
7
|
}
|
|
@@ -28,8 +31,13 @@ export class DefaultParallelSyncCoordinator {
|
|
|
28
31
|
planChorePaths;
|
|
29
32
|
nextId = 1;
|
|
30
33
|
nextEpoch = 1;
|
|
31
|
-
//
|
|
34
|
+
// Fast and slow realtime lanes retain separate slots. Pulls use both when
|
|
35
|
+
// possible; a queued push can preempt them at the next worker boundary.
|
|
32
36
|
realtimeRunningByLane = {};
|
|
37
|
+
realtimeOverflowPush;
|
|
38
|
+
pullYieldStates = new Map();
|
|
39
|
+
lastRealtimeAdmissionAt = null;
|
|
40
|
+
lastChoreAdmissionAt = null;
|
|
33
41
|
choreRunning;
|
|
34
42
|
stopping = false;
|
|
35
43
|
shutdownPromise;
|
|
@@ -60,6 +68,8 @@ export class DefaultParallelSyncCoordinator {
|
|
|
60
68
|
});
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
71
|
+
this.touchPullYieldStates(submission);
|
|
72
|
+
this.expireInactivePullYieldStates();
|
|
63
73
|
if (work.class === "chore" &&
|
|
64
74
|
work.cursor?.phase !== undefined &&
|
|
65
75
|
work.cursor.phase !== "apply" &&
|
|
@@ -85,12 +95,30 @@ export class DefaultParallelSyncCoordinator {
|
|
|
85
95
|
return {
|
|
86
96
|
realtimeQueued: this.realtimeQueue.filter((submission) => !submission.finished).length,
|
|
87
97
|
choreQueued: this.choreQueue.filter((submission) => !submission.finished).length,
|
|
88
|
-
realtimeRunning: this.realtimeRunningByLane.fast !== undefined ||
|
|
98
|
+
realtimeRunning: this.realtimeRunningByLane.fast !== undefined ||
|
|
99
|
+
this.realtimeRunningByLane.slow !== undefined || this.realtimeOverflowPush !== undefined,
|
|
100
|
+
realtimeLanes: {
|
|
101
|
+
fast: this.snapshotRealtimeLane(this.realtimeRunningByLane.fast),
|
|
102
|
+
slow: this.snapshotRealtimeLane(this.realtimeRunningByLane.slow),
|
|
103
|
+
overflowPush: this.snapshotRealtimeLane(this.realtimeOverflowPush),
|
|
104
|
+
},
|
|
89
105
|
choreRunning: this.choreRunning !== undefined,
|
|
106
|
+
retainedPullYieldStates: this.pullYieldStates.size,
|
|
107
|
+
lastRealtimeAdmissionAt: this.lastRealtimeAdmissionAt,
|
|
108
|
+
lastChoreAdmissionAt: this.lastChoreAdmissionAt,
|
|
90
109
|
leases: this.leases.inspect(),
|
|
91
110
|
now: this.clock.now(),
|
|
92
111
|
};
|
|
93
112
|
}
|
|
113
|
+
snapshotRealtimeLane(submission) {
|
|
114
|
+
return submission
|
|
115
|
+
? {
|
|
116
|
+
direction: submission.work.class === "realtime" ? submission.work.direction : undefined,
|
|
117
|
+
source: submission.work.class === "realtime" ? submission.work.source : undefined,
|
|
118
|
+
paths: submission.paths.length,
|
|
119
|
+
}
|
|
120
|
+
: null;
|
|
121
|
+
}
|
|
94
122
|
shutdown() {
|
|
95
123
|
if (this.shutdownPromise)
|
|
96
124
|
return this.shutdownPromise;
|
|
@@ -102,10 +130,13 @@ export class DefaultParallelSyncCoordinator {
|
|
|
102
130
|
this.choreQueue.length = 0;
|
|
103
131
|
this.realtimeRunningByLane.fast?.controller.abort();
|
|
104
132
|
this.realtimeRunningByLane.slow?.controller.abort();
|
|
133
|
+
this.realtimeOverflowPush?.controller.abort();
|
|
105
134
|
this.choreRunning?.controller.abort();
|
|
135
|
+
this.pullYieldStates.clear();
|
|
106
136
|
const executions = [
|
|
107
137
|
this.realtimeRunningByLane.fast?.execution,
|
|
108
138
|
this.realtimeRunningByLane.slow?.execution,
|
|
139
|
+
this.realtimeOverflowPush?.execution,
|
|
109
140
|
this.choreRunning?.execution,
|
|
110
141
|
]
|
|
111
142
|
.filter((execution) => execution !== undefined);
|
|
@@ -125,6 +156,8 @@ export class DefaultParallelSyncCoordinator {
|
|
|
125
156
|
results: new Map(),
|
|
126
157
|
resolve,
|
|
127
158
|
controller: new AbortController(),
|
|
159
|
+
supersededPaths: new Set(),
|
|
160
|
+
yieldedToPush: false,
|
|
128
161
|
running: false,
|
|
129
162
|
finished: false,
|
|
130
163
|
};
|
|
@@ -174,14 +207,81 @@ export class DefaultParallelSyncCoordinator {
|
|
|
174
207
|
queued.paths = queued.paths.filter((path) => !revoked.includes(path));
|
|
175
208
|
this.finishIfEmpty(queued);
|
|
176
209
|
}
|
|
210
|
+
if (direction === "push" &&
|
|
211
|
+
this.realtimeRunningByLane.fast &&
|
|
212
|
+
this.realtimeRunningByLane.slow &&
|
|
213
|
+
this.realtimeRunningByLane.fast.work.class === "realtime" &&
|
|
214
|
+
this.realtimeRunningByLane.slow.work.class === "realtime" &&
|
|
215
|
+
this.realtimeRunningByLane.fast.work.direction === "pull" &&
|
|
216
|
+
this.realtimeRunningByLane.slow.work.direction === "pull") {
|
|
217
|
+
const pullToYield = ["slow", "fast"]
|
|
218
|
+
.map((lane) => this.realtimeRunningByLane[lane])
|
|
219
|
+
.find((running) => running?.work.class === "realtime" &&
|
|
220
|
+
running.work.direction === "pull" &&
|
|
221
|
+
!running.paths.some((path) => incoming.paths.some((candidate) => overlaps(path, candidate))));
|
|
222
|
+
if (pullToYield) {
|
|
223
|
+
const pathKeys = pullToYield.paths.map(identityKey);
|
|
224
|
+
const existingStates = pathKeys.map((key) => this.pullYieldStates.get(key)).filter((state) => state !== undefined);
|
|
225
|
+
const now = this.clock.now();
|
|
226
|
+
const state = {
|
|
227
|
+
yields: existingStates.reduce((max, item) => Math.max(max, item.yields), 0),
|
|
228
|
+
startedAt: existingStates.reduce((min, item) => Math.min(min, item.startedAt), now),
|
|
229
|
+
lastSeenAt: now,
|
|
230
|
+
capLogged: existingStates.some((item) => item.capLogged),
|
|
231
|
+
};
|
|
232
|
+
const cumulativeMs = Math.max(0, now - state.startedAt);
|
|
233
|
+
const cap = state.yields >= MAX_REALTIME_PULL_YIELDS
|
|
234
|
+
? "yield-count"
|
|
235
|
+
: cumulativeMs >= MAX_REALTIME_PULL_YIELD_MS
|
|
236
|
+
? "cumulative-time"
|
|
237
|
+
: undefined;
|
|
238
|
+
if (cap) {
|
|
239
|
+
for (const key of pathKeys)
|
|
240
|
+
this.pullYieldStates.set(key, { ...state, capLogged: true });
|
|
241
|
+
if (!state.capLogged) {
|
|
242
|
+
process.stderr.write(`hq-sync: realtime pull yield cap reached ${JSON.stringify({
|
|
243
|
+
pullId: pullToYield.id,
|
|
244
|
+
pathCount: pullToYield.paths.length,
|
|
245
|
+
yields: state.yields,
|
|
246
|
+
cumulativeMs,
|
|
247
|
+
cap,
|
|
248
|
+
})}\n`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
state.yields += 1;
|
|
253
|
+
for (const key of pathKeys)
|
|
254
|
+
this.pullYieldStates.set(key, state);
|
|
255
|
+
pullToYield.yieldedToPush = true;
|
|
256
|
+
pullToYield.controller.abort();
|
|
257
|
+
if (state.yields === MAX_REALTIME_PULL_YIELDS && !state.capLogged) {
|
|
258
|
+
for (const key of pathKeys)
|
|
259
|
+
this.pullYieldStates.set(key, { ...state, capLogged: true });
|
|
260
|
+
process.stderr.write(`hq-sync: realtime pull yield cap reached ${JSON.stringify({
|
|
261
|
+
pullId: pullToYield.id,
|
|
262
|
+
pathCount: pullToYield.paths.length,
|
|
263
|
+
yields: state.yields,
|
|
264
|
+
cumulativeMs,
|
|
265
|
+
cap: "yield-count",
|
|
266
|
+
})}\n`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
177
271
|
for (const running of [
|
|
178
272
|
this.realtimeRunningByLane.fast,
|
|
179
273
|
this.realtimeRunningByLane.slow,
|
|
274
|
+
this.realtimeOverflowPush,
|
|
180
275
|
this.choreRunning,
|
|
181
276
|
]) {
|
|
182
277
|
if (running &&
|
|
183
278
|
(running.work.class === "chore" || running.work.direction === direction) &&
|
|
184
279
|
running.paths.some((path) => incoming.paths.some((candidate) => overlaps(path, candidate)))) {
|
|
280
|
+
for (const path of running.paths) {
|
|
281
|
+
if (incoming.paths.some((candidate) => overlaps(path, candidate))) {
|
|
282
|
+
running.supersededPaths.add(identityKey(path));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
185
285
|
running.controller.abort();
|
|
186
286
|
}
|
|
187
287
|
}
|
|
@@ -193,27 +293,103 @@ export class DefaultParallelSyncCoordinator {
|
|
|
193
293
|
this.startNext("realtime", "fast");
|
|
194
294
|
if (!this.realtimeRunningByLane.slow)
|
|
195
295
|
this.startNext("realtime", "slow");
|
|
296
|
+
if (this.realtimeRunningByLane.fast &&
|
|
297
|
+
this.realtimeRunningByLane.slow &&
|
|
298
|
+
!this.realtimeOverflowPush)
|
|
299
|
+
this.startNextOverflowPush();
|
|
196
300
|
if (!this.choreRunning)
|
|
197
301
|
this.startNext("chore");
|
|
198
302
|
}
|
|
303
|
+
/** A single bounded push slot keeps disjoint watcher work moving under two held lanes. */
|
|
304
|
+
startNextOverflowPush() {
|
|
305
|
+
if (this.stopping || this.realtimeOverflowPush)
|
|
306
|
+
return;
|
|
307
|
+
const cappedActivePull = [
|
|
308
|
+
this.realtimeRunningByLane.fast,
|
|
309
|
+
this.realtimeRunningByLane.slow,
|
|
310
|
+
].some((submission) => submission?.work.class === "realtime" &&
|
|
311
|
+
submission.work.direction === "pull" &&
|
|
312
|
+
submission.paths.some((path) => this.pullYieldStates.get(identityKey(path))?.capLogged));
|
|
313
|
+
if (cappedActivePull)
|
|
314
|
+
return;
|
|
315
|
+
for (const submission of this.realtimeQueue) {
|
|
316
|
+
if (submission.finished || submission.work.class !== "realtime" || submission.work.direction !== "push") {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const lease = this.leases.tryAcquire(submission.id, this.requestsFor(submission));
|
|
320
|
+
if (!lease)
|
|
321
|
+
continue;
|
|
322
|
+
const selectedQueueIndex = this.realtimeQueue.indexOf(submission);
|
|
323
|
+
if (selectedQueueIndex < 0) {
|
|
324
|
+
lease.release();
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
this.realtimeQueue.splice(selectedQueueIndex, 1);
|
|
328
|
+
submission.lease = lease;
|
|
329
|
+
submission.running = true;
|
|
330
|
+
submission.runningLane = "overflow-push";
|
|
331
|
+
this.realtimeOverflowPush = submission;
|
|
332
|
+
this.lastRealtimeAdmissionAt = this.clock.now();
|
|
333
|
+
submission.execution = this.execute("realtime", submission);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
199
337
|
startNext(kind, lane) {
|
|
200
338
|
const queue = kind === "realtime" ? this.realtimeQueue : this.choreQueue;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
339
|
+
const entries = queue.map((candidate, queueIndex) => ({ candidate, queueIndex }));
|
|
340
|
+
const protectedPulls = new Set();
|
|
341
|
+
for (const push of entries) {
|
|
342
|
+
if (push.candidate.work.class !== "realtime" || push.candidate.work.direction !== "push")
|
|
204
343
|
continue;
|
|
205
|
-
|
|
344
|
+
for (const earlier of entries) {
|
|
345
|
+
if (earlier.queueIndex >= push.queueIndex)
|
|
346
|
+
break;
|
|
347
|
+
if (earlier.candidate.work.class === "realtime" &&
|
|
348
|
+
earlier.candidate.work.direction === "pull" &&
|
|
349
|
+
earlier.candidate.paths.some((pullPath) => push.candidate.paths.some((pushPath) => overlaps(pullPath, pushPath))))
|
|
350
|
+
protectedPulls.add(earlier.queueIndex);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const ordered = [
|
|
354
|
+
...entries.filter((entry) => protectedPulls.has(entry.queueIndex)),
|
|
355
|
+
...entries.filter((entry) => !protectedPulls.has(entry.queueIndex) && entry.candidate.work.class === "realtime" && entry.candidate.work.direction === "push"),
|
|
356
|
+
...entries.filter((entry) => !protectedPulls.has(entry.queueIndex) && !(entry.candidate.work.class === "realtime" && entry.candidate.work.direction === "push")),
|
|
357
|
+
];
|
|
358
|
+
for (const selected of ordered) {
|
|
359
|
+
const submission = selected.candidate;
|
|
360
|
+
if (submission.finished)
|
|
206
361
|
continue;
|
|
362
|
+
let assignedLane;
|
|
363
|
+
if (kind === "realtime") {
|
|
364
|
+
const preferredLane = this.realtimeLane(submission);
|
|
365
|
+
const pushMayUseFreeLane = submission.work.class === "realtime" &&
|
|
366
|
+
submission.work.direction === "push" && preferredLane !== lane &&
|
|
367
|
+
this.realtimeRunningByLane[preferredLane]?.work.class === "realtime" &&
|
|
368
|
+
this.realtimeRunningByLane[preferredLane]?.work.direction === "pull";
|
|
369
|
+
assignedLane = pushMayUseFreeLane ? lane : preferredLane;
|
|
370
|
+
if (assignedLane !== lane)
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
207
373
|
const lease = this.leases.tryAcquire(submission.id, this.requestsFor(submission));
|
|
208
374
|
if (!lease)
|
|
209
375
|
continue;
|
|
210
|
-
queue.
|
|
376
|
+
const selectedQueueIndex = queue.indexOf(submission);
|
|
377
|
+
if (selectedQueueIndex === -1)
|
|
378
|
+
continue;
|
|
379
|
+
queue.splice(selectedQueueIndex, 1);
|
|
211
380
|
submission.lease = lease;
|
|
212
381
|
submission.running = true;
|
|
213
|
-
if (kind === "realtime")
|
|
214
|
-
|
|
215
|
-
|
|
382
|
+
if (kind === "realtime") {
|
|
383
|
+
submission.runningLane = assignedLane;
|
|
384
|
+
// Keep the original work object as the execution registry key. The
|
|
385
|
+
// physical lane is tracked separately in runningLane.
|
|
386
|
+
this.realtimeRunningByLane[assignedLane] = submission;
|
|
387
|
+
this.lastRealtimeAdmissionAt = this.clock.now();
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
216
390
|
this.choreRunning = submission;
|
|
391
|
+
this.lastChoreAdmissionAt = this.clock.now();
|
|
392
|
+
}
|
|
217
393
|
submission.execution = this.execute(kind, submission);
|
|
218
394
|
return;
|
|
219
395
|
}
|
|
@@ -245,8 +421,13 @@ export class DefaultParallelSyncCoordinator {
|
|
|
245
421
|
finally {
|
|
246
422
|
submission.lease?.release();
|
|
247
423
|
submission.running = false;
|
|
248
|
-
if (kind === "realtime")
|
|
249
|
-
|
|
424
|
+
if (kind === "realtime") {
|
|
425
|
+
if (submission.runningLane === "overflow-push")
|
|
426
|
+
this.realtimeOverflowPush = undefined;
|
|
427
|
+
else
|
|
428
|
+
this.realtimeRunningByLane[submission.runningLane ?? this.realtimeLane(submission)] = undefined;
|
|
429
|
+
submission.runningLane = undefined;
|
|
430
|
+
}
|
|
250
431
|
else
|
|
251
432
|
this.choreRunning = undefined;
|
|
252
433
|
this.finishIfEmpty(submission);
|
|
@@ -260,7 +441,8 @@ export class DefaultParallelSyncCoordinator {
|
|
|
260
441
|
}
|
|
261
442
|
const byPath = new Map(outcome.paths.map((result) => [result.path, result]));
|
|
262
443
|
for (const path of submission.paths) {
|
|
263
|
-
if (
|
|
444
|
+
if (submission.supersededPaths.has(identityKey(path)) ||
|
|
445
|
+
this.isStale(path, submission.work.class === "realtime" ? submission.work.direction : undefined)) {
|
|
264
446
|
this.setResult(submission, path, "refused", "stale path epoch");
|
|
265
447
|
continue;
|
|
266
448
|
}
|
|
@@ -269,7 +451,9 @@ export class DefaultParallelSyncCoordinator {
|
|
|
269
451
|
this.setResult(submission, path, "retryable", outcome.error ?? "worker omitted or mismatched a path outcome");
|
|
270
452
|
continue;
|
|
271
453
|
}
|
|
272
|
-
this.setResult(submission, path, result.status, result.
|
|
454
|
+
this.setResult(submission, path, result.status, submission.yieldedToPush && result.status === "retryable"
|
|
455
|
+
? "yielded to queued watcher push"
|
|
456
|
+
: result.reason);
|
|
273
457
|
}
|
|
274
458
|
}
|
|
275
459
|
isStale(path, direction) {
|
|
@@ -284,16 +468,45 @@ export class DefaultParallelSyncCoordinator {
|
|
|
284
468
|
for (const path of submission.paths) {
|
|
285
469
|
if (submission.results.has(identityKey(path)))
|
|
286
470
|
continue;
|
|
287
|
-
if (
|
|
471
|
+
if (submission.supersededPaths.has(identityKey(path)) ||
|
|
472
|
+
this.isStale(path, submission.work.class === "realtime" ? submission.work.direction : undefined)) {
|
|
288
473
|
this.setResult(submission, path, "refused", "stale path epoch");
|
|
289
474
|
}
|
|
290
475
|
else
|
|
291
|
-
this.setResult(submission, path, "retryable", reason);
|
|
476
|
+
this.setResult(submission, path, "retryable", submission.yieldedToPush ? "yielded to queued watcher push" : reason);
|
|
292
477
|
}
|
|
293
478
|
this.finishIfEmpty(submission);
|
|
294
479
|
}
|
|
295
480
|
setResult(submission, path, status, reason) {
|
|
296
481
|
submission.results.set(identityKey(path), { path: path.canonicalPath, status, epoch: path.epoch, ...(reason ? { reason } : {}) });
|
|
482
|
+
if (submission.work.class === "realtime" &&
|
|
483
|
+
submission.work.direction === "pull" &&
|
|
484
|
+
(status !== "retryable" || reason !== "yielded to queued watcher push")) {
|
|
485
|
+
for (const path of submission.paths)
|
|
486
|
+
this.pullYieldStates.delete(identityKey(path));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
expireInactivePullYieldStates() {
|
|
490
|
+
const activePaths = new Set([
|
|
491
|
+
this.realtimeRunningByLane.fast,
|
|
492
|
+
this.realtimeRunningByLane.slow,
|
|
493
|
+
this.realtimeOverflowPush,
|
|
494
|
+
].flatMap((submission) => submission?.paths.map(identityKey) ?? []));
|
|
495
|
+
const now = this.clock.now();
|
|
496
|
+
for (const [key, state] of this.pullYieldStates) {
|
|
497
|
+
if (!activePaths.has(key) && now - state.lastSeenAt >= REALTIME_PULL_YIELD_STATE_TTL_MS) {
|
|
498
|
+
this.pullYieldStates.delete(key);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
touchPullYieldStates(submission) {
|
|
503
|
+
const now = this.clock.now();
|
|
504
|
+
for (const path of submission.paths) {
|
|
505
|
+
const key = identityKey(path);
|
|
506
|
+
const state = this.pullYieldStates.get(key);
|
|
507
|
+
if (state)
|
|
508
|
+
this.pullYieldStates.set(key, { ...state, lastSeenAt: now });
|
|
509
|
+
}
|
|
297
510
|
}
|
|
298
511
|
completeRemaining(submission, status, reason) {
|
|
299
512
|
for (const path of submission.paths) {
|