agentic-workflow-manager 3.13.2 → 3.13.3
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.
|
@@ -275,17 +275,51 @@ function spawnStructured(argv, cwd, nonce, extraEnv = {}) {
|
|
|
275
275
|
* rompia el invariante "JAMAS safe sin evidencia" (safeToReplace
|
|
276
276
|
* devolvia 'safe' para un proceso vivo). process.kill(pid,0) evita la
|
|
277
277
|
* capa de emulacion por completo. */
|
|
278
|
+
/** Sleep sincronico REAL, multiplataforma, sin depender de un binario externo
|
|
279
|
+
* (`sleep` no existe nativamente en win32 — ver el comentario de sleepSync
|
|
280
|
+
* mas arriba). `Atomics.wait` sobre un buffer compartido bloquea el hilo
|
|
281
|
+
* actual durante `ms` sin abrir ningun subproceso; funciona identico en
|
|
282
|
+
* cualquier plataforma que soporte Node. Usado SOLO por pidExistsNative
|
|
283
|
+
* para el reintento acotado de abajo — nunca en un hot-path de alta
|
|
284
|
+
* frecuencia. */
|
|
285
|
+
function sleepMsSync(ms) {
|
|
286
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
287
|
+
}
|
|
288
|
+
/** Reintento acotado (R6, post-mortem de CI real): dos corridas reales de
|
|
289
|
+
* windows-latest sobre el MISMO commit (uno vía `release.yml`, uno vía
|
|
290
|
+
* `ci.yml`, ambos re-corriendo el suite completo desde cero) dieron
|
|
291
|
+
* resultados DISTINTOS para el mismo test — `pidExistsNative` reportando
|
|
292
|
+
* ESRCH para un proceso recien spawneado por el propio test, genuinamente
|
|
293
|
+
* vivo — evidencia directa de una condicion de carrera transitoria
|
|
294
|
+
* especifica de esta plataforma/runner, no un bug determinista (el codigo
|
|
295
|
+
* no cambio entre ambas corridas). Un solo intento en el hot-path exacto
|
|
296
|
+
* "acabo de spawnear esto, ¿esta vivo?" no le da tiempo al SO a que el pid
|
|
297
|
+
* sea consultable via OpenProcess bajo carga pesada del runner. Reintentar
|
|
298
|
+
* con una pausa breve ANTES de declarar ESRCH definitivo no debilita el
|
|
299
|
+
* invariante "jamas muerte sin evidencia" — un proceso genuinamente muerto
|
|
300
|
+
* sigue reportando ESRCH en el reintento; esto solo absorbe el falso
|
|
301
|
+
* negativo transitorio. Costo maximo: ~150ms, y SOLO en la rama que ya iba
|
|
302
|
+
* a declarar "no existe" — el camino feliz (proceso vivo, exito inmediato)
|
|
303
|
+
* no paga nada. */
|
|
304
|
+
const PID_EXISTS_RETRY_ATTEMPTS = 3;
|
|
305
|
+
const PID_EXISTS_RETRY_DELAY_MS = 50;
|
|
278
306
|
function pidExistsNative(pid) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
for (let attempt = 0; attempt < PID_EXISTS_RETRY_ATTEMPTS; attempt++) {
|
|
308
|
+
try {
|
|
309
|
+
process.kill(pid, 0);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
// ESRCH = el SO confirmo que el pid no existe. Cualquier otro
|
|
314
|
+
// codigo (ej. EPERM: existe pero sin permiso de senializarlo) NO
|
|
315
|
+
// es evidencia de muerte — falla a favor de "vivo" (R2.1).
|
|
316
|
+
if (error.code !== 'ESRCH')
|
|
317
|
+
return true;
|
|
318
|
+
if (attempt < PID_EXISTS_RETRY_ATTEMPTS - 1)
|
|
319
|
+
sleepMsSync(PID_EXISTS_RETRY_DELAY_MS);
|
|
320
|
+
}
|
|
288
321
|
}
|
|
322
|
+
return false;
|
|
289
323
|
}
|
|
290
324
|
/** Vivo Y con la MISMA identidad — tupla completa, nunca PID solo (R2.1,
|
|
291
325
|
* bloqueador 6): startTime + pgid + digest de ps args.
|
|
@@ -237,6 +237,22 @@ describe('process identity (win32, mockeado — sin windows real disponible en e
|
|
|
237
237
|
expect((0, process_1.refIsAlive)(fakeRef)).toBe(false);
|
|
238
238
|
expect(killSpy).toHaveBeenCalledWith(999999, 0);
|
|
239
239
|
});
|
|
240
|
+
test('pidExistsNative reintenta un ESRCH transitorio antes de declarar muerte — no confia en un unico intento (regresion: CI real windows-latest, dos corridas del mismo commit dieron resultados distintos para un proceso recien spawneado)', () => {
|
|
241
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
242
|
+
let calls = 0;
|
|
243
|
+
const killSpy = jest.spyOn(process, 'kill').mockImplementation(() => {
|
|
244
|
+
calls++;
|
|
245
|
+
if (calls < 3) {
|
|
246
|
+
const err = new Error('transient');
|
|
247
|
+
err.code = 'ESRCH';
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
250
|
+
return true; // el pid "aparece" recien al tercer intento — simula la carrera real observada
|
|
251
|
+
});
|
|
252
|
+
const fakeRef = { pid: 424242, startTime: 'x', spawnNonce: 'n', argvDigest: 'd', processGroup: 424242, psArgsDigest: 'x' };
|
|
253
|
+
expect((0, process_1.refIsAlive)(fakeRef)).toBe(true); // NUNCA declara muerte por el ESRCH transitorio de los primeros 2 intentos
|
|
254
|
+
expect(killSpy).toHaveBeenCalledTimes(3);
|
|
255
|
+
});
|
|
240
256
|
test('refIsAlive en win32 NUNCA declara muerte por un error que no sea ESRCH (ej. EPERM: el pid existe pero sin permiso de senializarlo) (R2.1)', () => {
|
|
241
257
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
242
258
|
jest.spyOn(process, 'kill').mockImplementation(() => {
|