querysub 0.572.0 → 0.573.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
|
@@ -221,6 +221,8 @@ let launchesPerService = new Map<string, number>();
|
|
|
221
221
|
let lastLaunchedTimePerService = new Map<string, number>();
|
|
222
222
|
|
|
223
223
|
const LAUNCH_VERIFY_DELAY = 2000;
|
|
224
|
+
// How far a re-read start time may drift from the recorded one and still be the same process. Well under any interval a pid could be reused in, and wide enough for sources that only resolve to the second.
|
|
225
|
+
const START_TIME_TOLERANCE = timeInSecond * 5;
|
|
224
226
|
|
|
225
227
|
/** Every instance of every service that belongs to this machine, with its config already resolved for that instance - the command has this entry's template variables substituted, and a version with no entry at this index is marked as deploying nothing here. */
|
|
226
228
|
function getMachineInstances(configs: ServiceConfig[], machineId: string): { config: ServiceConfig; index: number }[] {
|
|
@@ -497,7 +499,8 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
497
499
|
await syncProcessRecords({
|
|
498
500
|
isAlive: async record => {
|
|
499
501
|
if (!running.has(record.pid)) return false;
|
|
500
|
-
|
|
502
|
+
// Compared with a tolerance rather than exactly: the sources we read a start time from only resolve to the second, and they do not all round the same way, so an exact match would call a live process dead
|
|
503
|
+
return Math.abs(await getProcessStartTime(record.pid) - record.startTime) <= START_TIME_TOLERANCE;
|
|
501
504
|
},
|
|
502
505
|
now: Date.now(),
|
|
503
506
|
});
|
|
@@ -99,6 +99,11 @@ export async function readProcessRecord(folder: string, pid: string, startTime:
|
|
|
99
99
|
return JSON.parse(await fs.promises.readFile(file, "utf8")) as ProcessRecord;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/** The live record for a pid in this folder. The pid is what identifies a process, so a start time that reads back a second off - the sources only resolve to the second - must not make us miss the record and write a duplicate. */
|
|
103
|
+
export async function findProcessRecordByPid(folder: string, pid: string): Promise<ProcessRecord | undefined> {
|
|
104
|
+
return (await readFolderRecords(folder)).find(x => x.pid === pid && x.deadTime === undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
export async function writeProcessRecord(record: ProcessRecord): Promise<void> {
|
|
103
108
|
await fs.promises.mkdir(path.join(record.folder, PROCESS_FOLDER), { recursive: true });
|
|
104
109
|
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,
|
|
16
|
+
import { ProcessRecord, getLogPipeScript, getLogTailScript, getProcessLogPath, getPipeScriptPath, getTailScriptPath, findProcessRecordByPid, writeProcessRecord } 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.
|
|
@@ -163,11 +163,23 @@ const getAllParentPids = measureWrap(async function getAllParentPids(): Promise<
|
|
|
163
163
|
let obj = textTableLineToObj(table) as { PID: string; PPID: string }[];
|
|
164
164
|
return new Set(obj.map(x => x.PPID));
|
|
165
165
|
});
|
|
166
|
-
/** The start time the OS reports for a pid, as an epoch time in milliseconds like every other time we store. Asked of the machine rather than remembered, so it stays true across our restarts and catches a reused pid. Returns 0 when the process is gone. */
|
|
166
|
+
/** The start time the OS reports for a pid, as an epoch time in milliseconds like every other time we store. Asked of the machine rather than remembered, so it stays true across our restarts and catches a reused pid. Returns 0 when the process is gone, or when neither source can answer. */
|
|
167
167
|
export const getProcessStartTime = measureWrap(async function getProcessStartTime(pid: string): Promise<number> {
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
// /proc/<pid> is created when the process is, so its ctime IS the start time - no date parsing, and nothing to get wrong about locales or ps output formats
|
|
169
|
+
try {
|
|
170
|
+
let started = await runPromise(`stat -c %Y /proc/${pid}`, { quiet: true });
|
|
171
|
+
let seconds = parseInt(started.trim()) || 0;
|
|
172
|
+
if (seconds) return seconds * 1000;
|
|
173
|
+
} catch {
|
|
174
|
+
// No procfs (or the process is gone), so fall through to ps
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
// Second-accurate, which is plenty to tell two processes on the same pid apart
|
|
178
|
+
let started = await runPromise(`date -d "$(ps -o lstart= -p ${pid})" +%s`, { quiet: true });
|
|
179
|
+
return (parseInt(started.trim()) || 0) * 1000;
|
|
180
|
+
} catch {
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
171
183
|
});
|
|
172
184
|
|
|
173
185
|
export const isScreenRunningProcess = measureWrap(async function isScreenRunningProcess(pid: string): Promise<boolean> {
|
|
@@ -263,9 +275,9 @@ export async function ensureProcessRecord(config: {
|
|
|
263
275
|
record: Omit<ProcessRecord, "pid" | "startTime" | "folder" | "screenName">;
|
|
264
276
|
}): Promise<void> {
|
|
265
277
|
let pid = config.panePid;
|
|
278
|
+
// A start time we could not read is still recorded: a process with no record at all is invisible everywhere, which is worse than one whose identity is only its pid
|
|
266
279
|
let startTime = await getProcessStartTime(pid);
|
|
267
|
-
|
|
268
|
-
let existing = await readProcessRecord(config.folder, pid, startTime);
|
|
280
|
+
let existing = await findProcessRecordByPid(config.folder, pid);
|
|
269
281
|
if (existing) {
|
|
270
282
|
// A takeover renames the session, so the record follows the name it now runs under
|
|
271
283
|
if (existing.screenName === config.screenName) return;
|
|
@@ -340,9 +352,7 @@ export async function takeoverScreenLocked(config: {
|
|
|
340
352
|
|
|
341
353
|
/** Points a process's record at the session name it now runs under. The pid is the key, so this reads exactly one file. */
|
|
342
354
|
async function renameProcessScreen(config: { folder: string; panePid: string; screenName: string }): Promise<void> {
|
|
343
|
-
let
|
|
344
|
-
if (!startTime) return;
|
|
345
|
-
let record = await readProcessRecord(config.folder, config.panePid, startTime);
|
|
355
|
+
let record = await findProcessRecordByPid(config.folder, config.panePid);
|
|
346
356
|
if (!record || record.screenName === config.screenName) return;
|
|
347
357
|
await writeProcessRecord({ ...record, screenName: config.screenName });
|
|
348
358
|
}
|