querysub 0.566.0 → 0.568.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/appSecrets.ts CHANGED
@@ -24,7 +24,7 @@ export const getCloudflareCreds = lazy(async (): Promise<{ key: string; email: s
24
24
  /** Serves the secret keys sliftutils' getSecret expects, reading them the way querysub already does: backblaze creds off disk (getBackblazePath), cloudflare creds from the keys archives bucket (with its local file fallback). */
25
25
  export async function getAppSecret(key: string): Promise<string | undefined> {
26
26
  // Import query sub so our logs go to our actual log server. Otherwise we won't know if we get any errors.
27
- //Querysub;
27
+ Querysub;
28
28
  if (key.startsWith("backblaze.json.")) {
29
29
  let creds = JSON.parse(fs.readFileSync(getBackblazePath(), "utf8")) as { applicationKeyId?: string; applicationKey?: string };
30
30
  if (key === "backblaze.json.applicationKeyId") return creds.applicationKeyId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.566.0",
3
+ "version": "0.568.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",
@@ -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, getFutureScreenName, getTmuxPrefix, getScreenState, isScreenRunningProcess, getProcessStartTime, ensureProcessRecord, runScreenCommand, killScreen, streamProcessOutput } from "./processManager";
29
+ import { getScreenName, getFutureScreenName, getRetiringScreenName, getTmuxPrefix, getScreenState, isScreenRunningProcess, getProcessStartTime, ensureProcessRecord, runScreenCommand, killScreen, streamProcessOutput } from "./processManager";
30
30
  import { ProcessRecord, syncProcessRecords, writeProcessRecord, listProcessRecords, getProcessLogPath } from "./processLogs";
31
31
 
32
32
 
@@ -339,11 +339,17 @@ async function takeoverFutureScreen(config: {
339
339
  // The rename makes the future name vanish, but the caller's original screen list still has it — mark it used so the unused-screen cleanup doesn't try to kill a nonexistent session
340
340
  config.screenNamesUsed.add(futureScreenName);
341
341
  if (screenStateMap.has(canonicalScreenName)) {
342
- await killScreen({ screenName: canonicalScreenName, skipNodeIdRemoval: true });
342
+ // The old screen is moved aside rather than waited on: the canonical name is free immediately, and its shutdown (Ctrl+C, then the hard kill) runs on its own without holding up this service or the next one.
343
+ let retiringScreenName = getRetiringScreenName(canonicalScreenName, Date.now());
344
+ let prefix = getTmuxPrefix();
345
+ await runPromise(`${prefix}tmux rename-session -t ${canonicalScreenName} ${retiringScreenName}`);
343
346
  screenStateMap.delete(canonicalScreenName);
347
+ // Kept out of the unused-screen sweep, which would kill it a second time while it is already winding down
348
+ config.screenNamesUsed.add(retiringScreenName);
349
+ void killScreen({ screenName: retiringScreenName, skipNodeIdRemoval: true });
344
350
  let oldNodeId = takeoverOldNodeIds.get(canonicalScreenName);
345
351
  if (oldNodeId) {
346
- await forceRemoveNode(oldNodeId);
352
+ void forceRemoveNode(oldNodeId);
347
353
  }
348
354
  }
349
355
  takeoverOldNodeIds.delete(canonicalScreenName);
@@ -429,7 +435,8 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
429
435
  }
430
436
  }
431
437
 
432
- for (let { record, config, releaseTime, overlapTime, killTime, releaseInFlight, isPrepTime, isOverlapTime } of recordStates) {
438
+ // Services run concurrently: each has its own folders, its own screens and its own records, and yarn install takes a network mutex, so there is nothing for them to serialize behind. One slow service (a git sync, a yarn install, a screen taking its full shutdown grace) no longer delays every service after it.
439
+ await Promise.all(recordStates.map(async ({ record, config, releaseTime, overlapTime, killTime, releaseInFlight, isPrepTime, isOverlapTime }) => {
433
440
  if (releaseInFlight) {
434
441
  upcomingReleases.push({ releaseTime, overlapTime });
435
442
  console.log(magenta(`Release in flight for ${record.serviceId} (${record.parameters.key}): the new version starts at ${new Date(releaseTime).toLocaleString()}${isOverlapTime && " (overlap running)" || ""}, the old instances are killed at ${new Date(killTime).toLocaleString()}`));
@@ -460,7 +467,7 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
460
467
  }
461
468
  }
462
469
  }
463
- if (!config.parameters.deploy) continue;
470
+ if (!config.parameters.deploy) return;
464
471
  let matchedTargets = getMachineTargets(config.parameters).filter(target => target.machineId === machineId);
465
472
  for (let i = 0; i < matchedTargets.length; i++) {
466
473
  // Each instance runs the command with its own entry's template variables resolved, so the per-instance parameters (written to parameters.json and compared for restarts) carry the resolved command.
@@ -512,13 +519,13 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
512
519
  };
513
520
 
514
521
  if (releaseInFlight) {
515
- // Tell the running instance when it will be shut down, so it (and everything talking to it) can wind down gracefully. Once the future instance may have started, the folder's nodeId file can belong to the NEW process, so from then on only the stashed old nodeId is safe to notify.
522
+ // Tell the running instance when it will be shut down, so it (and everything talking to it) can wind down gracefully. Fire and forget: it is a courtesy to the process, and nothing about taking it down may wait on a call to the process we are taking down. Once the future instance may have started, the folder's nodeId file can belong to the NEW process, so from then on only the stashed old nodeId is safe to notify.
516
523
  if (!isOverlapTime) {
517
- await notifyServiceShutdown(screenName, killTime);
524
+ void notifyServiceShutdown(screenName, killTime);
518
525
  } else {
519
526
  let oldNodeId = takeoverOldNodeIds.get(screenName);
520
527
  if (oldNodeId) {
521
- await notifyNodeShutdown(screenName, oldNodeId, killTime);
528
+ void notifyNodeShutdown(screenName, oldNodeId, killTime);
522
529
  }
523
530
  }
524
531
  } else {
@@ -690,11 +697,12 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
690
697
  lastLaunchedTimePerService.set(config.serviceId, lastLaunchedTime);
691
698
  launchesPerService.set(config.serviceId, launchCount);
692
699
  }
693
- }
700
+ }));
694
701
 
695
702
  for (let { screenName } of screenState) {
696
703
  if (screenNamesUsed.has(screenName)) continue;
697
- await killScreen({
704
+ // Detached, so a screen that takes its full grace period to go down doesn't hold up the end of the resync
705
+ void killScreen({
698
706
  screenName,
699
707
  });
700
708
  }
@@ -24,6 +24,10 @@ const SCREEN_SUFFIX = "-dply";
24
24
  export function getScreenName(config: { serviceKey: string; index: number }): string {
25
25
  return `${config.serviceKey}-${config.index}${SCREEN_SUFFIX}`.replace(/[^a-zA-Z0-9\-_]/g, "_");
26
26
  }
27
+ /** The name an outgoing screen is moved to so the new process can take the canonical name at once, instead of anything waiting for the old process to wind down. Keeps the suffix, so it is still a screen we know about. */
28
+ export function getRetiringScreenName(canonicalScreenName: string, at: number): string {
29
+ return canonicalScreenName.slice(0, -SCREEN_SUFFIX.length) + `-retiring-${at}` + SCREEN_SUFFIX;
30
+ }
27
31
  // The new version's screen during a release, in the SAME folder as the canonical screen: created (just echoing when it will start) shortly before releaseTime, started at releaseTime, and renamed to the canonical screen name once the old screen is killed at releaseTime + overlapTime.
28
32
  export function getFutureScreenName(canonicalScreenName: string): string {
29
33
  return canonicalScreenName.slice(0, -SCREEN_SUFFIX.length) + "-future" + SCREEN_SUFFIX;