@@ -444,16 +444,51 @@ export function runNodeStep(label, args, timeoutMs, params = {}) {
|
444 | 444 | let settled = false; |
445 | 445 | let canceled = false; |
446 | 446 | let killTimer; |
| 447 | +let killDeadlineAt = 0; |
447 | 448 | const stdoutWriter = createPrefixedOutputWriter(label, process.stdout); |
448 | 449 | const stderrWriter = createPrefixedOutputWriter(label, process.stderr); |
449 | 450 | const killNodeStep = (signal) => signalNodeStep(child, signal); |
| 451 | +const processGroupAlive = () => { |
| 452 | +if (process.platform === "win32" || !child.pid) { |
| 453 | +return false; |
| 454 | +} |
| 455 | +try { |
| 456 | +process.kill(-child.pid, 0); |
| 457 | +return true; |
| 458 | +} catch (error) { |
| 459 | +return Boolean(error && error.code === "EPERM"); |
| 460 | +} |
| 461 | +}; |
| 462 | +const waitForProcessGroupExit = async (waitMs) => { |
| 463 | +const deadlineAt = Date.now() + waitMs; |
| 464 | +while (Date.now() < deadlineAt) { |
| 465 | +if (!processGroupAlive()) { |
| 466 | +return true; |
| 467 | +} |
| 468 | +await new Promise((resolvePoll) => { |
| 469 | +setTimeout(resolvePoll, 25); |
| 470 | +}); |
| 471 | +} |
| 472 | +return !processGroupAlive(); |
| 473 | +}; |
| 474 | +const waitForCanceledStepTeardown = async () => { |
| 475 | +const remainingGraceMs = Math.max(0, killDeadlineAt - Date.now()); |
| 476 | +if (remainingGraceMs > 0) { |
| 477 | +await waitForProcessGroupExit(remainingGraceMs); |
| 478 | +} |
| 479 | +if (processGroupAlive()) { |
| 480 | +killNodeStep("SIGKILL"); |
| 481 | +await waitForProcessGroupExit(100); |
| 482 | +} |
| 483 | +}; |
450 | 484 | ACTIVE_NODE_STEP_KILLERS.add(killNodeStep); |
451 | 485 | const abortStep = () => { |
452 | 486 | if (settled || canceled) { |
453 | 487 | return; |
454 | 488 | } |
455 | 489 | canceled = true; |
456 | 490 | killNodeStep("SIGTERM"); |
| 491 | +killDeadlineAt = Date.now() + NODE_STEP_ABORT_KILL_GRACE_MS; |
457 | 492 | killTimer = setTimeout(() => { |
458 | 493 | killTimer = undefined; |
459 | 494 | killNodeStep("SIGKILL"); |
@@ -508,27 +543,29 @@ export function runNodeStep(label, args, timeoutMs, params = {}) {
|
508 | 543 | if (settled) { |
509 | 544 | return; |
510 | 545 | } |
511 | | -settled = true; |
512 | | -stdoutWriter.flush(); |
513 | | -stderrWriter.flush(); |
514 | | -if (exitingAfterParentSignal) { |
515 | | -killNodeStep("SIGKILL"); |
516 | | -cleanup(); |
517 | | -return; |
518 | | -} |
519 | | -if (canceled) { |
520 | | -killNodeStep("SIGKILL"); |
| 546 | +void (async () => { |
| 547 | +settled = true; |
| 548 | +stdoutWriter.flush(); |
| 549 | +stderrWriter.flush(); |
| 550 | +if (exitingAfterParentSignal) { |
| 551 | +killNodeStep("SIGKILL"); |
| 552 | +cleanup(); |
| 553 | +return; |
| 554 | +} |
| 555 | +if (canceled) { |
| 556 | +await waitForCanceledStepTeardown(); |
| 557 | +cleanup(); |
| 558 | +rejectPromise(new Error(`${label} canceled after sibling failure`)); |
| 559 | +return; |
| 560 | +} |
521 | 561 | cleanup(); |
522 | | -rejectPromise(new Error(`${label} canceled after sibling failure`)); |
523 | | -return; |
524 | | -} |
525 | | -cleanup(); |
526 | | -if (code === 0) { |
527 | | -resolvePromise(); |
528 | | -return; |
529 | | -} |
530 | | -abortSiblingSteps(abortController); |
531 | | -rejectPromise(new Error(`${label} failed with exit code ${code ?? 1}`)); |
| 562 | +if (code === 0) { |
| 563 | +resolvePromise(); |
| 564 | +return; |
| 565 | +} |
| 566 | +abortSiblingSteps(abortController); |
| 567 | +rejectPromise(new Error(`${label} failed with exit code ${code ?? 1}`)); |
| 568 | +})(); |
532 | 569 | }); |
533 | 570 | }); |
534 | 571 | } |
|