🌐 AI搜索 & 代理 主页
Skip to content

Conversation

@mudassaralichouhan
Copy link

@mudassaralichouhan mudassaralichouhan commented Sep 12, 2025

Q A
Branch? 7.3
Bug fix? yes
New feature? no
Deprecations? no
Issues Fix #61732
License MIT

The issue occurred when using QuestionHelper with ChoiceQuestion and a signal handler that calls exit(). The terminal would be left in raw mode (-icanon -echo) because the stty restoration only happened at the end of the autocomplete() method.

This fix:

  1. Registers signal handlers for common termination signals (SIGINT, SIGQUIT, SIGTERM, etc.)
  2. Uses a try-finally block to ensure terminal restoration even if exceptions occur or the process exits early
  3. Adds two private methods:
    • registerTerminalRestoreHandler() — sets up signal-safe terminal restoration
    • restoreTerminal() — restores the saved terminal mode via stty
  4. Ensures compatibility with Unix environments only (no effect on Windows)
  5. Maintains backward compatibility with existing code

Reproducer: https://github.com/johnstevenson/sigbug
Related issues: #44045, #48205, #57241

- Add signal handlers to restore terminal settings when Ctrl-C is pressed during choice selection
- Wrap autocomplete method in try-finally block to ensure terminal restoration
- Fixes issue where terminal would be left in raw mode when signal handlers call exit()
- Resolves symfony#61732

The issue occurred when using QuestionHelper with ChoiceQuestion and a signal handler
that calls exit(). The terminal would be left in raw mode (-icanon -echo) because
the stty restoration only happened at the end of the autocomplete method.

This fix:
1. Registers signal handlers for common termination signals (SIGINT, SIGQUIT, SIGTERM, etc.)
2. Uses try-finally to ensure terminal restoration even if exceptions occur
3. Maintains backward compatibility with existing code

Reproducer: https://github.com/johnstevenson/sigbug
Related issues: symfony#44045, symfony#48205, symfony#57241
@carsonbot
Copy link

Hey!

Thanks for your PR. You are targeting branch "7.4" but it seems your PR description refers to branch "7.3".
Could you update the PR description or change target branch? This helps core maintainers a lot.

Cheers!

Carsonbot

@carsonbot carsonbot changed the title Fix Console Ctrl-C terminal breaking issue (#61732) Fix Console Ctrl-C terminal breaking issue (#61732) Sep 12, 2025
@nicolas-grekas nicolas-grekas changed the title Fix Console Ctrl-C terminal breaking issue (#61732) Fix Console Ctrl-C terminal breaking issue Sep 13, 2025
@carsonbot carsonbot changed the title Fix Console Ctrl-C terminal breaking issue [Console] Fix Console Ctrl-C terminal breaking issue Sep 13, 2025
Comment on lines 379 to 380
// Always restore terminal settings, even if an exception occurs
$this->restoreTerminal($sttyMode);
Copy link
Member

Choose a reason for hiding this comment

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

This would at the very least need to restore previous signal handlers.. But it also needs to call the previous signal handlers when a signal triggers for full compatibility IMO. So this is definitely not a complete fix as it is.

@johnstevenson
Copy link
Contributor

Strange, but this doesn't fix anything when I use your code in the reproducer.

@johnstevenson
Copy link
Contributor

johnstevenson commented Sep 15, 2025

Ah, you clearly haven't tested this. The reason it doesn't work is that the signal handlers are never set. You need to fix this so that pcntl_signal($signal, $restoreHandler) is actually called.

The issue occurred when using QuestionHelper with ChoiceQuestion and a signal handler that calls exit().

The issue also occurs in a restarted process as well. While you have ensured that the terminal continues to work, you have unfortunately replaced any user signal handler with your own, which doesn't call exit, thus defeating the most common use of Ctrl-C. You've also wiped out the user signal handler for the remainder of the process after a choice question.

I've updated the reproducer so that the questions are called twice, which makes it more obvious where Ctrl-C has been called.

I think you need to do the following:

  • save the signal handler for each signal and run it after you have reset the terminal in your handler. For a callable, invoke it, for SIG_DFL exit with an appropriate exit code, and for SIG_IGN do nothing more.
  • restore these signal handlers afterwards (in restoreTerminal).
  • for Ctrl-C signals, perhaps echo ^C in the handler so it is clear to the user that the signal has been received.

@nicolas-grekas
Copy link
Member

On review, I wondered if this implementation, if made to work, won't collide with signal handlers as managed by the application?

@johnstevenson
Copy link
Contributor

On review, I wondered if this implementation, if made to work, won't collide with signal handlers as managed by the application?

Provided that existing signal handlers are saved before the choice question, invoked as I described above if signalled in the choice question, then restored afterwards, then there shouldn't be any collisions. Well that's the theory, anyway.

@mudassaralichouhan
Copy link
Author

mudassaralichouhan commented Sep 15, 2025

You can verify the fix by running this command, which simulates a Ctrl-C during the interactive prompt and checks the terminal state before and after:

orig=$(stty -g); php sigbug handler & pid=$!; sleep 1; kill -INT $pid; wait $pid; echo "-- stty after --"; stty -a; stty $orig; echo "-- restored --"; stty -a

This test was performed on macOS with an M1 chip. Please let me know if you need assistance testing on other platforms or with the restart scenario.

@johnstevenson
Copy link
Contributor

Thanks for the fixes. However, registering lots of shutdown functions is not ideal or even necessary. I think you are making things a bit too complicated and could use something like this to set the SIGINT handler:

$signalHandler = null;

if (\function_exists('pcntl_async_signals') && \function_exists('pcntl_signal')) {
    pcntl_async_signals(true);
    $signalHandler = pcntl_signal_get_handler(\SIGINT);
    pcntl_signal(\SIGINT, function ($signo) use ($sttyMode, $signalHandler) {
        echo '^C', \PHP_EOL;
        shell_exec('stty '.$sttyMode);

        // Original handler will be a callable, SIG_DFL or SIG_IGN
        if (\is_callable($signalHandler)) {
            $signalHandler($signo);
            return;
        }
        if ($signalHandler === \SIG_DFL) {
            exit(128 + $signo);
        }
    });
}

Then call the following (either before throwing the exception and at the end of the function, or just once in a try, finally block);

// Restore terminal and original SIGINT handler
shell_exec('stty '.$sttyMode);
if ($signalHandler !== null) {
    pcntl_signal(\SIGINT, $signalHandler);
}

@johnstevenson
Copy link
Contributor

You can verify the fix by running this command, which simulates a Ctrl-C during the interactive prompt and checks the terminal state before and after

This command does not work for me because it shows the same stty state with the broken code. In my terminal the signal stops the process, then restores the terminal so it can tell me it has done so and allow me to resume with fg #1.

@nicolas-grekas
Copy link
Member

Closing in favor of #61861, thanks for giving this a try!

@mudassaralichouhan mudassaralichouhan deleted the fix/console-ctrl-c-terminal-breaking branch October 2, 2025 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Console] Ctrl-C can break the terminal when selecting input from a list

5 participants