querysub 0.612.0 → 0.614.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.614.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
76
76
|
"pako": "^2.1.0",
|
|
77
77
|
"peggy": "^5.0.6",
|
|
78
|
-
"sliftutils": "^1.7.
|
|
78
|
+
"sliftutils": "^1.7.86",
|
|
79
79
|
"socket-function": "^1.2.30",
|
|
80
80
|
"terser": "^5.31.0",
|
|
81
81
|
"typenode": "^6.6.1",
|
|
@@ -26,7 +26,7 @@ import { PromiseObj } from "../promise";
|
|
|
26
26
|
import path from "path";
|
|
27
27
|
import { fsExistsAsync } from "../fs";
|
|
28
28
|
import { ALIVE_WINDOW_FOREVER, ParametersTimelineEntry, syncParametersTimelineFiles } from "./parametersTimeline";
|
|
29
|
-
import { getScreenName, getRetiringKillTime, retireScreen, retireCurrentScreen, getTmuxPrefix, getScreenState, isScreenRunningProcess, getProcessStartTime, getScreenPanePid, ensureProcessRecord, runScreenCommand, killScreen, readServiceNodeId, removeServiceNode, killScreenNow, withScreenLock } from "./processManager";
|
|
29
|
+
import { getScreenName, getRetiringKillTime, getCanonicalScreenName, retireScreen, retireScreenWithMaxKillTime, retireCurrentScreen, getTmuxPrefix, getScreenState, isScreenRunningProcess, getProcessStartTime, getScreenPanePid, ensureProcessRecord, runScreenCommand, killScreen, readServiceNodeId, removeServiceNode, killScreenNow, withScreenLock } from "./processManager";
|
|
30
30
|
import { ProcessRecord, syncProcessRecords, listProcessRecords } from "./processLogs";
|
|
31
31
|
|
|
32
32
|
|
|
@@ -261,6 +261,17 @@ function getMachineInstances(configs: ServiceConfig[], machineId: string): { con
|
|
|
261
261
|
return instances;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// A version being replaced is renamed aside and given until this before it is taken down, which is the only thing an overlap is. Anchored to the release, not to whenever this resync happened to run, so every machine's old process goes at the same moment however staggered the resyncs are. Uses the NEWEST parameters' overlapTime, so shortening the overlap pulls the deadline in even for versions already retiring.
|
|
265
|
+
function getInstanceKillTime(parameters: ServiceParameters): number {
|
|
266
|
+
return (parameters.releaseTime || Date.now()) + (parameters.overlapTime ?? DEFAULT_OVERLAP_TIME);
|
|
267
|
+
}
|
|
268
|
+
/** The canonical screen name of one instance - the version that should be running there. Single source of truth for the name, so the retiring-screen sweep derives it the same way ensureScreen does. */
|
|
269
|
+
function getInstanceScreenName(config: ServiceConfig, index: number): string {
|
|
270
|
+
let live = getLiveServiceParameters(config);
|
|
271
|
+
let serviceKey = (live.deploy ? live : config.parameters).key;
|
|
272
|
+
return getScreenName({ serviceKey, index });
|
|
273
|
+
}
|
|
274
|
+
|
|
264
275
|
/** Puts one instance of a service into the state its configuration describes: the folder synced, the process running what it should be, and the records and timeline saying so. Returns the screen it is using, so anything nobody claimed can be killed. Everything about the screen happens under its lock, so two passes can never interleave. */
|
|
265
276
|
async function ensureScreen(config: {
|
|
266
277
|
config: ServiceConfig;
|
|
@@ -272,13 +283,12 @@ async function ensureScreen(config: {
|
|
|
272
283
|
let serviceId = config.config.serviceId;
|
|
273
284
|
let serviceConfig = config.config;
|
|
274
285
|
|
|
275
|
-
|
|
276
|
-
let killTime = (serviceConfig.parameters.releaseTime || Date.now()) + (serviceConfig.parameters.overlapTime ?? DEFAULT_OVERLAP_TIME);
|
|
286
|
+
let killTime = getInstanceKillTime(serviceConfig.parameters);
|
|
277
287
|
|
|
278
288
|
// The screen name means one thing: the version that should be running. There is no second name to reason about - anything it replaced is already renamed aside and carries its own deadline.
|
|
279
289
|
let parameters: ServiceParameters | undefined = getLiveServiceParameters(serviceConfig);
|
|
280
290
|
if (!parameters.deploy) parameters = undefined;
|
|
281
|
-
let screenName =
|
|
291
|
+
let screenName = getInstanceScreenName(serviceConfig, index);
|
|
282
292
|
let folder = os.homedir() + "/" + SERVICE_FOLDER + screenName + "/";
|
|
283
293
|
// Returned even when the work below throws, so a transient failure never makes the sweep kill a screen we still want
|
|
284
294
|
let usedScreenNames = [screenName];
|
|
@@ -500,6 +510,12 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
500
510
|
// The fast-path results are memoized per resync, so a folder shared by two instances is only traversed once
|
|
501
511
|
gitFastPathChecks.clear();
|
|
502
512
|
|
|
513
|
+
// The kill time each instance's overlap now wants, keyed by the canonical screen name its retiring predecessor was renamed from. Lets the sweep cap an already-retiring screen's deadline against the newest overlap instead of the (possibly longer) one baked into its name.
|
|
514
|
+
let killTimeByScreen = new Map<string, number>();
|
|
515
|
+
for (let { config, index } of instances) {
|
|
516
|
+
killTimeByScreen.set(getInstanceScreenName(config, index), getInstanceKillTime(config.parameters));
|
|
517
|
+
}
|
|
518
|
+
|
|
503
519
|
// Every instance converges concurrently. Each has its own folder, its own records and its own lock, and yarn install takes a network mutex, so there is nothing to serialize behind - one slow git sync or shutdown no longer delays everything after it.
|
|
504
520
|
let usedScreenNames = new Set((await Promise.all(instances.map(instance => ensureScreen({ ...instance, machineInfo })))).flat());
|
|
505
521
|
|
|
@@ -508,7 +524,14 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
508
524
|
if (usedScreenNames.has(screenName)) continue;
|
|
509
525
|
// A retiring screen is not unclaimed - it is a version that was replaced, and its name says when it is due to go. Cached per name, so seeing it on every resync only ever starts one, and a restart picks it up again from the name alone.
|
|
510
526
|
if (getRetiringKillTime(screenName) !== undefined) {
|
|
511
|
-
|
|
527
|
+
let canonicalScreenName = getCanonicalScreenName(screenName);
|
|
528
|
+
// Capped against the newest overlap when we still have that instance's config, so a shortened overlap pulls the deadline in (down to now, killing it) rather than letting it wait out the time baked into its name. Without a matching config we cannot know the new overlap, so we honor the name.
|
|
529
|
+
let maxKillTime = canonicalScreenName !== undefined ? killTimeByScreen.get(canonicalScreenName) : undefined;
|
|
530
|
+
if (maxKillTime !== undefined && canonicalScreenName !== undefined) {
|
|
531
|
+
void retireScreenWithMaxKillTime({ screenName, folder: root + canonicalScreenName + "/", maxKillTime });
|
|
532
|
+
} else {
|
|
533
|
+
void retireScreen(screenName);
|
|
534
|
+
}
|
|
512
535
|
continue;
|
|
513
536
|
}
|
|
514
537
|
// Detached, so a screen that takes its full grace period to go down doesn't hold up the end of the resync
|
|
@@ -356,6 +356,47 @@ export async function retireCurrentScreen(config: {
|
|
|
356
356
|
void retireScreen(retiringScreenName);
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
/** The canonical name a retiring screen was renamed from - the inverse of getRetiringScreenName. Undefined for a name that is not a retiring screen. */
|
|
360
|
+
export function getCanonicalScreenName(screenName: string): string | undefined {
|
|
361
|
+
let marker = screenName.lastIndexOf(RETIRING_MARKER);
|
|
362
|
+
if (marker === -1) return undefined;
|
|
363
|
+
return screenName.slice(0, marker) + SCREEN_SUFFIX;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Schedules a retiring screen's takedown, capped at maxKillTime: a shortened overlap can leave a screen scheduled to go later than it now should, and since the deadline lives in the name, the only way to move it is to rename the screen. When the cap is earlier than the time baked into the name, this renames it to carry the earlier time (down to now, which retireScreen then acts on immediately, killing it) and re-arms against the new name. When nothing needs pulling in, it just re-arms the existing timer, so it is safe to call every sweep. */
|
|
367
|
+
export async function retireScreenWithMaxKillTime(config: { screenName: string; folder: string; maxKillTime: number }): Promise<void> {
|
|
368
|
+
let { screenName, folder } = config;
|
|
369
|
+
let maxKillTime = Math.round(config.maxKillTime);
|
|
370
|
+
let currentKillTime = getRetiringKillTime(screenName);
|
|
371
|
+
if (currentKillTime === undefined) return;
|
|
372
|
+
let canonicalScreenName = getCanonicalScreenName(screenName);
|
|
373
|
+
if (canonicalScreenName !== undefined && maxKillTime < currentKillTime) {
|
|
374
|
+
let newScreenName = getRetiringScreenName(canonicalScreenName, maxKillTime);
|
|
375
|
+
if (newScreenName !== screenName) {
|
|
376
|
+
console.log(green(`Pulling in retiring ${screenName} to ${newScreenName} (kill time ${new Date(currentKillTime).toISOString()} -> ${new Date(maxKillTime).toISOString()})`));
|
|
377
|
+
let pid = await getScreenPanePid(screenName);
|
|
378
|
+
// The pending timer is waiting on the OLD, later deadline; drop it so a fresh one runs against the new name's earlier deadline.
|
|
379
|
+
retireScreen.clear(screenName);
|
|
380
|
+
try {
|
|
381
|
+
await runPromise(`${getTmuxPrefix()}tmux rename-session -t ${screenName} ${newScreenName}`);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Another pass may have already renamed or killed it
|
|
384
|
+
console.log(`Could not rename retiring ${screenName} to ${newScreenName} (already renamed or gone?): ${(e as Error).stack ?? e}`);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (pid) {
|
|
388
|
+
let record = await findProcessRecordByPid(folder, pid);
|
|
389
|
+
if (record) {
|
|
390
|
+
await writeProcessRecord({ ...record, screenName: newScreenName });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
void retireScreen(newScreenName);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
void retireScreen(screenName);
|
|
398
|
+
}
|
|
399
|
+
|
|
359
400
|
/** Ends a session at once, with no notice - for a screen that should not exist at all, so there is nothing to wind down gracefully. Finding one is always a surprise, so the reason it was not supposed to be there is logged. */
|
|
360
401
|
export async function killScreenNow(screenName: string, reason: string): Promise<void> {
|
|
361
402
|
let panePid = await getScreenPanePid(screenName);
|