Skip to content

fix(review): exit immediately on the second signal - #1185

Open
HOWILLMAKEIT wants to merge 1 commit into
alibaba:mainfrom
HOWILLMAKEIT:fix/second-signal-exit
Open

fix(review): exit immediately on the second signal#1185
HOWILLMAKEIT wants to merge 1 commit into
alibaba:mainfrom
HOWILLMAKEIT:fix/second-signal-exit

Conversation

@HOWILLMAKEIT

@HOWILLMAKEIT HOWILLMAKEIT commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Description

ocr review registered SIGINT/SIGTERM via signal.NotifyContext. Its watcher goroutine exits after the first signal without unregistering, so during the entire graceful-shutdown window every further signal lands in an unread channel and is dropped — a second Ctrl+C does nothing, however long the cleanup takes.

This change replaces NotifyContext with an owned signal channel in cmd/opencodereview/interrupt.go:

  • the first signal cancels the context, running the existing graceful-shutdown defer chain;
  • a second signal force-exits the process immediately, since the user has explicitly abandoned the graceful path;
  • a late signal arriving after shutdown already completed is dropped rather than turning a successful run into exit 1;
  • stop() is idempotent and mirrors NotifyContext's stop semantics.

scan_cmd.go is untouched; it registers no signal handling today.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring (no functional changes)
  • Documentation update
  • CI / Build / Tooling

How Has This Been Tested?

  • cmd/opencodereview/interrupt_test.go covers first-signal graceful cancellation (SIGINT and SIGTERM), second-signal forced exit, stop/parent-cancel releasing the watcher, and two subprocess-level integration tests exercising the real os.Exit path end to end.

  • The second-signal integration test asserts the child exits in under 100 ms with status 1; the single-signal test asserts the partial report lands on disk and the child exits 0.

  • make test passes locally

  • Manual testing: verified the second-signal exit path and the single-signal graceful path via the subprocess integration tests

Checklist

  • My code follows the project's coding style (go fmt, go vet)
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective or my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly (if applicable)
  • I have signed the CLA

Related Issues

Related to #1141 (item 3).

@HOWILLMAKEIT
HOWILLMAKEIT force-pushed the fix/second-signal-exit branch 2 times, most recently from 7c9ee90 to eb0bd68 Compare September 5, 2026 17:45

@Qiyuanqiii Qiyuanqiii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking issue in the forced-exit path; details are in the inline comment.

Comment thread cmd/opencodereview/interrupt.go Outdated
review_cmd.go registered SIGINT/SIGTERM via signal.NotifyContext. Its
watcher goroutine receives the first signal, cancels the context and
exits -- without unregistering the channel: stop() only runs in the
caller's defer, after the whole graceful shutdown has finished. For
that entire window (report flush, retry-report freeze, session
persist, MCP client close) every further signal lands in the stdlib's
unread size-one buffer and is dropped while the default kill behavior
stays suppressed, so a second Ctrl+C does nothing however long the
cleanup takes.

Replace NotifyContext with an owned signal channel: the first signal
cancels the context exactly as before, and the watcher now stays alive
across the whole shutdown window; a second signal force-exits the
process at once with status 1, skipping the remaining cleanup -- the
user has explicitly abandoned the graceful path. A signal queued while
stop() was closing done must not turn an already-successful run into
exit 1, so the watcher drops latecomers once shutdown completion won
the race. The buffer holds two entries: os/signal sends non-blockingly,
so a size-one buffer could drop the second of two back-to-back signals
before the watcher consumes the first. stop() is idempotent,
unregisters the channel, releases the watcher and cancels the context,
mirroring NotifyContext's stop semantics.

Exit status stays plain 1 to match the current effective behavior;
switching to the 128+sig convention (130 SIGINT / 143 SIGTERM) deserves
its own discussion and is raised in the PR description. scan_cmd.go is
untouched: it registers no signal handling today, and keeping it that
way until alibaba#996 lands preserves symmetry between the two commands.

Tests: in-process unit tests cover first-signal cancellation (SIGINT
and SIGTERM), the second-signal forced exit through a stubbed hook, and
watcher release on stop-before-signal and parent cancellation. Two
subprocess integration tests re-execute the test binary so the real
os.Exit path is exercised: a second SIGINT during a simulated 30s
graceful shutdown ends the process well under 100ms with status 1, and
a single SIGINT still completes the graceful path with the partial
report persisted on disk and status 0.
@HOWILLMAKEIT
HOWILLMAKEIT force-pushed the fix/second-signal-exit branch from eb0bd68 to 85efaa3 Compare September 7, 2026 06:35
@HOWILLMAKEIT

Copy link
Copy Markdown
Contributor Author

thanks to @Qiyuanqiii , a problem is fixed in 85efaa3.

The diagnostic write now runs in a goroutine (go fmt.Fprintf(...)) so a full stderr pipe can no longer block the exit. The process reaches os.Exit(1) unconditionally; the message is best-effort.

I also added a regression test (TestSecondSignalExitsWithBlockedStderr) that reproduces this scenario: the child's stderr is redirected to a pipe that is filled to capacity before the signals are sent, and the test verifies the second SIGINT still terminates the process.

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.

2 participants