Skip to content
Open
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 @@ -142,6 +142,7 @@
use Symfony\Component\Messenger\Middleware\AddDefaultStampsMiddleware;
use Symfony\Component\Messenger\Middleware\DeduplicateMiddleware;
use Symfony\Component\Messenger\Middleware\RouterContextMiddleware;
use Symfony\Component\Messenger\Retry\DynamicRetryStrategy;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory;
use Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
Expand Down Expand Up @@ -2425,6 +2426,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder

$loader->load('messenger.php');

if (!class_exists(DynamicRetryStrategy::class)) {
$container->removeDefinition('messenger.retry.abstract_dynamic_retry_strategy');
}

if (!interface_exists(DenormalizerInterface::class)) {
$container->removeDefinition('serializer.normalizer.flatten_exception');
}
Expand Down Expand Up @@ -2576,7 +2581,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
$serializerIds[$transportId] = $serializerId;

if (null !== $transport['retry_strategy']['service']) {
$transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']);
$retryServiceId = $transport['retry_strategy']['service'];
} else {
$retryServiceId = \sprintf('messenger.retry.multiplier_retry_strategy.%s', $name);
$retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy');
Expand All @@ -2587,7 +2592,11 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
->replaceArgument(3, $transport['retry_strategy']['max_delay'])
->replaceArgument(4, $transport['retry_strategy']['jitter']);
$container->setDefinition($retryServiceId, $retryDefinition);
}

if ($container->hasDefinition('messenger.retry.abstract_dynamic_retry_strategy')) {
$transportRetryReferences[$name] = (new ChildDefinition('messenger.retry.abstract_dynamic_retry_strategy'))->setDecoratedService($retryServiceId);
} else {
$transportRetryReferences[$name] = new Reference($retryServiceId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Middleware\TraceableMiddleware;
use Symfony\Component\Messenger\Middleware\ValidationMiddleware;
use Symfony\Component\Messenger\Retry\DynamicRetryStrategy;
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
use Symfony\Component\Messenger\RoutableMessageBus;
use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory;
Expand Down Expand Up @@ -193,6 +194,12 @@ class_exists(LazyString::class, false) ? service_closure('.messenger.signing_ser
abstract_arg('jitter'),
])

->set('messenger.retry.abstract_dynamic_retry_strategy', DynamicRetryStrategy::class)
->abstract()
->args([
abstract_arg('fallback strategy'),
])

// rate limiter
->set('messenger.rate_limiter_locator', ServiceLocator::class)
->args([[]])
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* Add the possibility to configure exchange to exchange bindings in AMQP transport
* Add `MessageSentToTransportsEvent` that is dispatched only after the message was sent to at least one transport
* Support signing messages per handler
* Add `RetryStrategyStamp` to dynamically control retry behavior

7.3
---
Expand Down
37 changes: 37 additions & 0 deletions src/Symfony/Component/Messenger/Retry/DynamicRetryStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

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

namespace Symfony\Component\Messenger\Retry;

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\RetryStrategyStamp;

/**
* @author Valtteri R <valtzu@gmail.com>
*/
final class DynamicRetryStrategy implements RetryStrategyInterface
{
public function __construct(private RetryStrategyInterface $fallbackStrategy)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function __construct(private RetryStrategyInterface $fallbackStrategy)
public function __construct(private readonly RetryStrategyInterface $fallbackStrategy)

{
}

public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool
{
return $message->last(RetryStrategyStamp::class)?->isRetryable()
?? $this->fallbackStrategy->isRetryable($message, $throwable);
}

public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int
{
return $message->last(RetryStrategyStamp::class)?->getWaitingTime()
?? $this->fallbackStrategy->getWaitingTime($message, $throwable);
}
}
36 changes: 36 additions & 0 deletions src/Symfony/Component/Messenger/Stamp/RetryStrategyStamp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

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

namespace Symfony\Component\Messenger\Stamp;

/**
* Override retry behavior for a single message when dispatching.
*
* @author Valtteri R <valtzu@gmail.com>
*/
final class RetryStrategyStamp implements StampInterface
{
public function __construct(
private ?bool $retryable = null,
private ?int $waitingTime = null,
) {
}

public function isRetryable(): ?bool
{
return $this->retryable;
}

public function getWaitingTime(): ?int
{
return $this->waitingTime;
}
Comment on lines +21 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function __construct(
private ?bool $retryable = null,
private ?int $waitingTime = null,
) {
}
public function isRetryable(): ?bool
{
return $this->retryable;
}
public function getWaitingTime(): ?int
{
return $this->waitingTime;
}
public function __construct(
public readonly ?bool $retryable = null,
public readonly ?int $waitingTime = null,
) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

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

namespace Symfony\Component\Messenger\Tests\Retry;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Retry\DynamicRetryStrategy;
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
use Symfony\Component\Messenger\Stamp\RetryStrategyStamp;

class DynamicRetryStrategyTest extends TestCase
{
public function testIsRetryable()
{
$strategy = new DynamicRetryStrategy($this->createStub(RetryStrategyInterface::class));
$envelope = new Envelope(new \stdClass(), [new RetryStrategyStamp(true)]);

$this->assertTrue($strategy->isRetryable($envelope));
}

public function testIsNotRetryable()
{
$strategy = new DynamicRetryStrategy($this->createStub(RetryStrategyInterface::class));
$envelope = new Envelope(new \stdClass(), [new RetryStrategyStamp(false)]);

$this->assertFalse($strategy->isRetryable($envelope));
}

public function testIsRetryableWithNoStamp()
{
$fallbackStrategy = $this->createMock(RetryStrategyInterface::class);
$fallbackStrategy->expects($this->once())->method('isRetryable')->willReturn(true);

$strategy = new DynamicRetryStrategy($fallbackStrategy);
$envelope = new Envelope(new \stdClass());

$this->assertTrue($strategy->isRetryable($envelope));
}

public function testGetWaitingTime()
{
$strategy = new DynamicRetryStrategy($this->createStub(RetryStrategyInterface::class));
$envelope = new Envelope(new \stdClass(), [new RetryStrategyStamp(null, 123)]);

$this->assertSame(123, $strategy->getWaitingTime($envelope));
}

public function testGetWaitingTimeWithNoStamp()
{
$fallbackStrategy = $this->createMock(RetryStrategyInterface::class);
$fallbackStrategy->expects($this->once())->method('getWaitingTime')->willReturn(456);

$strategy = new DynamicRetryStrategy($fallbackStrategy);
$envelope = new Envelope(new \stdClass());

$this->assertSame(456, $strategy->getWaitingTime($envelope));
}
}