bug #31024 [Mailer] fixed roundrobin test one dead which should recover (scuben, fabpot)
This PR was merged into the 4.3-dev branch. Discussion ---------- [Mailer] fixed roundrobin test one dead which should recover | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | WIP <!-- please add some, will be required by reviewers --> | License | MIT | Doc PR | n/a The Test `testSendOneDeadButRecover` is not checking the recovery part of its job. I fixed that by adding more `send` calls and added another test so that both recoveries (within retry period and not within retry period) are covered. The `RoundRobinTransport` had a bug where the transport is dead but not yet in the `retryPeriod`. In that case the transport would not have been added back to the stack and thus got lost. Fixed that but that required an additional check if all transports are dead to prevent an infinite loop. Commits -------5d4d4e7a71
fixed roundrobin dead transport which should recoverccbb171312
fixed roundrobin dead transport which should recover
This commit is contained in:
commit
751baaf3c7
@ -14,6 +14,7 @@ namespace Symfony\Component\Mailer\Tests\Transport;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Symfony\Component\Mailer\Transport\FailoverTransport;
|
||||
use Symfony\Component\Mailer\Transport\RoundRobinTransport;
|
||||
use Symfony\Component\Mailer\Transport\TransportInterface;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
@ -36,8 +37,11 @@ class FailoverTransportTest extends TestCase
|
||||
$t2->expects($this->never())->method('send');
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
}
|
||||
|
||||
public function testSendAllDead()
|
||||
@ -50,6 +54,7 @@ class FailoverTransportTest extends TestCase
|
||||
$this->expectException(TransportException::class);
|
||||
$this->expectExceptionMessage('All transports failed.');
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1, $t2]);
|
||||
}
|
||||
|
||||
public function testSendOneDead()
|
||||
@ -60,7 +65,57 @@ class FailoverTransportTest extends TestCase
|
||||
$t2->expects($this->exactly(3))->method('send');
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
}
|
||||
|
||||
public function testSendOneDeadAndRecoveryWithinRetryPeriod()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->expects($this->at(0))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t1->expects($this->at(1))->method('send');
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->at(0))->method('send');
|
||||
$t2->expects($this->at(1))->method('send');
|
||||
$t2->expects($this->at(2))->method('send');
|
||||
$t2->expects($this->at(3))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t = new FailoverTransport([$t1, $t2], 6);
|
||||
$t->send(new RawMessage('')); // t1>fail - t2>sent
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
sleep(4);
|
||||
$t->send(new RawMessage('')); // t2>sent
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
sleep(4);
|
||||
$t->send(new RawMessage('')); // t2>sent
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
sleep(4);
|
||||
$t->send(new RawMessage('')); // t2>fail - t1>sent
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
sleep(4);
|
||||
$t->send(new RawMessage('')); // t1>sent
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
}
|
||||
|
||||
public function testSendAllDeadWithinRetryPeriod()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->expects($this->at(0))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t1->expects($this->once())->method('send');
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->at(0))->method('send');
|
||||
$t2->expects($this->at(1))->method('send');
|
||||
$t2->expects($this->at(2))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t2->expects($this->exactly(3))->method('send');
|
||||
$t = new FailoverTransport([$t1, $t2], 40);
|
||||
$t->send(new RawMessage(''));
|
||||
sleep(4);
|
||||
$t->send(new RawMessage(''));
|
||||
sleep(4);
|
||||
$this->expectException(TransportException::class);
|
||||
$this->expectExceptionMessage('All transports failed.');
|
||||
$t->send(new RawMessage(''));
|
||||
}
|
||||
|
||||
@ -80,4 +135,15 @@ class FailoverTransportTest extends TestCase
|
||||
sleep(1);
|
||||
$t->send(new RawMessage(''));
|
||||
}
|
||||
|
||||
private function assertTransports(RoundRobinTransport $transport, int $cursor, array $deadTransports)
|
||||
{
|
||||
$p = new \ReflectionProperty(RoundRobinTransport::class, 'cursor');
|
||||
$p->setAccessible(true);
|
||||
$this->assertSame($cursor, $p->getValue($transport));
|
||||
|
||||
$p = new \ReflectionProperty(RoundRobinTransport::class, 'deadTransports');
|
||||
$p->setAccessible(true);
|
||||
$this->assertSame($deadTransports, iterator_to_array($p->getValue($transport)));
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,11 @@ class RoundRobinTransportTest extends TestCase
|
||||
$t2->expects($this->once())->method('send');
|
||||
$t = new RoundRobinTransport([$t1, $t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
}
|
||||
|
||||
public function testSendAllDead()
|
||||
@ -50,6 +53,7 @@ class RoundRobinTransportTest extends TestCase
|
||||
$this->expectException(TransportException::class);
|
||||
$this->expectExceptionMessage('All transports failed.');
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, [$t1, $t2]);
|
||||
}
|
||||
|
||||
public function testSendOneDead()
|
||||
@ -60,20 +64,57 @@ class RoundRobinTransportTest extends TestCase
|
||||
$t2->expects($this->exactly(3))->method('send');
|
||||
$t = new RoundRobinTransport([$t1, $t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, [$t1]);
|
||||
}
|
||||
|
||||
public function testSendOneDeadButRecover()
|
||||
public function testSendOneDeadAndRecoveryNotWithinRetryPeriod()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->expects($this->at(0))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t1->expects($this->at(1))->method('send');
|
||||
$t1->expects($this->exactly(4))->method('send');
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->once())->method('send');
|
||||
$t = new RoundRobinTransport([$t1, $t2], 1);
|
||||
$t2->expects($this->once())->method('send')->will($this->throwException(new TransportException()));
|
||||
$t = new RoundRobinTransport([$t1, $t2], 60);
|
||||
$t->send(new RawMessage(''));
|
||||
sleep(2);
|
||||
$this->assertTransports($t, 1, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
}
|
||||
|
||||
public function testSendOneDeadAndRecoveryWithinRetryPeriod()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->expects($this->exactly(3))->method('send');
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->at(0))->method('send')->will($this->throwException(new TransportException()));
|
||||
$t2->expects($this->at(1))->method('send');
|
||||
$t = new RoundRobinTransport([$t1, $t2], 3);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, [$t2]);
|
||||
sleep(5);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 0, []);
|
||||
$t->send(new RawMessage(''));
|
||||
$this->assertTransports($t, 1, []);
|
||||
}
|
||||
|
||||
private function assertTransports(RoundRobinTransport $transport, int $cursor, array $deadTransports)
|
||||
{
|
||||
$p = new \ReflectionProperty($transport, 'cursor');
|
||||
$p->setAccessible(true);
|
||||
$this->assertSame($cursor, $p->getValue($transport));
|
||||
|
||||
$p = new \ReflectionProperty($transport, 'deadTransports');
|
||||
$p->setAccessible(true);
|
||||
$this->assertSame($deadTransports, iterator_to_array($p->getValue($transport)));
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class RoundRobinTransport implements TransportInterface
|
||||
private $deadTransports;
|
||||
private $transports = [];
|
||||
private $retryPeriod;
|
||||
private $cursor = 0;
|
||||
|
||||
/**
|
||||
* @param TransportInterface[] $transports
|
||||
@ -62,20 +63,26 @@ class RoundRobinTransport implements TransportInterface
|
||||
*/
|
||||
protected function getNextTransport(): ?TransportInterface
|
||||
{
|
||||
while ($transport = array_shift($this->transports)) {
|
||||
$cursor = $this->cursor;
|
||||
while (true) {
|
||||
$transport = $this->transports[$cursor];
|
||||
|
||||
if (!$this->isTransportDead($transport)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((microtime(true) - $this->deadTransports[$transport]) > $this->retryPeriod) {
|
||||
$this->deadTransports->detach($transport);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->cursor === $cursor = $this->moveCursor($cursor)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($transport) {
|
||||
$this->transports[] = $transport;
|
||||
}
|
||||
$this->cursor = $this->moveCursor($cursor);
|
||||
|
||||
return $transport;
|
||||
}
|
||||
@ -84,4 +91,9 @@ class RoundRobinTransport implements TransportInterface
|
||||
{
|
||||
return $this->deadTransports->contains($transport);
|
||||
}
|
||||
|
||||
private function moveCursor(int $cursor): int
|
||||
{
|
||||
return ++$cursor >= \count($this->transports) ? 0 : $cursor;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user