@north-light/crouter 0.3.168 → 0.3.169

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.
@@ -25,6 +25,17 @@ function run(command, args, cwd, capture = false, env = process.env) {
25
25
  });
26
26
  }
27
27
 
28
+ /** Capture a probe's stdout even when a candidate process exits during the probe. */
29
+ function probe(command, args, cwd) {
30
+ return new Promise((resolveRun) => {
31
+ const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
32
+ let output = '';
33
+ child.stdout.on('data', (chunk) => { output += chunk; });
34
+ child.on('error', () => resolveRun(output));
35
+ child.on('exit', () => resolveRun(output));
36
+ });
37
+ }
38
+
28
39
  function digest(value) {
29
40
  return createHash('sha256').update(value).digest('hex');
30
41
  }
@@ -146,32 +157,44 @@ async function pack(archiveDir) {
146
157
  return join(archiveDir, packed[0].filename);
147
158
  }
148
159
 
160
+ /** Generation ids named anywhere in a `ps` or `lsof` dump. */
149
161
  function generationIdsIn(text) {
150
- return [...text.matchAll(/generations\/([a-f0-9]{64})(?:\/|$)/g)].map((match) => match[1]);
162
+ return [...text.matchAll(/generations\/([a-f0-9]{64})(?:[/\s]|$)/g)].map((match) => match[1]);
151
163
  }
152
164
 
165
+ /** Return every generation used by a running process and whether every stable
166
+ * selector process exposed its generation lease. */
153
167
  async function liveGenerationIds() {
154
168
  const output = await run('ps', ['-axww', '-o', 'pid=,command='], RUNTIME_HOME, true);
155
169
  const live = new Set(generationIdsIn(output));
156
170
  const nodePids = [];
157
- let uncertain = false;
171
+ const opaqueStablePids = new Set();
158
172
  for (const line of output.split('\n')) {
159
173
  const match = /^\s*(\d+)\s+(.*)$/.exec(line);
160
174
  if (!match) continue;
161
175
  const [, pid, command] = match;
162
- if (/(?:^|\s)\S*\/bin\/(?:crtr|crtrd|crouter)(?:\s|$)/.test(command)) uncertain = true;
176
+ const stableSelector = /^(?:\S*node\s+)?\S*\/bin\/(?:crtr|crtrd|crouter)(?:\s|$)/.test(command);
177
+ if (stableSelector && generationIdsIn(command).length === 0) opaqueStablePids.add(pid);
163
178
  if (/(?:^|[\s/])(?:node|crtrd?|crouter)(?:[\s]|$)/.test(command)) nodePids.push(pid);
164
179
  }
165
- // An opaque stable shim means GC will skip regardless, so do not spend
166
- // minutes resolving process cwd values that cannot change that decision.
167
- if (uncertain || nodePids.length === 0) return { live, uncertain };
180
+ if (nodePids.length === 0) return { live, uncertain: opaqueStablePids.size > 0 };
168
181
 
169
- // argv identifies entry modules; cwd covers a node process launched from a
170
- // generation directory. Ask lsof for every candidate in ONE invocation:
171
- // starting lsof once per process costs ~0.5s on macOS, which turned hundreds
172
- // of live agent/tool processes into a multi-minute post-install stall.
173
- const cwd = await run('lsof', ['-n', '-P', '-a', '-p', nodePids.join(','), '-d', 'cwd', '-Fn'], RUNTIME_HOME, true);
174
- for (const id of generationIdsIn(cwd)) live.add(id);
182
+ // One lsof invocation resolves cwd references and the runtime entry-file
183
+ // lease held by each stable CLI process without a per-process startup penalty.
184
+ const open = await probe('lsof', ['-n', '-P', '-a', '-p', nodePids.join(','), '-Fn'], RUNTIME_HOME);
185
+ let pid;
186
+ const exposedStablePids = new Set();
187
+ for (const line of open.split('\n')) {
188
+ const processMatch = /^p(\d+)$/.exec(line);
189
+ if (processMatch) {
190
+ pid = processMatch[1];
191
+ continue;
192
+ }
193
+ const ids = generationIdsIn(line);
194
+ for (const id of ids) live.add(id);
195
+ if (pid !== undefined && ids.length > 0 && opaqueStablePids.has(pid)) exposedStablePids.add(pid);
196
+ }
197
+ const uncertain = [...opaqueStablePids].some((stablePid) => !exposedStablePids.has(stablePid));
175
198
  return { live, uncertain };
176
199
  }
177
200
 
@@ -186,7 +209,7 @@ async function pruneGenerations() {
186
209
  const selected = await selectedGenerationId();
187
210
  const { live, uncertain } = await liveGenerationIds();
188
211
  if (uncertain) {
189
- process.stderr.write('runtime generation GC skipped: a live stable crouter shim does not expose its generation path\n');
212
+ process.stderr.write('runtime generation GC skipped: a live stable crouter shim does not expose its generation lease\n');
190
213
  return;
191
214
  }
192
215
  const entries = await readdir(GENERATIONS, { withFileTypes: true });