Skip to content

test(events): regression guard for #5056 AbortSignal exit-hang#5190

Merged
proggeramlug merged 1 commit into
mainfrom
fix/issue-5056-events-abort-exit-hang
Jun 15, 2026
Merged

test(events): regression guard for #5056 AbortSignal exit-hang#5190
proggeramlug merged 1 commit into
mainfrom
fix/issue-5056-events-abort-exit-hang

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

events.once / events.on with an AbortSignal used to leave a leaked keepalive (a pending promise / abort listener) parking the event loop — programs printed the correct output, then never exited (#5056).

Re-verified on current main: both repros now exit 0 and match node --experimental-strip-types byte-for-byte. The runtime fix landed incidentally with the events.on async-iterator rework (#5099); the underlying events.{once,on} abort paths clean up their listeners and don't register an active handle. So no runtime change is needed here — this PR adds the regression guard that was missing.

Why a dedicated guard

The node-suite differential runner (scripts/node_suite_run.py) does compare exit codes and time out on hangs, but:

  • a hang there only surfaces as a timeout → perry_err, and
  • that runner is not part of the per-PR CI gate.

So an exit-hang regression on these two fixtures could merge green. This stand-alone script — same shape as tests/test_issue_3730_timers_promises_unref_await.sh — pins the behaviour directly: it compiles both abort fixtures and asserts each exits 0 within a timeout with the expected stdout. A hang trips the timeout (exit 124) and fails the test.

Fixtures guarded:

  • test-parity/node-suite/events/once/abort-cleans-pending-error.ts
  • test-parity/node-suite/events/on/async-iterator-abort.ts

Test

$ bash tests/test_issue_5056_events_abort_exit.sh
  ok: once-abort
  ok: on-abort
PASS

Closes #5056.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added regression tests for event handling with AbortSignal to prevent event-loop hangs and ensure proper abort semantics are maintained.

events.once / events.on with an AbortSignal used to leave a leaked
keepalive (pending promise / abort listener) parking the event loop, so
the program produced correct output then never exited. The runtime fix
landed incidentally with the events.on async-iterator rework (#5099);
this adds an explicit guard so it cannot silently regress.

The node-suite differential runner does check exit codes, but a hang
only surfaces as a timeout there and that runner is not part of the
per-PR gate. This stand-alone script (same shape as
tests/test_issue_3730_timers_promises_unref_await.sh) compiles both
abort fixtures and asserts each exits 0 within a timeout with the
expected stdout — a hang trips the timeout (exit 124) and fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new standalone Bash regression test tests/test_issue_5056_events_abort_exit.sh is added. It locates the perry binary, implements timeout and compile-run-assert helpers, and executes two cases verifying that programs using events.once and events.on with AbortSignal terminate cleanly without hanging the event loop.

Changes

AbortSignal exit regression test

Layer / File(s) Summary
Script bootstrap and perry binary discovery
tests/test_issue_5056_events_abort_exit.sh
Sets strict Bash options, resolves the perry binary path preferring target/release over target/debug, exits with SKIP when the binary is absent, and creates a temp directory with an EXIT trap for cleanup.
run_with_timeout helper
tests/test_issue_5056_events_abort_exit.sh
Defines run_with_timeout using system timeout/gtimeout when available; otherwise runs the command in the background with a watcher process that enforces termination and maps hangs to exit code 124.
check_case compile-run-assert helper
tests/test_issue_5056_events_abort_exit.sh
Compiles a fixture into a temp binary with PERRY_ environment flags, runs it under an 8-second timeout, and fails distinctly on hang (rc==124), non-zero exit, or stdout mismatch, printing ok on success.
once-abort and on-abort regression cases
tests/test_issue_5056_events_abort_exit.sh
Invokes check_case for the two concrete AbortSignal fixtures with their expected stdout strings and prints PASS after both succeed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A signal was sent, the loop should have stopped,
But the keepalive lingered — the process was locked.
So I scribbled a script with a timeout in hand,
To compile and run and then make a stand:
once-abort, on-abort — both exit as planned!
Now PASS prints with pride on the terminal screen,
The cleanest of exits the bunny has seen. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change—adding a regression test for issue #5056 that guards against AbortSignal exit-hang in events.once/on.
Description check ✅ Passed The description covers all required sections: summary, changes, related issue (#5056), test plan with verification steps, and adheres to the checklist requirements.
Linked Issues check ✅ Passed The PR directly addresses issue #5056 by adding a regression guard that compiles the two affected fixtures and verifies they exit cleanly with correct output, preventing silent regressions.
Out of Scope Changes check ✅ Passed All changes are in-scope: a single test script that implements the regression guard for #5056 with no modifications to runtime or other unrelated code.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-5056-events-abort-exit-hang

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_issue_5056_events_abort_exit.sh`:
- Around line 39-50: The exit code normalization in the
wait_for_output_or_timeout function currently only maps SIGTERM (exit code 143)
to the timeout indicator 124, but does not handle SIGKILL (exit code 137).
Modify the condition that checks whether rc equals 143 to also check for 137, so
both SIGTERM and SIGKILL process terminations are normalized to return 124,
ensuring consistent timeout/hang classification regardless of which termination
signal was ultimately required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65468740-93d6-4bc7-8aae-cf8f1db3f302

📥 Commits

Reviewing files that changed from the base of the PR and between 9343f63 and 8e61bd3.

📒 Files selected for processing (1)
  • tests/test_issue_5056_events_abort_exit.sh

Comment on lines +39 to +50
( sleep "$secs" && kill -TERM "$pid" 2>/dev/null && sleep 1 && kill -KILL "$pid" 2>/dev/null ) &
local watcher=$!
if wait "$pid" 2>/dev/null; then
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
return 0
fi
local rc=$?
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
[[ "$rc" == "143" ]] && return 124
return "$rc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize SIGKILL timeout exits to 124 as well.

The fallback timeout path currently maps only 143 (SIGTERM) to 124. On hangs that need SIGKILL, wait returns 137, so the script reports a generic non-zero exit instead of a timeout/hang classification.

Suggested patch
-    [[ "$rc" == "143" ]] && return 124
+    [[ "$rc" == "143" || "$rc" == "137" ]] && return 124
     return "$rc"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
( sleep "$secs" && kill -TERM "$pid" 2>/dev/null && sleep 1 && kill -KILL "$pid" 2>/dev/null ) &
local watcher=$!
if wait "$pid" 2>/dev/null; then
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
return 0
fi
local rc=$?
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
[[ "$rc" == "143" ]] && return 124
return "$rc"
( sleep "$secs" && kill -TERM "$pid" 2>/dev/null && sleep 1 && kill -KILL "$pid" 2>/dev/null ) &
local watcher=$!
if wait "$pid" 2>/dev/null; then
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
return 0
fi
local rc=$?
kill -TERM "$watcher" 2>/dev/null || true
wait "$watcher" 2>/dev/null || true
[[ "$rc" == "143" || "$rc" == "137" ]] && return 124
return "$rc"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_issue_5056_events_abort_exit.sh` around lines 39 - 50, The exit
code normalization in the wait_for_output_or_timeout function currently only
maps SIGTERM (exit code 143) to the timeout indicator 124, but does not handle
SIGKILL (exit code 137). Modify the condition that checks whether rc equals 143
to also check for 137, so both SIGTERM and SIGKILL process terminations are
normalized to return 124, ensuring consistent timeout/hang classification
regardless of which termination signal was ultimately required.

@proggeramlug proggeramlug merged commit 6304804 into main Jun 15, 2026
14 of 15 checks passed
@proggeramlug proggeramlug deleted the fix/issue-5056-events-abort-exit-hang branch June 15, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

events.once/events.on with AbortSignal: process never exits after completion (event loop parks on a leaked keepalive)

1 participant