@ricsam/r5d-worker 0.0.51 → 0.0.54
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/cjs/heartbeat.cjs +15 -2
- package/dist/cjs/main.cjs +186 -145
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-incident-state.cjs +39 -0
- package/dist/cjs/workspace-mutation-gate.cjs +92 -0
- package/dist/cjs/workspace-sync.cjs +101 -41
- package/dist/mjs/heartbeat.mjs +12 -1
- package/dist/mjs/main.mjs +191 -146
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-incident-state.mjs +14 -0
- package/dist/mjs/workspace-mutation-gate.mjs +68 -0
- package/dist/mjs/workspace-sync.mjs +99 -41
- package/dist/types/heartbeat.d.ts +7 -0
- package/dist/types/workspace-incident-state.d.ts +8 -0
- package/dist/types/workspace-mutation-gate.d.ts +19 -0
- package/dist/types/workspace-sync.d.ts +14 -1
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var workspace_mutation_gate_exports = {};
|
|
20
|
+
__export(workspace_mutation_gate_exports, {
|
|
21
|
+
WorkspaceMutationGate: () => WorkspaceMutationGate
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(workspace_mutation_gate_exports);
|
|
24
|
+
class WorkspaceMutationGate {
|
|
25
|
+
activeMutations = 0;
|
|
26
|
+
syncActive = false;
|
|
27
|
+
waiters = [];
|
|
28
|
+
acquireMutation() {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
this.waiters.push({ kind: "mutation", resolve });
|
|
31
|
+
this.drain();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async runMutation(operation) {
|
|
35
|
+
const release = await this.acquireMutation();
|
|
36
|
+
try {
|
|
37
|
+
return await operation();
|
|
38
|
+
} finally {
|
|
39
|
+
release();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async runSync(operation) {
|
|
43
|
+
const release = await new Promise((resolve) => {
|
|
44
|
+
this.waiters.push({ kind: "sync", resolve });
|
|
45
|
+
this.drain();
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
return await operation();
|
|
49
|
+
} finally {
|
|
50
|
+
release();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
drain() {
|
|
54
|
+
if (this.syncActive || this.waiters.length === 0) return;
|
|
55
|
+
const first = this.waiters[0];
|
|
56
|
+
if (first?.kind === "sync") {
|
|
57
|
+
if (this.activeMutations > 0) return;
|
|
58
|
+
this.waiters.shift();
|
|
59
|
+
this.syncActive = true;
|
|
60
|
+
first.resolve(this.releaseSync());
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
while (this.waiters[0]?.kind === "mutation") {
|
|
64
|
+
const waiter = this.waiters.shift();
|
|
65
|
+
if (!waiter || waiter.kind !== "mutation") break;
|
|
66
|
+
this.activeMutations += 1;
|
|
67
|
+
waiter.resolve(this.releaseMutation());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
releaseMutation() {
|
|
71
|
+
let released = false;
|
|
72
|
+
return () => {
|
|
73
|
+
if (released) return;
|
|
74
|
+
released = true;
|
|
75
|
+
this.activeMutations -= 1;
|
|
76
|
+
this.drain();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
releaseSync() {
|
|
80
|
+
let released = false;
|
|
81
|
+
return () => {
|
|
82
|
+
if (released) return;
|
|
83
|
+
released = true;
|
|
84
|
+
this.syncActive = false;
|
|
85
|
+
this.drain();
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
90
|
+
0 && (module.exports = {
|
|
91
|
+
WorkspaceMutationGate
|
|
92
|
+
});
|
|
@@ -30,10 +30,12 @@ var workspace_sync_exports = {};
|
|
|
30
30
|
__export(workspace_sync_exports, {
|
|
31
31
|
MAX_WORKSPACE_SYNC_DIFF_BYTES: () => MAX_WORKSPACE_SYNC_DIFF_BYTES,
|
|
32
32
|
WORKSPACE_BRANCH: () => WORKSPACE_BRANCH,
|
|
33
|
+
WORKSPACE_INTENT_REF_PREFIX: () => WORKSPACE_INTENT_REF_PREFIX,
|
|
33
34
|
WORKSPACE_NATIVE_PLANS_RELATIVE_PATH: () => WORKSPACE_NATIVE_PLANS_RELATIVE_PATH,
|
|
34
35
|
WORKSPACE_PERIODIC_SCAN_INTERVAL_MS: () => WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
|
|
35
36
|
WorkspaceSyncSingleFlight: () => WorkspaceSyncSingleFlight,
|
|
36
37
|
assertCanonicalCheckoutIndexMatchesWorktree: () => assertCanonicalCheckoutIndexMatchesWorktree,
|
|
38
|
+
assertWorkspaceQuarantineRef: () => assertWorkspaceQuarantineRef,
|
|
37
39
|
calculateWorkspaceDiffFingerprint: () => calculateWorkspaceDiffFingerprint,
|
|
38
40
|
encodeWorkspaceBranch: () => encodeWorkspaceBranch,
|
|
39
41
|
mirrorShadowWorkspaceToVisible: () => mirrorShadowWorkspaceToVisible,
|
|
@@ -49,7 +51,9 @@ var import_node_crypto = require("node:crypto");
|
|
|
49
51
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
50
52
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
51
53
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
54
|
+
var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
|
|
52
55
|
const WORKSPACE_BRANCH = "main";
|
|
56
|
+
const WORKSPACE_INTENT_REF_PREFIX = "refs/r5d/workspace-intents/";
|
|
53
57
|
const MAX_WORKSPACE_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
54
58
|
const WORKSPACE_PERIODIC_SCAN_INTERVAL_MS = 5e3;
|
|
55
59
|
const MAX_REPORTED_WORKSPACE_PATHS = 2e3;
|
|
@@ -1119,10 +1123,27 @@ function commitMessage(input) {
|
|
|
1119
1123
|
...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
|
|
1120
1124
|
});
|
|
1121
1125
|
}
|
|
1122
|
-
function
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
+
function assertWorkspaceQuarantineRef(quarantineRef) {
|
|
1127
|
+
const suffix = quarantineRef.slice(WORKSPACE_INTENT_REF_PREFIX.length);
|
|
1128
|
+
if (!quarantineRef.startsWith(WORKSPACE_INTENT_REF_PREFIX) || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(suffix)) {
|
|
1129
|
+
throw new Error(`Workspace candidate ref must be below ${WORKSPACE_INTENT_REF_PREFIX}`);
|
|
1130
|
+
}
|
|
1131
|
+
const result = Bun.spawnSync(["git", "check-ref-format", quarantineRef], {
|
|
1132
|
+
stdout: "pipe",
|
|
1133
|
+
stderr: "pipe",
|
|
1134
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
1135
|
+
});
|
|
1136
|
+
if (result.exitCode !== 0) {
|
|
1137
|
+
throw new Error(`Invalid workspace candidate ref ${JSON.stringify(quarantineRef)}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function uploadWorkspaceCandidate(input, candidateHead) {
|
|
1141
|
+
const quarantineRef = input.quarantineRef;
|
|
1142
|
+
if (!quarantineRef) {
|
|
1143
|
+
throw new Error("A server-issued workspace candidate ref is required before uploading a canonical workspace candidate");
|
|
1144
|
+
}
|
|
1145
|
+
assertWorkspaceQuarantineRef(quarantineRef);
|
|
1146
|
+
return runGitResult(input, input.shadowRoot, ["push", "origin", `HEAD:${quarantineRef}`]);
|
|
1126
1147
|
}
|
|
1127
1148
|
function resetShadowToRemote(input, opaqueWorkspaceRoots = []) {
|
|
1128
1149
|
runGit(input, input.shadowRoot, ["fetch", "origin", "--prune"], "fetch canonical workspace before reset");
|
|
@@ -1141,6 +1162,7 @@ function baseResult(input, startingHead) {
|
|
|
1141
1162
|
workerLabel: input.workerLabel,
|
|
1142
1163
|
trigger: input.trigger,
|
|
1143
1164
|
startingHead,
|
|
1165
|
+
expectedHead: startingHead,
|
|
1144
1166
|
rebaseCount: 0,
|
|
1145
1167
|
diffSizeBytes: 0,
|
|
1146
1168
|
gitStatus: "",
|
|
@@ -1150,6 +1172,32 @@ function baseResult(input, startingHead) {
|
|
|
1150
1172
|
localChangesDiscarded: false
|
|
1151
1173
|
};
|
|
1152
1174
|
}
|
|
1175
|
+
function convergeRedundantInboundCandidate(input, result) {
|
|
1176
|
+
if (input.trigger.type !== "inbound_head" || !input.skipVisibleMirror || input.trigger.canonicalCheckoutOnly) return null;
|
|
1177
|
+
const remoteHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
|
|
1178
|
+
if (!remoteHead) return null;
|
|
1179
|
+
const stagedTree = runGit(input, input.shadowRoot, ["write-tree"], "write inbound workspace candidate tree");
|
|
1180
|
+
const remoteTree = runGit(
|
|
1181
|
+
input,
|
|
1182
|
+
input.shadowRoot,
|
|
1183
|
+
["rev-parse", "--verify", `${remoteHead}^{tree}`],
|
|
1184
|
+
"resolve authoritative workspace tree"
|
|
1185
|
+
);
|
|
1186
|
+
if (stagedTree !== remoteTree) return null;
|
|
1187
|
+
const localHead = revParse(input, "HEAD");
|
|
1188
|
+
runGit(input, input.shadowRoot, ["reset", "--hard", remoteHead], "converge redundant inbound workspace candidate");
|
|
1189
|
+
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean redundant inbound workspace candidate");
|
|
1190
|
+
mirrorShadowWorkspaceToVisible(input);
|
|
1191
|
+
return {
|
|
1192
|
+
...result,
|
|
1193
|
+
outcome: localHead === remoteHead ? "no_change" : "updated",
|
|
1194
|
+
publishedHead: remoteHead,
|
|
1195
|
+
diffSizeBytes: 0,
|
|
1196
|
+
gitStatus: gitStatus(input),
|
|
1197
|
+
affectedProjects: [],
|
|
1198
|
+
affectedPaths: []
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1153
1201
|
async function synchronizeWorkspace(rawInput) {
|
|
1154
1202
|
let input = { ...rawInput, attemptId: rawInput.attemptId ?? crypto.randomUUID() };
|
|
1155
1203
|
try {
|
|
@@ -1183,6 +1231,8 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1183
1231
|
const stagePathspecs = mirroredGitlinkProjection.opaqueRoots.flatMap((root) => [`:(exclude,literal)${root}`]);
|
|
1184
1232
|
runGit(input, input.shadowRoot, ["add", "-A", "--", ".", ...stagePathspecs], "stage workspace changes");
|
|
1185
1233
|
forceStageCanonicalCheckoutFiles(input, mirroredGitlinkProjection.entries, mirroredGitlinkProjection.opaqueRoots);
|
|
1234
|
+
const redundantInboundCandidate = convergeRedundantInboundCandidate(input, result);
|
|
1235
|
+
if (redundantInboundCandidate) return redundantInboundCandidate;
|
|
1186
1236
|
const sampledCanonicalCheckoutTreeHash = sampleCanonicalCheckoutTree(input);
|
|
1187
1237
|
const stagedWorkingPaths = stagedPaths(input);
|
|
1188
1238
|
const baseRevision = candidateBaseRevision(input);
|
|
@@ -1216,7 +1266,7 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1216
1266
|
if (stagedWorkingPaths.length > 0) {
|
|
1217
1267
|
runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit workspace changes");
|
|
1218
1268
|
}
|
|
1219
|
-
|
|
1269
|
+
let candidateHead = revParse(input, "HEAD");
|
|
1220
1270
|
if (candidateHead) assertUnscopedCanonicalCheckoutSubtreesMatchRemote(input, candidateHead);
|
|
1221
1271
|
const hasRemoteMain = Boolean(revParse(input, `origin/${WORKSPACE_BRANCH}`));
|
|
1222
1272
|
const hasUnpushedCommit = candidateHead ? !hasRemoteMain || !tryGit(input, input.shadowRoot, ["merge-base", "--is-ancestor", candidateHead, `origin/${WORKSPACE_BRANCH}`]) : false;
|
|
@@ -1252,58 +1302,59 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1252
1302
|
};
|
|
1253
1303
|
}
|
|
1254
1304
|
let rebaseCount = 0;
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (push.exitCode === 0) {
|
|
1258
|
-
const publishedHead = revParse(input, "HEAD") ?? candidateHead;
|
|
1259
|
-
if (publishedHead) assertPublishedCanonicalCheckoutTree(input, publishedHead, sampledCanonicalCheckoutTreeHash);
|
|
1260
|
-
mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots);
|
|
1261
|
-
return {
|
|
1262
|
-
...observed,
|
|
1263
|
-
outcome: "published",
|
|
1264
|
-
candidateHead: candidateHead ?? void 0,
|
|
1265
|
-
publishedHead: publishedHead ?? void 0,
|
|
1266
|
-
rebaseCount,
|
|
1267
|
-
gitStatus: gitStatus(input)
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
|
-
if (!isNonFastForward(push)) {
|
|
1271
|
-
return {
|
|
1272
|
-
...observed,
|
|
1273
|
-
outcome: "failed",
|
|
1274
|
-
candidateHead: candidateHead ?? void 0,
|
|
1275
|
-
rebaseCount,
|
|
1276
|
-
error: push.stderr || push.stdout || "Workspace push failed",
|
|
1277
|
-
gitStatus: gitStatus(input)
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
runGit(input, input.shadowRoot, ["fetch", "origin", "--prune"], "fetch workspace after push race");
|
|
1305
|
+
const expectedHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
|
|
1306
|
+
if (candidateHead && expectedHead && !tryGit(input, input.shadowRoot, ["merge-base", "--is-ancestor", expectedHead, candidateHead])) {
|
|
1281
1307
|
const rebase = runGitResult(input, input.shadowRoot, ["rebase", `origin/${WORKSPACE_BRANCH}`]);
|
|
1282
1308
|
rebaseCount += 1;
|
|
1283
1309
|
if (rebase.exitCode !== 0) {
|
|
1284
1310
|
const conflicted = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]);
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1311
|
+
const conflictPaths = reportedPaths([.../* @__PURE__ */ new Set([...paths, ...conflicted.stdout.split("\0").filter(Boolean)])].sort());
|
|
1312
|
+
runGit(input, input.shadowRoot, ["rebase", "--abort"], "preserve workspace candidate after rebase conflict");
|
|
1287
1313
|
return {
|
|
1288
1314
|
...observed,
|
|
1289
|
-
outcome: "
|
|
1315
|
+
outcome: "failed",
|
|
1316
|
+
expectedHead,
|
|
1290
1317
|
candidateHead: candidateHead ?? void 0,
|
|
1291
|
-
publishedHead: publishedHead ?? void 0,
|
|
1292
1318
|
rebaseCount,
|
|
1293
|
-
|
|
1294
|
-
|
|
1319
|
+
affectedPaths: conflictPaths,
|
|
1320
|
+
error: `Workspace candidate conflicts with canonical head ${expectedHead}; local changes were preserved for reconciliation.`,
|
|
1295
1321
|
gitStatus: gitStatus(input)
|
|
1296
1322
|
};
|
|
1297
1323
|
}
|
|
1324
|
+
candidateHead = revParse(input, "HEAD");
|
|
1298
1325
|
assertPublishedCanonicalCheckoutTree(input, "HEAD", sampledCanonicalCheckoutTreeHash);
|
|
1299
1326
|
assertUnscopedCanonicalCheckoutSubtreesMatchRemote(input, "HEAD");
|
|
1300
1327
|
}
|
|
1328
|
+
if (!candidateHead) {
|
|
1329
|
+
return {
|
|
1330
|
+
...observed,
|
|
1331
|
+
outcome: "failed",
|
|
1332
|
+
rebaseCount,
|
|
1333
|
+
error: "Workspace candidate does not have a commit",
|
|
1334
|
+
gitStatus: gitStatus(input)
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const upload = uploadWorkspaceCandidate(input, candidateHead);
|
|
1338
|
+
if (upload.exitCode !== 0) {
|
|
1339
|
+
return {
|
|
1340
|
+
...observed,
|
|
1341
|
+
outcome: "failed",
|
|
1342
|
+
candidateHead,
|
|
1343
|
+
quarantineRef: input.quarantineRef,
|
|
1344
|
+
rebaseCount,
|
|
1345
|
+
error: upload.stderr || upload.stdout || "Workspace candidate upload failed",
|
|
1346
|
+
gitStatus: gitStatus(input)
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
assertPublishedCanonicalCheckoutTree(input, candidateHead, sampledCanonicalCheckoutTreeHash);
|
|
1350
|
+
mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots);
|
|
1301
1351
|
return {
|
|
1302
1352
|
...observed,
|
|
1303
|
-
outcome: "
|
|
1304
|
-
|
|
1353
|
+
outcome: "candidate_ready",
|
|
1354
|
+
expectedHead,
|
|
1355
|
+
candidateHead,
|
|
1356
|
+
quarantineRef: input.quarantineRef,
|
|
1305
1357
|
rebaseCount,
|
|
1306
|
-
error: "Workspace push did not converge after three attempts",
|
|
1307
1358
|
gitStatus: gitStatus(input)
|
|
1308
1359
|
};
|
|
1309
1360
|
} catch (error) {
|
|
@@ -1346,9 +1397,10 @@ function calculateWorkspaceDiffFingerprint(rawInput) {
|
|
|
1346
1397
|
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify({ stagedTree, localHead, remoteHead })).digest("hex");
|
|
1347
1398
|
}
|
|
1348
1399
|
class WorkspaceSyncSingleFlight {
|
|
1400
|
+
mutationGate = new import_workspace_mutation_gate.WorkspaceMutationGate();
|
|
1349
1401
|
queue = Promise.resolve();
|
|
1350
1402
|
enqueue(task) {
|
|
1351
|
-
const queued = this.
|
|
1403
|
+
const queued = this.mutationGate.runSync(task);
|
|
1352
1404
|
this.queue = queued.then(
|
|
1353
1405
|
() => void 0,
|
|
1354
1406
|
() => void 0
|
|
@@ -1367,6 +1419,12 @@ class WorkspaceSyncSingleFlight {
|
|
|
1367
1419
|
fingerprintPrepared(prepare) {
|
|
1368
1420
|
return this.enqueue(() => calculateWorkspaceDiffFingerprint(prepare()));
|
|
1369
1421
|
}
|
|
1422
|
+
runMutation(operation) {
|
|
1423
|
+
return this.mutationGate.runMutation(operation);
|
|
1424
|
+
}
|
|
1425
|
+
acquireMutation() {
|
|
1426
|
+
return this.mutationGate.acquireMutation();
|
|
1427
|
+
}
|
|
1370
1428
|
afterCurrent() {
|
|
1371
1429
|
return this.queue;
|
|
1372
1430
|
}
|
|
@@ -1375,10 +1433,12 @@ class WorkspaceSyncSingleFlight {
|
|
|
1375
1433
|
0 && (module.exports = {
|
|
1376
1434
|
MAX_WORKSPACE_SYNC_DIFF_BYTES,
|
|
1377
1435
|
WORKSPACE_BRANCH,
|
|
1436
|
+
WORKSPACE_INTENT_REF_PREFIX,
|
|
1378
1437
|
WORKSPACE_NATIVE_PLANS_RELATIVE_PATH,
|
|
1379
1438
|
WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
|
|
1380
1439
|
WorkspaceSyncSingleFlight,
|
|
1381
1440
|
assertCanonicalCheckoutIndexMatchesWorktree,
|
|
1441
|
+
assertWorkspaceQuarantineRef,
|
|
1382
1442
|
calculateWorkspaceDiffFingerprint,
|
|
1383
1443
|
encodeWorkspaceBranch,
|
|
1384
1444
|
mirrorShadowWorkspaceToVisible,
|
package/dist/mjs/heartbeat.mjs
CHANGED
|
@@ -3,8 +3,19 @@ const WORKER_HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
|
3
3
|
function hasWorkerHeartbeatTimedOut(lastHeartbeatAt, now = Date.now(), timeoutMs = WORKER_HEARTBEAT_TIMEOUT_MS) {
|
|
4
4
|
return lastHeartbeatAt !== null && now - lastHeartbeatAt >= timeoutMs;
|
|
5
5
|
}
|
|
6
|
+
function grantWorkerHeartbeatBusyGrace(lastHeartbeatAt, currentGrace, now = Date.now(), timeoutMs = WORKER_HEARTBEAT_TIMEOUT_MS, graceMs = WORKER_HEARTBEAT_INTERVAL_MS) {
|
|
7
|
+
if (!hasWorkerHeartbeatTimedOut(lastHeartbeatAt, now, timeoutMs)) return currentGrace;
|
|
8
|
+
if (currentGrace?.heartbeatAt === lastHeartbeatAt) return currentGrace;
|
|
9
|
+
return { heartbeatAt: lastHeartbeatAt, expiresAt: now + graceMs };
|
|
10
|
+
}
|
|
11
|
+
function hasWorkerHeartbeatTimedOutWithBusyGrace(lastHeartbeatAt, busyGrace, now = Date.now(), timeoutMs = WORKER_HEARTBEAT_TIMEOUT_MS) {
|
|
12
|
+
if (lastHeartbeatAt !== null && busyGrace?.heartbeatAt === lastHeartbeatAt && now < busyGrace.expiresAt) return false;
|
|
13
|
+
return hasWorkerHeartbeatTimedOut(lastHeartbeatAt, now, timeoutMs);
|
|
14
|
+
}
|
|
6
15
|
export {
|
|
7
16
|
WORKER_HEARTBEAT_INTERVAL_MS,
|
|
8
17
|
WORKER_HEARTBEAT_TIMEOUT_MS,
|
|
9
|
-
|
|
18
|
+
grantWorkerHeartbeatBusyGrace,
|
|
19
|
+
hasWorkerHeartbeatTimedOut,
|
|
20
|
+
hasWorkerHeartbeatTimedOutWithBusyGrace
|
|
10
21
|
};
|