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

Skip to content

[Messenger] Worker events + global retry functionality #30557

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

Merged
merged 1 commit into from
Mar 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,20 @@ function ($a) {
->prototype('variable')
->end()
->end()
->arrayNode('retry_strategy')
->addDefaultsIfNotSet()
->validate()
->ifTrue(function ($v) { return null !== $v['service'] && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay'])); })
->thenInvalid('"service" cannot be used along with the other retry_strategy options.')
->end()
->children()
->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end()
->integerNode('max_retries')->defaultValue(3)->min(0)->end()
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end()
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
->end()
->end()
->end()
->end()
->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
}

$senderAliases = [];
$transportRetryReferences = [];
foreach ($config['transports'] as $name => $transport) {
if (0 === strpos($transport['dsn'], 'amqp://') && !$container->hasDefinition('messenger.transport.amqp.factory')) {
throw new LogicException('The default AMQP transport is not available. Make sure you have installed and enabled the Serializer component. Try enabling it or running "composer require symfony/serializer-pack".');
Expand All @@ -1665,6 +1666,21 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
;
$container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition);
$senderAliases[$name] = $transportId;

if (null !== $transport['retry_strategy']['service']) {
$transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']);
} else {
$retryServiceId = sprintf('messenger.retry.multiplier_retry_strategy.%s', $name);
$retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy');
$retryDefinition
->replaceArgument(0, $transport['retry_strategy']['max_retries'])
->replaceArgument(1, $transport['retry_strategy']['delay'])
->replaceArgument(2, $transport['retry_strategy']['multiplier'])
->replaceArgument(3, $transport['retry_strategy']['max_delay']);
$container->setDefinition($retryServiceId, $retryDefinition);

$transportRetryReferences[$name] = new Reference($retryServiceId);
}
}

$messageToSendersMapping = [];
Expand All @@ -1686,6 +1702,9 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
->replaceArgument(0, $messageToSendersMapping)
->replaceArgument(1, $messagesToSendAndHandle)
;

$container->getDefinition('messenger.retry_strategy_locator')
->replaceArgument(0, $transportRetryReferences);
}

private function registerCacheConfiguration(array $config, ContainerBuilder $container)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@
<argument type="service" id="logger" on-invalid="null" />
<argument type="collection" /> <!-- Receiver names -->
<argument type="collection" /> <!-- Message bus names -->
<argument type="service" id="messenger.retry_strategy_locator" />
<argument type="service" id="event_dispatcher" />

<tag name="console.command" command="messenger:consume" />
<tag name="console.command" command="messenger:consume-messages" />
<tag name="monolog.logger" channel="messenger" />
</service>

<service id="console.command.messenger_debug" class="Symfony\Component\Messenger\Command\DebugCommand">
Expand Down
13 changes: 13 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,18 @@
<tag name="messenger.transport_factory" />
<argument type="service" id="messenger.transport.serializer" />
</service>

<!-- retry -->
<service id="messenger.retry_strategy_locator">
<tag name="container.service_locator" />
<argument type="collection" />
</service>

<service id="messenger.retry.abstract_multiplier_retry_strategy" class="Symfony\Component\Messenger\Retry\MultiplierRetryStrategy" abstract="true">
<argument /> <!-- max retries -->
<argument /> <!-- delay ms -->
<argument /> <!-- multiplier -->
<argument /> <!-- max delay ms -->
</service>
</services>
</container>
29 changes: 28 additions & 1 deletion src/Symfony/Component/Messenger/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,34 @@ CHANGELOG

4.3.0
-----


* [BC BREAK] 2 new methods were added to `ReceiverInterface`:
`ack()` and `reject()`.
* [BC BREAK] Error handling was moved from the receivers into
`Worker`. Implementations of `ReceiverInterface::handle()`
should now allow all exceptions to be thrown, except for transport
exceptions. They should also not retry (e.g. if there's a queue,
remove from the queue) if there is a problem decoding the message.
* [BC BREAK] `RejectMessageExceptionInterface` was removed and replaced
by `Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException`,
which has the same behavior: a message will not be retried
* The default command name for `ConsumeMessagesCommand` was
changed from `messenger:consume-messages` to `messenger:consume`
* `ConsumeMessagesCommand` has two new optional constructor arguments
* `Worker` has 4 new option constructor arguments.
* The `Worker` class now handles calling `pcntl_signal_dispatch()` the
receiver no longer needs to call this.
* The `AmqpSender` will now retry messages using a dead-letter exchange
and delayed queues, instead of retrying via `nack()`
* Senders now receive the `Envelope` with the `SentStamp` on it. Previously,
the `Envelope` was passed to the sender and *then* the `SentStamp`
was added.
* `SerializerInterface` implementations should now throw a
`Symfony\Component\Messenger\Exception\MessageDecodingFailedException`
if `decode()` fails for any reason.
* [BC BREAK] The default `Serializer` will now throw a
`MessageDecodingFailedException` if `decode()` fails, instead
of the underlying exceptions from the Serializer component.
* Added `PhpSerializer` which uses PHP's native `serialize()` and
`unserialize()` to serialize messages to a transport
* [BC BREAK] If no serializer were passed, the default serializer
Expand Down
22 changes: 19 additions & 3 deletions src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\Transport\Receiver\StopWhenMemoryUsageIsExceededReceiver;
use Symfony\Component\Messenger\Transport\Receiver\StopWhenMessageCountIsExceededReceiver;
use Symfony\Component\Messenger\Transport\Receiver\StopWhenTimeLimitIsReachedReceiver;
Expand All @@ -32,21 +33,25 @@
*/
class ConsumeMessagesCommand extends Command
{
protected static $defaultName = 'messenger:consume-messages';
protected static $defaultName = 'messenger:consume';

private $busLocator;
private $receiverLocator;
private $logger;
private $receiverNames;
private $busNames;
private $retryStrategyLocator;
private $eventDispatcher;

public function __construct(ContainerInterface $busLocator, ContainerInterface $receiverLocator, LoggerInterface $logger = null, array $receiverNames = [], array $busNames = [])
public function __construct(ContainerInterface $busLocator, ContainerInterface $receiverLocator, LoggerInterface $logger = null, array $receiverNames = [], array $busNames = [], ContainerInterface $retryStrategyLocator = null, EventDispatcherInterface $eventDispatcher = null)
{
$this->busLocator = $busLocator;
$this->receiverLocator = $receiverLocator;
$this->logger = $logger;
$this->receiverNames = $receiverNames;
$this->busNames = $busNames;
$this->retryStrategyLocator = $retryStrategyLocator;
$this->eventDispatcher = $eventDispatcher;

parent::__construct();
}
Expand Down Expand Up @@ -132,6 +137,12 @@ protected function interact(InputInterface $input, OutputInterface $output)
*/
protected function execute(InputInterface $input, OutputInterface $output): void
{
if (false !== strpos($input->getFirstArgument(), ':consume-')) {
$message = 'The use of the "messenger:consume-messages" command is deprecated since version 4.3 and will be removed in 5.0. Use "messenger:consume" instead.';
@trigger_error($message, E_USER_DEPRECATED);
$output->writeln(sprintf('<comment>%s</comment>', $message));
}

if (!$this->receiverLocator->has($receiverName = $input->getArgument('receiver'))) {
throw new RuntimeException(sprintf('Receiver "%s" does not exist.', $receiverName));
}
Expand All @@ -140,8 +151,13 @@ protected function execute(InputInterface $input, OutputInterface $output): void
throw new RuntimeException(sprintf('Bus "%s" does not exist.', $busName));
}

if (null !== $this->retryStrategyLocator && !$this->retryStrategyLocator->has($receiverName)) {
throw new RuntimeException(sprintf('Receiver "%s" does not have a configured retry strategy.', $receiverName));
}

$receiver = $this->receiverLocator->get($receiverName);
$bus = $this->busLocator->get($busName);
$retryStrategy = null !== $this->retryStrategyLocator ? $this->retryStrategyLocator->get($receiverName) : null;

$stopsWhen = [];
if ($limit = $input->getOption('limit')) {
Expand Down Expand Up @@ -174,7 +190,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void
$io->comment('Re-run the command with a -vv option to see logs about consumed messages.');
}

$worker = new Worker($receiver, $bus);
$worker = new Worker($receiver, $bus, $receiverName, $retryStrategy, $this->eventDispatcher, $this->logger);
$worker->run();
}

Expand Down
12 changes: 12 additions & 0 deletions src/Symfony/Component/Messenger/Envelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ public function with(StampInterface ...$stamps): self
return $cloned;
}

/**
* @return Envelope a new Envelope instance without any stamps of the given class
*/
public function withoutAll(string $stampFqcn): self
{
$cloned = clone $this;

unset($cloned->stamps[$stampFqcn]);

return $cloned;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Handy method :). Allows us to remove the ReceivedStamp


public function last(string $stampFqcn): ?StampInterface
{
return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Event;

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Messenger\Envelope;

/**
* @experimental in 4.3
*/
abstract class AbstractWorkerMessageEvent extends Event
{
private $envelope;
private $receiverName;

public function __construct(Envelope $envelope, string $receiverName)
{
$this->envelope = $envelope;
$this->receiverName = $receiverName;
}

public function getEnvelope(): Envelope
{
return $this->envelope;
}

/**
* Returns a unique identifier for transport receiver this message was received from.
*/
public function getReceiverName(): string
{
return $this->receiverName;
}
}
45 changes: 45 additions & 0 deletions src/Symfony/Component/Messenger/Event/WorkerMessageFailedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Event;

use Symfony\Component\Messenger\Envelope;

/**
* Dispatched when a message was received from a transport and handling failed.
*
* The event name is the class name.
*
* @experimental in 4.3
*/
class WorkerMessageFailedEvent extends AbstractWorkerMessageEvent
{
private $throwable;
private $willRetry;

public function __construct(Envelope $envelope, string $receiverName, \Throwable $error, bool $willRetry)
{
$this->throwable = $error;
$this->willRetry = $willRetry;

parent::__construct($envelope, $receiverName);
}

public function getThrowable(): \Throwable
{
return $this->throwable;
}

public function willRetry(): bool
{
return $this->willRetry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Event;

/**
* Dispatched after a message was received from a transport and successfully handled.
*
* The event name is the class name.
*
* @experimental in 4.3
*/
class WorkerMessageHandledEvent extends AbstractWorkerMessageEvent
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Event;

/**
* Dispatched when a message was received from a transport but before sent to the bus.
*
* The event name is the class name.
*
* @experimental in 4.3
*/
class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Exception;

/**
* Thrown when a message cannot be decoded in a serializer.
*
* @experimental in 4.3
*/
class MessageDecodingFailedException extends \InvalidArgumentException implements ExceptionInterface
{
}
Loading