Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Console] Unexpected behavior on CONTROL-C #48340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
olegpro opened this issue Nov 26, 2022 · 2 comments · Fixed by #49529
Closed

[Console] Unexpected behavior on CONTROL-C #48340

olegpro opened this issue Nov 26, 2022 · 2 comments · Fixed by #49529

Comments

@olegpro
Copy link

olegpro commented Nov 26, 2022

Symfony version(s) affected

6.1.x

Description

Unexpected behavior on CONTROL-C in symfony/console & symfony/messenger

How to reproduce

Before testing, I modified a few files:

  1. \Symfony\Component\Messenger\Worker::run added to the bottom fwrite(\STDOUT, PHP_EOL . 'worker stop' . PHP_EOL);
  2. \Symfony\Component\Messenger\Command\ConsumeMessagesCommand::execute before return $io->comment('Quit');

Next run:

$ docker-compose exec php bin/console messenger:consume async -vvv
                                                                                                                      
 [OK] Consuming messages from transports "async".                                                                       
                                                                                                                        

 // The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command.       

 // Quit the worker with CONTROL-C.   

and pay attention to the phrase "Quit the worker with CONTROL-C."

After that, in the same terminal, I press CONTROL-C. and the consumer terminates unexpectedly without outputting to stdout, which I modified above in the source code. There will be only ^C in the console. Full log:

$ docker-compose exec php bin/console messenger:consume async -vvv

                                                                                                                        
 [OK] Consuming messages from transports "async".                                                                       
                                                                                                                        

 // The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command.       

 // Quit the worker with CONTROL-C.                                                                                     

^C

And if you open a second terminal with docker-compose exec php sh and call kill pid, then the consumer will exit:

13:08:59 INFO      [app] Received SIGTERM signal.
[
  "transport_names" => [
    "async"
  ]
]
[
  "uid" => "b66f4c45f3bba98ac04cd5dcc3db6968"
]
13:08:59 INFO      [messenger] Stopping worker.
[
  "transport_names" => [
    "async"
  ]
]
[
  "uid" => "b66f4c45f3bba98ac04cd5dcc3db6968"
]

worker stop
 // Quit  

Full log:

$ docker-compose exec php bin/console messenger:consume async -vvv

                                                                                                                        
 [OK] Consuming messages from transports "async".                                                                       
                                                                                                                        

 // The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command.       

 // Quit the worker with CONTROL-C.                                                                                     

13:08:59 INFO      [app] Received SIGTERM signal.
[
  "transport_names" => [
    "async"
  ]
]
[
  "uid" => "b66f4c45f3bba98ac04cd5dcc3db6968"
]
13:08:59 INFO      [messenger] Stopping worker.
[
  "transport_names" => [
    "async"
  ]
]
[
  "uid" => "b66f4c45f3bba98ac04cd5dcc3db6968"
]

worker stop
 // Quit   

Is this the expected behavior for us?

For example, the event WorkerStoppedEvent will never be called (and ConsoleTerminateEvent).


The reason is exit(0) in \Symfony\Component\Console\Application::doRunCommand in this section::

            if (null !== $this->dispatcher) {
                foreach ($this->signalsToDispatchEvent as $signal) {
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);

                    $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);

                        // No more handlers, we try to simulate PHP default behavior
                        if (!$hasNext) {
                            if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
                                exit(0);
                            }
                        }
                    });
                }
            }

If you remove exit(0), the result will be the same as with kill pid.


Thanks!

If this is legal behavior, then how do I solve the problem of missing WorkerStoppedEvent/ConsoleTerminateEvent events?

Possible Solution

No response

Additional Context

docker-image — php:8.1.11-fpm-alpine
symfony/console — 6.1.7
symfony/messenger — 6.1.6

@GromNaN
Copy link
Member

GromNaN commented Nov 26, 2022

Thanks for the report. It seems to be related to #47809.

Did you register an event listener on console.signal event? Can you check if the callback in StopWorkerOnSigtermSignalListener::onWorkerStarted is called when you press Ctrl+C?

@olegpro
Copy link
Author

olegpro commented Nov 27, 2022

Did you register an event listener on console.signal event?

No. I am trying to use my SignalableCommandInterface.

Can you check if the callback in StopWorkerOnSigtermSignalListener::onWorkerStarted is called when you press Ctrl+C?

Called.


COSTYLE-solution for my problem:

    private function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);    $application = $this->getApplication();

        if ($application === null) {
            return;
        }

        $signalRegistryReflection = new ReflectionProperty(\Symfony\Component\Console\Application::class, 'signalRegistry');
        $dispatcherReflection = new ReflectionProperty(\Symfony\Component\Console\Application::class, 'dispatcher');

        /** @var \Symfony\Component\Console\SignalRegistry\SignalRegistry $signalRegistry */
        $signalRegistry = $signalRegistryReflection->getValue($application);

        /** @var null|\Symfony\Contracts\EventDispatcher\EventDispatcherInterface $dispatcher */
        $dispatcher = $dispatcherReflection->getValue($application);

        $event = new ConsoleSignalEvent($this, $input, $output, SIGINT);
        $signalRegistry->register(SIGINT, static function (int $_signal, bool $_hasNext) use ($event, $dispatcher): void {
            $dispatcher?->dispatch($event, \Symfony\Component\Console\ConsoleEvents::SIGNAL);
        });
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants