Skip to content

[mailer] When using the FailoverTransport it not always picks the first defined transport #37593

@evertharmeling

Description

@evertharmeling

Symfony version(s) affected: 5.1

Description
According to the docs, specifically the following part:

The mailer will start using the first transport. If the sending fails, the mailer won’t retry it with the other transports, but it will switch to the next transport automatically for the following deliveries.

If I understand it correctly, it would always pick the first defined transport and only when this transport fails would use the next defined transport in line.

However this is not the case. This is introduced with the following change 6ebe83c.

How to reproduce
Run the following (a bit of hacky) test multiple times to trigger the 'randomized' code.

    public function testPickingFirstTransport()
    {
        $transport1 = Transport::fromDsn('smtp://mailserver-01:1025');
        $transport2 = Transport::fromDsn('smtp://mailserver-02:1026');

        $failoverTransport = new FailoverTransport([
            $transport1,
            $transport2,
        ]);

        $reflector = new ReflectionClass($failoverTransport);
        $method = $reflector->getMethod('getNextTransport');
        $method->setAccessible(true);

        $usedTransport = $method->invoke($failoverTransport);
        Assert::assertEquals($transport1, $usedTransport);
    }

https://github.com/evertharmeling/failover-transport

Possible Solution
Because the FailoverTransport extends the RoundRobinTransport and triggers the parent::getNextTransport();

  • Do not rely on the parent::getNextTransport() and rely on an own implementation, of picking the transport in defined order and moving to the next when marked as a 'dead transport'.
  • Make the $cursor variable of the RoundRobinTransport protected, so it can be initiated as protected $cursor = 0; in the FailoverTransport. So it would bypass the 'randomized' code defined in RoundRobinTransport::getNextTransport().

Additional context
If I misinterpreted the docs and misjudged the functionality, ignore this issue ;)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions