querysub 0.574.0 → 0.576.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
@@ -3,6 +3,7 @@ import { lazy } from "socket-function/src/caching";
3
3
  import { ArchivesBackblaze } from "sliftutils/storage/backblaze";
4
4
  import { STORAGE_DIR, getDomain, getBackblazePath } from "./src/misc/appPaths";
5
5
  import { Querysub } from "./src/4-querysub/Querysub";
6
+ import { formatDateTime } from "socket-function/src/formatting/format";
6
7
 
7
8
  const keysArchives = lazy(() => new ArchivesBackblaze({ bucketName: getDomain() }));
8
9
 
@@ -55,7 +56,7 @@ let doImmediateShutdown = lazy(() => {
55
56
  });
56
57
  }
57
58
  function doShutdown() {
58
- console.log("SIGINT received, shutting down immediately");
59
+ console.log(`SIGINT received, shutting down immediately (${formatDateTime(Date.now())})`);
59
60
  process.exit();
60
61
  }
61
62
  process.on("SIGINT", doShutdown);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.574.0",
3
+ "version": "0.576.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",
@@ -71,7 +71,7 @@
71
71
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
72
72
  "pako": "^2.1.0",
73
73
  "peggy": "^5.0.6",
74
- "sliftutils": "^1.7.43",
74
+ "sliftutils": "^1.7.45",
75
75
  "socket-function": "^1.2.26",
76
76
  "terser": "^5.31.0",
77
77
  "typenode": "^6.6.1",
@@ -104,6 +104,16 @@ export async function findProcessRecordByPid(folder: string, pid: string): Promi
104
104
  return (await readFolderRecords(folder)).find(x => x.pid === pid && x.deadTime === undefined);
105
105
  }
106
106
 
107
+ /** Marks a process dead now, for a process we killed ourselves. Without this the record reads as running until the next resync happens to look, which is a long time to show something that we know is gone. */
108
+ export async function markProcessDead(pid: string, now: number): Promise<void> {
109
+ for (let record of await listProcessRecords()) {
110
+ if (record.pid !== pid || record.deadTime !== undefined) continue;
111
+ record.deadTime = now;
112
+ console.log(`Process ${record.pid} (${record.screenName}) was killed, marking it dead. Its log is deleted at ${new Date(record.deadTime + DEAD_PROCESS_RETENTION).toISOString()}`);
113
+ await writeProcessRecord(record);
114
+ }
115
+ }
116
+
107
117
  export async function writeProcessRecord(record: ProcessRecord): Promise<void> {
108
118
  await fs.promises.mkdir(path.join(record.folder, PROCESS_FOLDER), { recursive: true });
109
119
  await fs.promises.writeFile(getRecordPath(record.folder, record.pid, record.startTime), JSON.stringify(record));
@@ -13,7 +13,7 @@ import { forceRemoveNode } from "../-f-node-discovery/NodeDiscovery";
13
13
  import { fsExistsAsync } from "../fs";
14
14
  import { PromiseObj } from "../promise";
15
15
  import { SERVICE_FOLDER, SERVICE_NODE_FILE_NAME } from "./machineSchema";
16
- import { ProcessRecord, getLogPipeScript, getLogTailScript, getProcessLogPath, getPipeScriptPath, getTailScriptPath, findProcessRecordByPid, writeProcessRecord } from "./processLogs";
16
+ import { ProcessRecord, getLogPipeScript, getLogTailScript, getProcessLogPath, getPipeScriptPath, getTailScriptPath, findProcessRecordByPid, writeProcessRecord, markProcessDead } from "./processLogs";
17
17
  import { setPreciseTimeout } from "../misc";
18
18
 
19
19
  // Running, inspecting and killing the tmux screens services run in. This layer only knows "here is a configuration, run it" - which version should be running when is the deploy logic's problem.
@@ -256,9 +256,11 @@ export const runScreenCommand = measureWrap(async function runScreenCommand(conf
256
256
  let screenName = config.screenName;
257
257
 
258
258
  // Always a brand new session, so the pane is always new - and the pane's pid is what makes this a distinct process with its own log. A session already under this name is debris from a start that never got taken over.
259
- if (await getScreenPanePid(screenName)) {
259
+ let leftoverPid = await getScreenPanePid(screenName);
260
+ if (leftoverPid) {
260
261
  console.log(red(`Removing the leftover session ${screenName} from a start that never completed`));
261
262
  await runPromise(`${prefix}tmux kill-session -t ${screenName}`);
263
+ await markProcessDead(leftoverPid, Date.now());
262
264
  }
263
265
  await runPromise(`${prefix}tmux new -s ${screenName} -d`);
264
266
 
@@ -339,21 +341,27 @@ export async function retireCurrentScreen(config: {
339
341
  let retiringScreenName = getRetiringScreenName(canonicalScreenName, killTime);
340
342
  console.log(green(`Retiring ${canonicalScreenName} as ${retiringScreenName}`));
341
343
  await runPromise(`${getTmuxPrefix()}tmux rename-session -t ${canonicalScreenName} ${retiringScreenName}`);
342
- let record = await findProcessRecordByPid(folder, outgoingPid);
343
- if (record) {
344
- await writeProcessRecord({ ...record, screenName: retiringScreenName });
345
- }
344
+ void (async () => {
345
+ let record = await findProcessRecordByPid(folder, outgoingPid);
346
+ if (record) {
347
+ await writeProcessRecord({ ...record, screenName: retiringScreenName });
348
+ }
349
+ })();
346
350
  if (nodeId) {
347
351
  // The file in the folder belongs to its replacement now, so only the registration goes
348
352
  void removeServiceNode({ folder, nodeId, ownsFile: false });
349
353
  }
354
+ // Started here rather than waiting for a resync to notice the screen: with a killTime already past - a zero overlap - this takes it down right now
355
+ void retireScreen(retiringScreenName);
350
356
  }
351
357
 
352
358
  /** 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. */
353
359
  export async function killScreenNow(screenName: string, reason: string): Promise<void> {
354
- if (!await getScreenPanePid(screenName)) return;
360
+ let panePid = await getScreenPanePid(screenName);
361
+ if (!panePid) return;
355
362
  console.log(red(`Unexpected screen ${screenName}: ${reason}. Killing it immediately.`));
356
363
  await runPromise(`${getTmuxPrefix()}tmux kill-session -t ${screenName}`);
364
+ await markProcessDead(panePid, Date.now());
357
365
  }
358
366
 
359
367
  // Kills a screen and nothing else. Which node the process registered as, and whether that registration should be dropped, is the deploy logic's business - it reads the node id off the process's record, which was taken before any replacement could overwrite the file.
@@ -362,10 +370,15 @@ export const killScreen = measureWrap(async function killScreen(config: {
362
370
  }) {
363
371
  console.log(red(`Killing screen ${config.screenName} (Ctrl+C, then killing the session in ${formatTime(SHUTDOWN_GRACE_TIME)})`));
364
372
  let prefix = getTmuxPrefix();
373
+ // Taken before the grace period, as the process is free to exit on the Ctrl+C and take the pane's pid with it
374
+ let panePid = await getScreenPanePid(config.screenName);
365
375
  // Purely a notice to the process that it is going away, so it can wind down. We never check whether it worked and we never reuse the screen - the session is killed either way.
366
- await runPromise(`${prefix}tmux send-keys -t ${config.screenName} 'C-c' Enter`);
376
+ void runPromise(`${prefix}tmux send-keys -t ${config.screenName} 'C-c' Enter`);
367
377
  await delay(SHUTDOWN_GRACE_TIME);
368
378
  await runPromise(`${prefix}tmux kill-session -t ${config.screenName}`);
379
+ if (panePid) {
380
+ await markProcessDead(panePid, Date.now());
381
+ }
369
382
  });
370
383
 
371
384
  /** Streams one process's log: everything already in it, then everything appended after. One process, one file - nothing here has to reason about which process a byte came from. */