Merge branch '4.3' into 4.4
This commit is contained in:
commit
c950130fc2
@ -1717,6 +1717,7 @@ class FrameworkExtension extends Extension
|
|||||||
$defaultMiddleware = [
|
$defaultMiddleware = [
|
||||||
'before' => [
|
'before' => [
|
||||||
['id' => 'add_bus_name_stamp_middleware'],
|
['id' => 'add_bus_name_stamp_middleware'],
|
||||||
|
['id' => 'reject_redelivered_message_middleware'],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
['id' => 'failed_message_processing_middleware'],
|
['id' => 'failed_message_processing_middleware'],
|
||||||
],
|
],
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
<argument type="service" id="validator" />
|
<argument type="service" id="validator" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="messenger.middleware.reject_redelivered_message_middleware" class="Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware" />
|
||||||
|
|
||||||
<service id="messenger.middleware.failed_message_processing_middleware" class="Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware" />
|
<service id="messenger.middleware.failed_message_processing_middleware" class="Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware" />
|
||||||
|
|
||||||
<service id="messenger.middleware.traceable" class="Symfony\Component\Messenger\Middleware\TraceableMiddleware" abstract="true">
|
<service id="messenger.middleware.traceable" class="Symfony\Component\Messenger\Middleware\TraceableMiddleware" abstract="true">
|
||||||
|
@ -740,6 +740,7 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$this->assertSame([], $container->getDefinition('messenger.bus.commands')->getArgument(0));
|
$this->assertSame([], $container->getDefinition('messenger.bus.commands')->getArgument(0));
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.commands']],
|
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.commands']],
|
||||||
|
['id' => 'reject_redelivered_message_middleware'],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
['id' => 'failed_message_processing_middleware'],
|
['id' => 'failed_message_processing_middleware'],
|
||||||
['id' => 'send_message'],
|
['id' => 'send_message'],
|
||||||
@ -749,6 +750,7 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$this->assertSame([], $container->getDefinition('messenger.bus.events')->getArgument(0));
|
$this->assertSame([], $container->getDefinition('messenger.bus.events')->getArgument(0));
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.events']],
|
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.events']],
|
||||||
|
['id' => 'reject_redelivered_message_middleware'],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
['id' => 'failed_message_processing_middleware'],
|
['id' => 'failed_message_processing_middleware'],
|
||||||
['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]],
|
['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]],
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"symfony/http-client": "^4.4|^5.0",
|
"symfony/http-client": "^4.4|^5.0",
|
||||||
"symfony/lock": "^4.4|^5.0",
|
"symfony/lock": "^4.4|^5.0",
|
||||||
"symfony/mailer": "^4.4|^5.0",
|
"symfony/mailer": "^4.4|^5.0",
|
||||||
"symfony/messenger": "^4.3|^5.0",
|
"symfony/messenger": "^4.3.6|^5.0",
|
||||||
"symfony/mime": "^4.4|^5.0",
|
"symfony/mime": "^4.4|^5.0",
|
||||||
"symfony/process": "^3.4|^4.0|^5.0",
|
"symfony/process": "^3.4|^4.0|^5.0",
|
||||||
"symfony/security-csrf": "^3.4|^4.0|^5.0",
|
"symfony/security-csrf": "^3.4|^4.0|^5.0",
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"symfony/form": "<4.3",
|
"symfony/form": "<4.3",
|
||||||
"symfony/lock": "<4.4",
|
"symfony/lock": "<4.4",
|
||||||
"symfony/mailer": "<4.4",
|
"symfony/mailer": "<4.4",
|
||||||
"symfony/messenger": "<4.3",
|
"symfony/messenger": "<4.3.6",
|
||||||
"symfony/mime": "<4.4",
|
"symfony/mime": "<4.4",
|
||||||
"symfony/property-info": "<3.4",
|
"symfony/property-info": "<3.4",
|
||||||
"symfony/security-bundle": "<4.4",
|
"symfony/security-bundle": "<4.4",
|
||||||
|
@ -16,11 +16,6 @@ use Symfony\Contracts\HttpClient\Test\HttpClientTestCase as BaseHttpClientTestCa
|
|||||||
|
|
||||||
abstract class HttpClientTestCase extends BaseHttpClientTestCase
|
abstract class HttpClientTestCase extends BaseHttpClientTestCase
|
||||||
{
|
{
|
||||||
public function testMaxDuration()
|
|
||||||
{
|
|
||||||
$this->markTestSkipped('Implemented as of version 4.4');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testAcceptHeader()
|
public function testAcceptHeader()
|
||||||
{
|
{
|
||||||
$client = $this->getHttpClient(__FUNCTION__);
|
$client = $this->getHttpClient(__FUNCTION__);
|
||||||
@ -80,4 +75,34 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
|
|||||||
$response = $client->request('GET', 'http://localhost:8057/404');
|
$response = $client->request('GET', 'http://localhost:8057/404');
|
||||||
$stream = $response->toStream();
|
$stream = $response->toStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInfoOnCanceledResponse()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBufferSink()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConditionalBuffering()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReentrantBufferCallback()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThrowingBufferCallback()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMaxDuration()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Implemented as of version 4.4');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,7 @@ abstract class AbstractFailedMessagesCommand extends Command
|
|||||||
|
|
||||||
/** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
|
/** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
|
||||||
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
||||||
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
|
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
|
||||||
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
|
||||||
|
|
||||||
$rows = [
|
$rows = [
|
||||||
['Class', \get_class($envelope->getMessage())],
|
['Class', \get_class($envelope->getMessage())],
|
||||||
@ -72,13 +71,13 @@ abstract class AbstractFailedMessagesCommand extends Command
|
|||||||
$rows[] = ['Message Id', $id];
|
$rows[] = ['Message Id', $id];
|
||||||
}
|
}
|
||||||
|
|
||||||
$flattenException = null === $lastRedeliveryStamp ? null : $lastRedeliveryStamp->getFlattenException();
|
$flattenException = null === $lastRedeliveryStampWithException ? null : $lastRedeliveryStampWithException->getFlattenException();
|
||||||
if (null === $sentToFailureTransportStamp) {
|
if (null === $sentToFailureTransportStamp) {
|
||||||
$io->warning('Message does not appear to have been sent to this transport after failing');
|
$io->warning('Message does not appear to have been sent to this transport after failing');
|
||||||
} else {
|
} else {
|
||||||
$rows = array_merge($rows, [
|
$rows = array_merge($rows, [
|
||||||
['Failed at', null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')],
|
['Failed at', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s')],
|
||||||
['Error', null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getExceptionMessage()],
|
['Error', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage()],
|
||||||
['Error Class', null === $flattenException ? '(unknown)' : $flattenException->getClass()],
|
['Error Class', null === $flattenException ? '(unknown)' : $flattenException->getClass()],
|
||||||
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
|
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
|
||||||
]);
|
]);
|
||||||
@ -120,4 +119,16 @@ abstract class AbstractFailedMessagesCommand extends Command
|
|||||||
{
|
{
|
||||||
return $this->receiver;
|
return $this->receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
|
||||||
|
{
|
||||||
|
/** @var RedeliveryStamp $stamp */
|
||||||
|
foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
|
||||||
|
if (null !== $stamp->getExceptionMessage()) {
|
||||||
|
return $stamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
|
||||||
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,14 +82,13 @@ EOF
|
|||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
foreach ($envelopes as $envelope) {
|
foreach ($envelopes as $envelope) {
|
||||||
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
|
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
|
||||||
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
|
||||||
|
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
$this->getMessageId($envelope),
|
$this->getMessageId($envelope),
|
||||||
\get_class($envelope->getMessage()),
|
\get_class($envelope->getMessage()),
|
||||||
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s'),
|
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s'),
|
||||||
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getExceptionMessage(),
|
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tobias Schultze <http://tobion.de>
|
||||||
|
*
|
||||||
|
* @experimental in 4.3
|
||||||
|
*/
|
||||||
|
class RejectRedeliveredMessageException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
<?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\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
|
||||||
|
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
||||||
|
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that throws a RejectRedeliveredMessageException when a message is detected that has been redelivered by AMQP.
|
||||||
|
*
|
||||||
|
* The middleware runs before the HandleMessageMiddleware and prevents redelivered messages from being handled directly.
|
||||||
|
* The thrown exception is caught by the worker and will trigger the retry logic according to the retry strategy.
|
||||||
|
*
|
||||||
|
* AMQP redelivers messages when they do not get acknowledged or rejected. This can happen when the connection times out
|
||||||
|
* or an exception is thrown before acknowledging or rejecting. When such errors happen again while handling the
|
||||||
|
* redelivered message, the message would get redelivered again and again. The purpose of this middleware is to prevent
|
||||||
|
* infinite redelivery loops and to unblock the queue by republishing the redelivered messages as retries with a retry
|
||||||
|
* limit and potential delay.
|
||||||
|
*
|
||||||
|
* @experimental in 4.3
|
||||||
|
*
|
||||||
|
* @author Tobias Schultze <http://tobion.de>
|
||||||
|
*/
|
||||||
|
class RejectRedeliveredMessageMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||||
|
{
|
||||||
|
// ignore the dispatched messages for retry
|
||||||
|
if (null !== $envelope->last(ReceivedStamp::class)) {
|
||||||
|
$amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
|
||||||
|
|
||||||
|
if ($amqpReceivedStamp instanceof AmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
|
||||||
|
throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stack->next()->handle($envelope, $stack);
|
||||||
|
}
|
||||||
|
}
|
@ -13,15 +13,10 @@ namespace Symfony\Component\Messenger\Tests\Command;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
|
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
|
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
|
||||||
use Symfony\Component\Messenger\Envelope;
|
use Symfony\Component\Messenger\Envelope;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
|
|
||||||
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
|
||||||
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
||||||
|
|
||||||
class FailedMessagesRetryCommandTest extends TestCase
|
class FailedMessagesRetryCommandTest extends TestCase
|
||||||
@ -38,52 +33,16 @@ class FailedMessagesRetryCommandTest extends TestCase
|
|||||||
// the bus should be called in the worker
|
// the bus should be called in the worker
|
||||||
$bus->expects($this->exactly(2))->method('dispatch')->willReturn(new Envelope(new \stdClass()));
|
$bus->expects($this->exactly(2))->method('dispatch')->willReturn(new Envelope(new \stdClass()));
|
||||||
|
|
||||||
$command = new FailedMessagesRetryCommand('failure_receiver', $receiver, $bus, $dispatcher);
|
$command = new FailedMessagesRetryCommand(
|
||||||
|
'failure_receiver',
|
||||||
|
$receiver,
|
||||||
|
$bus,
|
||||||
|
$dispatcher
|
||||||
|
);
|
||||||
|
|
||||||
$tester = new CommandTester($command);
|
$tester = new CommandTester($command);
|
||||||
$tester->execute(['id' => [10, 12]]);
|
$tester->execute(['id' => [10, 12]]);
|
||||||
|
|
||||||
$this->assertStringContainsString('[OK]', $tester->getDisplay());
|
$this->assertStringContainsString('[OK]', $tester->getDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExceptionOnRetry()
|
|
||||||
{
|
|
||||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
|
||||||
$receiver->expects($this->once())->method('find')->with(10)->willReturn(new Envelope(new \stdClass()));
|
|
||||||
// message will eventually be ack'ed in Worker
|
|
||||||
$receiver->expects($this->once())->method('ack');
|
|
||||||
|
|
||||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
|
||||||
$bus = $this->createMock(MessageBusInterface::class);
|
|
||||||
// the bus should be called in the worker
|
|
||||||
$bus->expects($this->at(0))
|
|
||||||
->method('dispatch')
|
|
||||||
->with($this->callback(function (Envelope $envelope) {
|
|
||||||
$lastReceivedStamp = $envelope->last(ReceivedStamp::class);
|
|
||||||
|
|
||||||
return $lastReceivedStamp instanceof ReceivedStamp && \is_string($lastReceivedStamp->getTransportName());
|
|
||||||
}))
|
|
||||||
->will($this->throwException(new \Exception('Mock test exception')));
|
|
||||||
|
|
||||||
$bus->expects($this->at(1))
|
|
||||||
->method('dispatch')
|
|
||||||
->with($this->callback(function (Envelope $envelope) {
|
|
||||||
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
|
||||||
|
|
||||||
return $lastRedeliveryStamp instanceof RedeliveryStamp &&
|
|
||||||
\is_string($lastRedeliveryStamp->getExceptionMessage()) &&
|
|
||||||
($lastRedeliveryStamp->getFlattenException() instanceof FlattenException || $lastRedeliveryStamp->getFlattenException() instanceof LegacyFlattenException);
|
|
||||||
}))
|
|
||||||
->willReturn(new Envelope(new \stdClass()));
|
|
||||||
|
|
||||||
$retryStrategy = $this->createMock(RetryStrategyInterface::class);
|
|
||||||
$retryStrategy->expects($this->once())->method('isRetryable')->with($this->isInstanceOf(Envelope::class))->willReturn(true);
|
|
||||||
|
|
||||||
$command = new FailedMessagesRetryCommand('failure_receiver', $receiver, $bus, $dispatcher, $retryStrategy);
|
|
||||||
|
|
||||||
$tester = new CommandTester($command);
|
|
||||||
$tester->execute(['id' => [10]]);
|
|
||||||
|
|
||||||
$this->assertStringContainsString('[OK]', $tester->getDisplay());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -58,4 +58,40 @@ EOF
|
|||||||
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||||
$tester->getDisplay(true));
|
$tester->getDisplay(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMultipleRedeliveryFails()
|
||||||
|
{
|
||||||
|
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||||
|
$redeliveryStamp1 = new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!');
|
||||||
|
$redeliveryStamp2 = new RedeliveryStamp(0, 'failure_receiver');
|
||||||
|
$envelope = new Envelope(new \stdClass(), [
|
||||||
|
new TransportMessageIdStamp(15),
|
||||||
|
$sentToFailureStamp,
|
||||||
|
$redeliveryStamp1,
|
||||||
|
$redeliveryStamp2,
|
||||||
|
]);
|
||||||
|
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||||
|
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
||||||
|
|
||||||
|
$command = new FailedMessagesShowCommand(
|
||||||
|
'failure_receiver',
|
||||||
|
$receiver
|
||||||
|
);
|
||||||
|
|
||||||
|
$tester = new CommandTester($command);
|
||||||
|
$tester->execute(['id' => 15]);
|
||||||
|
|
||||||
|
$this->assertStringContainsString(sprintf(<<<EOF
|
||||||
|
------------- ---------------------
|
||||||
|
Class stdClass
|
||||||
|
Message Id 15
|
||||||
|
Failed at %s
|
||||||
|
Error Things are bad!
|
||||||
|
Error Class (unknown)
|
||||||
|
Transport async
|
||||||
|
EOF
|
||||||
|
,
|
||||||
|
$redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||||
|
$tester->getDisplay(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,11 @@ class ConnectionTest extends TestCase
|
|||||||
$queryBuilder
|
$queryBuilder
|
||||||
->method('getParameters')
|
->method('getParameters')
|
||||||
->willReturn([]);
|
->willReturn([]);
|
||||||
|
$queryBuilder
|
||||||
|
->method('getParameterTypes')
|
||||||
|
->willReturn([]);
|
||||||
$driverConnection
|
$driverConnection
|
||||||
->method('prepare')
|
->method('executeQuery')
|
||||||
->willReturn($stmt);
|
->willReturn($stmt);
|
||||||
|
|
||||||
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
||||||
@ -65,13 +68,17 @@ class ConnectionTest extends TestCase
|
|||||||
$queryBuilder
|
$queryBuilder
|
||||||
->method('getParameters')
|
->method('getParameters')
|
||||||
->willReturn([]);
|
->willReturn([]);
|
||||||
|
$queryBuilder
|
||||||
|
->method('getParameterTypes')
|
||||||
|
->willReturn([]);
|
||||||
$driverConnection->expects($this->once())
|
$driverConnection->expects($this->once())
|
||||||
->method('createQueryBuilder')
|
->method('createQueryBuilder')
|
||||||
->willReturn($queryBuilder);
|
->willReturn($queryBuilder);
|
||||||
$driverConnection->method('prepare')
|
|
||||||
->willReturn($stmt);
|
|
||||||
$driverConnection->expects($this->never())
|
$driverConnection->expects($this->never())
|
||||||
->method('update');
|
->method('update');
|
||||||
|
$driverConnection
|
||||||
|
->method('executeQuery')
|
||||||
|
->willReturn($stmt);
|
||||||
|
|
||||||
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
||||||
$doctrineEnvelope = $connection->get();
|
$doctrineEnvelope = $connection->get();
|
||||||
@ -273,7 +280,7 @@ class ConnectionTest extends TestCase
|
|||||||
->method('getParameters')
|
->method('getParameters')
|
||||||
->willReturn([]);
|
->willReturn([]);
|
||||||
$driverConnection
|
$driverConnection
|
||||||
->method('prepare')
|
->method('executeQuery')
|
||||||
->willReturn($stmt);
|
->willReturn($stmt);
|
||||||
|
|
||||||
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
||||||
@ -316,8 +323,11 @@ class ConnectionTest extends TestCase
|
|||||||
$queryBuilder
|
$queryBuilder
|
||||||
->method('getParameters')
|
->method('getParameters')
|
||||||
->willReturn([]);
|
->willReturn([]);
|
||||||
|
$queryBuilder
|
||||||
|
->method('getParameterTypes')
|
||||||
|
->willReturn([]);
|
||||||
$driverConnection
|
$driverConnection
|
||||||
->method('prepare')
|
->method('executeQuery')
|
||||||
->willReturn($stmt);
|
->willReturn($stmt);
|
||||||
|
|
||||||
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
|
||||||
|
@ -80,25 +80,25 @@ class DoctrineIntegrationTest extends TestCase
|
|||||||
'body' => '{"message": "Hi handled"}',
|
'body' => '{"message": "Hi handled"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'delivered_at' => Connection::formatDateTime(new \DateTime()),
|
'delivered_at' => $this->formatDateTime(new \DateTime()),
|
||||||
]);
|
]);
|
||||||
// one available later
|
// one available later
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi delayed"}',
|
'body' => '{"message": "Hi delayed"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 13:00:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 13:00:00')),
|
||||||
]);
|
]);
|
||||||
// one available
|
// one available
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi available"}',
|
'body' => '{"message": "Hi available"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$encoded = $this->connection->get();
|
$encoded = $this->connection->get();
|
||||||
@ -114,33 +114,33 @@ class DoctrineIntegrationTest extends TestCase
|
|||||||
'body' => '{"message": "Hi handled"}',
|
'body' => '{"message": "Hi handled"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'delivered_at' => Connection::formatDateTime(new \DateTime()),
|
'delivered_at' => $this->formatDateTime(new \DateTime()),
|
||||||
]);
|
]);
|
||||||
// one available later
|
// one available later
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi delayed"}',
|
'body' => '{"message": "Hi delayed"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime((new \DateTime())->modify('+1 minute')),
|
'available_at' => $this->formatDateTime((new \DateTime())->modify('+1 minute')),
|
||||||
]);
|
]);
|
||||||
// one available
|
// one available
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi available"}',
|
'body' => '{"message": "Hi available"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
||||||
]);
|
]);
|
||||||
// another available
|
// another available
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi available"}',
|
'body' => '{"message": "Hi available"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertSame(2, $this->connection->getMessageCount());
|
$this->assertSame(2, $this->connection->getMessageCount());
|
||||||
@ -155,16 +155,16 @@ class DoctrineIntegrationTest extends TestCase
|
|||||||
'body' => '{"message": "Hi requeued"}',
|
'body' => '{"message": "Hi requeued"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'delivered_at' => Connection::formatDateTime($twoHoursAgo),
|
'delivered_at' => $this->formatDateTime($twoHoursAgo),
|
||||||
]);
|
]);
|
||||||
$this->driverConnection->insert('messenger_messages', [
|
$this->driverConnection->insert('messenger_messages', [
|
||||||
'body' => '{"message": "Hi available"}',
|
'body' => '{"message": "Hi available"}',
|
||||||
'headers' => json_encode(['type' => DummyMessage::class]),
|
'headers' => json_encode(['type' => DummyMessage::class]),
|
||||||
'queue_name' => 'default',
|
'queue_name' => 'default',
|
||||||
'created_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
'created_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:00:00')),
|
||||||
'available_at' => Connection::formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
'available_at' => $this->formatDateTime(new \DateTime('2019-03-15 12:30:00')),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$next = $this->connection->get();
|
$next = $this->connection->get();
|
||||||
@ -181,4 +181,9 @@ class DoctrineIntegrationTest extends TestCase
|
|||||||
$envelope = $this->connection->get();
|
$envelope = $this->connection->get();
|
||||||
$this->assertEquals('the body', $envelope['body']);
|
$this->assertEquals('the body', $envelope['body']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function formatDateTime(\DateTime $dateTime)
|
||||||
|
{
|
||||||
|
return $dateTime->format($this->driverConnection->getDatabasePlatform()->getDateTimeFormatString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,8 @@ class ConnectionTest extends TestCase
|
|||||||
public function testFromDsnWithOptions()
|
public function testFromDsnWithOptions()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
new Connection(['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false, 'stream_max_entries' => 20000], [
|
Connection::fromDsn('redis://localhost', ['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false, 'serializer' => 2]),
|
||||||
'host' => 'localhost',
|
Connection::fromDsn('redis://localhost/queue/group1/consumer1?serializer=2&auto_setup=0')
|
||||||
'port' => 6379,
|
|
||||||
], [
|
|
||||||
'serializer' => 2,
|
|
||||||
]),
|
|
||||||
Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2, 'auto_setup' => false, 'stream_max_entries' => 20000])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +94,21 @@ class ConnectionTest extends TestCase
|
|||||||
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
||||||
|
|
||||||
$redis->expects($this->exactly(1))->method('auth')
|
$redis->expects($this->exactly(1))->method('auth')
|
||||||
->with('password');
|
->with('password')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
Connection::fromDsn('redis://password@localhost/queue', [], $redis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailedAuth()
|
||||||
|
{
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Redis connection failed');
|
||||||
|
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
||||||
|
|
||||||
|
$redis->expects($this->exactly(1))->method('auth')
|
||||||
|
->with('password')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
Connection::fromDsn('redis://password@localhost/queue', [], $redis);
|
Connection::fromDsn('redis://password@localhost/queue', [], $redis);
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,8 @@ class WorkerTest extends TestCase
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// old message acknowledged
|
// old message rejected
|
||||||
$this->assertSame(1, $receiver->getAcknowledgeCount());
|
$this->assertSame(1, $receiver->getRejectCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnrecoverableMessageHandlingExceptionPreventsRetries()
|
public function testUnrecoverableMessageHandlingExceptionPreventsRetries()
|
||||||
|
@ -13,7 +13,7 @@ namespace Symfony\Component\Messenger\Transport\Doctrine;
|
|||||||
|
|
||||||
use Doctrine\DBAL\Connection as DBALConnection;
|
use Doctrine\DBAL\Connection as DBALConnection;
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\DBALException;
|
||||||
use Doctrine\DBAL\Driver\Statement;
|
use Doctrine\DBAL\Driver\ResultStatement;
|
||||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||||
use Doctrine\DBAL\Query\QueryBuilder;
|
use Doctrine\DBAL\Query\QueryBuilder;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
@ -123,8 +123,14 @@ class Connection
|
|||||||
$body,
|
$body,
|
||||||
json_encode($headers),
|
json_encode($headers),
|
||||||
$this->configuration['queue_name'],
|
$this->configuration['queue_name'],
|
||||||
self::formatDateTime($now),
|
$now,
|
||||||
self::formatDateTime($availableAt),
|
$availableAt,
|
||||||
|
], [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Type::DATETIME,
|
||||||
|
Type::DATETIME,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->driverConnection->lastInsertId();
|
return $this->driverConnection->lastInsertId();
|
||||||
@ -142,7 +148,8 @@ class Connection
|
|||||||
// use SELECT ... FOR UPDATE to lock table
|
// use SELECT ... FOR UPDATE to lock table
|
||||||
$doctrineEnvelope = $this->executeQuery(
|
$doctrineEnvelope = $this->executeQuery(
|
||||||
$query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(),
|
$query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(),
|
||||||
$query->getParameters()
|
$query->getParameters(),
|
||||||
|
$query->getParameterTypes()
|
||||||
)->fetch();
|
)->fetch();
|
||||||
|
|
||||||
if (false === $doctrineEnvelope) {
|
if (false === $doctrineEnvelope) {
|
||||||
@ -159,8 +166,10 @@ class Connection
|
|||||||
->where('id = ?');
|
->where('id = ?');
|
||||||
$now = new \DateTime();
|
$now = new \DateTime();
|
||||||
$this->executeQuery($queryBuilder->getSQL(), [
|
$this->executeQuery($queryBuilder->getSQL(), [
|
||||||
self::formatDateTime($now),
|
$now,
|
||||||
$doctrineEnvelope['id'],
|
$doctrineEnvelope['id'],
|
||||||
|
], [
|
||||||
|
Type::DATETIME,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->driverConnection->commit();
|
$this->driverConnection->commit();
|
||||||
@ -227,7 +236,7 @@ class Connection
|
|||||||
->select('COUNT(m.id) as message_count')
|
->select('COUNT(m.id) as message_count')
|
||||||
->setMaxResults(1);
|
->setMaxResults(1);
|
||||||
|
|
||||||
return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchColumn();
|
return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAll(int $limit = null): array
|
public function findAll(int $limit = null): array
|
||||||
@ -237,7 +246,7 @@ class Connection
|
|||||||
$queryBuilder->setMaxResults($limit);
|
$queryBuilder->setMaxResults($limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchAll();
|
$data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchAll();
|
||||||
|
|
||||||
return array_map(function ($doctrineEnvelope) {
|
return array_map(function ($doctrineEnvelope) {
|
||||||
return $this->decodeEnvelopeHeaders($doctrineEnvelope);
|
return $this->decodeEnvelopeHeaders($doctrineEnvelope);
|
||||||
@ -266,9 +275,12 @@ class Connection
|
|||||||
->andWhere('m.available_at <= ?')
|
->andWhere('m.available_at <= ?')
|
||||||
->andWhere('m.queue_name = ?')
|
->andWhere('m.queue_name = ?')
|
||||||
->setParameters([
|
->setParameters([
|
||||||
self::formatDateTime($redeliverLimit),
|
$redeliverLimit,
|
||||||
self::formatDateTime($now),
|
$now,
|
||||||
$this->configuration['queue_name'],
|
$this->configuration['queue_name'],
|
||||||
|
], [
|
||||||
|
Type::DATETIME,
|
||||||
|
Type::DATETIME,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,12 +291,10 @@ class Connection
|
|||||||
->from($this->configuration['table_name'], 'm');
|
->from($this->configuration['table_name'], 'm');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function executeQuery(string $sql, array $parameters = []): Statement
|
private function executeQuery(string $sql, array $parameters = [], array $types = []): ResultStatement
|
||||||
{
|
{
|
||||||
$stmt = null;
|
|
||||||
try {
|
try {
|
||||||
$stmt = $this->driverConnection->prepare($sql);
|
$stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
|
||||||
$stmt->execute($parameters);
|
|
||||||
} catch (TableNotFoundException $e) {
|
} catch (TableNotFoundException $e) {
|
||||||
if ($this->driverConnection->isTransactionActive()) {
|
if ($this->driverConnection->isTransactionActive()) {
|
||||||
throw $e;
|
throw $e;
|
||||||
@ -294,11 +304,7 @@ class Connection
|
|||||||
if ($this->autoSetup) {
|
if ($this->autoSetup) {
|
||||||
$this->setup();
|
$this->setup();
|
||||||
}
|
}
|
||||||
// statement not prepared ? SQLite throw on exception on prepare if the table does not exist
|
$stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
|
||||||
if (null === $stmt) {
|
|
||||||
$stmt = $this->driverConnection->prepare($sql);
|
|
||||||
}
|
|
||||||
$stmt->execute($parameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $stmt;
|
return $stmt;
|
||||||
@ -331,11 +337,6 @@ class Connection
|
|||||||
return $schema;
|
return $schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function formatDateTime(\DateTimeInterface $dateTime): string
|
|
||||||
{
|
|
||||||
return $dateTime->format('Y-m-d\TH:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
|
private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
|
||||||
{
|
{
|
||||||
$doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
|
$doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
|
||||||
|
@ -20,6 +20,7 @@ use Symfony\Component\Messenger\Exception\TransportException;
|
|||||||
*
|
*
|
||||||
* @author Alexander Schranz <alexander@sulu.io>
|
* @author Alexander Schranz <alexander@sulu.io>
|
||||||
* @author Antoine Bluchet <soyuka@gmail.com>
|
* @author Antoine Bluchet <soyuka@gmail.com>
|
||||||
|
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @final
|
* @final
|
||||||
@ -53,8 +54,8 @@ class Connection
|
|||||||
$this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379);
|
$this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379);
|
||||||
$this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP);
|
$this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP);
|
||||||
|
|
||||||
if (isset($connectionCredentials['auth'])) {
|
if (isset($connectionCredentials['auth']) && !$this->connection->auth($connectionCredentials['auth'])) {
|
||||||
$this->connection->auth($connectionCredentials['auth']);
|
throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) {
|
if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) {
|
||||||
@ -76,9 +77,9 @@ class Connection
|
|||||||
|
|
||||||
$pathParts = explode('/', $parsedUrl['path'] ?? '');
|
$pathParts = explode('/', $parsedUrl['path'] ?? '');
|
||||||
|
|
||||||
$stream = $pathParts[1] ?? null;
|
$stream = $pathParts[1] ?? $redisOptions['stream'] ?? null;
|
||||||
$group = $pathParts[2] ?? null;
|
$group = $pathParts[2] ?? $redisOptions['group'] ?? null;
|
||||||
$consumer = $pathParts[3] ?? null;
|
$consumer = $pathParts[3] ?? $redisOptions['consumer'] ?? null;
|
||||||
|
|
||||||
$connectionCredentials = [
|
$connectionCredentials = [
|
||||||
'host' => $parsedUrl['host'] ?? '127.0.0.1',
|
'host' => $parsedUrl['host'] ?? '127.0.0.1',
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
namespace Symfony\Component\Messenger;
|
namespace Symfony\Component\Messenger;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
|
||||||
use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
|
use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
|
||||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
|
use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
|
||||||
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
|
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
|
||||||
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
|
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
|
||||||
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
||||||
@ -134,6 +134,13 @@ class Worker implements WorkerInterface
|
|||||||
try {
|
try {
|
||||||
$envelope = $this->bus->dispatch($envelope->with(new ReceivedStamp($transportName)));
|
$envelope = $this->bus->dispatch($envelope->with(new ReceivedStamp($transportName)));
|
||||||
} catch (\Throwable $throwable) {
|
} catch (\Throwable $throwable) {
|
||||||
|
$rejectFirst = $throwable instanceof RejectRedeliveredMessageException;
|
||||||
|
if ($rejectFirst) {
|
||||||
|
// redelivered messages are rejected first so that continuous failures in an event listener or while
|
||||||
|
// publishing for retry does not cause infinite redelivery loops
|
||||||
|
$receiver->reject($envelope);
|
||||||
|
}
|
||||||
|
|
||||||
if ($throwable instanceof HandlerFailedException) {
|
if ($throwable instanceof HandlerFailedException) {
|
||||||
$envelope = $throwable->getEnvelope();
|
$envelope = $throwable->getEnvelope();
|
||||||
}
|
}
|
||||||
@ -152,18 +159,18 @@ class Worker implements WorkerInterface
|
|||||||
|
|
||||||
// add the delay and retry stamp info + remove ReceivedStamp
|
// add the delay and retry stamp info + remove ReceivedStamp
|
||||||
$retryEnvelope = $envelope->with(new DelayStamp($delay))
|
$retryEnvelope = $envelope->with(new DelayStamp($delay))
|
||||||
->with(new RedeliveryStamp($retryCount, $transportName, $throwable->getMessage(), $this->flattenedException($throwable)))
|
->with(new RedeliveryStamp($retryCount, $transportName))
|
||||||
->withoutAll(ReceivedStamp::class);
|
->withoutAll(ReceivedStamp::class);
|
||||||
|
|
||||||
// re-send the message
|
// re-send the message for retry
|
||||||
$this->bus->dispatch($retryEnvelope);
|
$this->bus->dispatch($retryEnvelope);
|
||||||
// acknowledge the previous message has received
|
|
||||||
$receiver->ack($envelope);
|
|
||||||
} else {
|
} else {
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->logger->critical('Error thrown while handling message {class}. Removing from transport after {retryCount} retries. Error: "{error}"', $context + ['retryCount' => $retryCount, 'error' => $throwable->getMessage(), 'exception' => $throwable]);
|
$this->logger->critical('Error thrown while handling message {class}. Removing from transport after {retryCount} retries. Error: "{error}"', $context + ['retryCount' => $retryCount, 'error' => $throwable->getMessage(), 'exception' => $throwable]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$rejectFirst) {
|
||||||
$receiver->reject($envelope);
|
$receiver->reject($envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,17 +222,4 @@ class Worker implements WorkerInterface
|
|||||||
|
|
||||||
return $retryStrategy->isRetryable($envelope);
|
return $retryStrategy->isRetryable($envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function flattenedException(\Throwable $throwable): ?FlattenException
|
|
||||||
{
|
|
||||||
if (!class_exists(FlattenException::class)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($throwable instanceof HandlerFailedException) {
|
|
||||||
$throwable = $throwable->getNestedExceptions()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return FlattenException::createFromThrowable($throwable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user