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

Skip to content

[Messenger] Ability to use transactions while sending messages with AMQP transport #51656

Open
@michalsitek

Description

@michalsitek

Description

By default, AMQP doesn't guarantee message delivery to the queue. There are 2 ways of ensuring delivery:

  1. using Publisher Confirms (specifically using RabbitMQ: https://www.rabbitmq.com/confirms.html#publisher-confirms). It is already implemented as confirm_timeout option.
  2. using transactions

Currently there is no option to use transactions with Symfony Messenger to ensure message deliverability. Using this option wouldn't even have a big effect on performance, since all the messages are published one-by-one by this component.

Example

Introducing simple transport attribute, like transactional:

    email:
        dsn: '%env(resolve:MESSENGER_DSN)%'
        options:
            transactional: true
            exchange:
                name: some-name
            queues:
                some-name: ~

Which could be implemented with a simple change to Symfony\Component\Messenger\Bridge\Amqp\Transport\Connection method publishOnExchange.

Before:

    private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
    {
        $attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
        $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
        $attributes['delivery_mode'] ??= 2;
        $attributes['timestamp'] ??= time();

        $this->lastActivityTime = time();

        $exchange->publish(
            $body,
            $routingKey,
            $amqpStamp ? $amqpStamp->getFlags() : \AMQP_NOPARAM,
            $attributes
        );

        if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
            $this->channel()->waitForConfirm((float) $this->connectionOptions['confirm_timeout']);
        }
    }

After:

    private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
    {
        $attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
        $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
        $attributes['delivery_mode'] ??= 2;
        $attributes['timestamp'] ??= time();
        $transactional = $this->connectionOptions['transactional'] ??= false;

        $this->lastActivityTime = time();
        
        if ($transactional) {
            $exchange->getChannel()->startTransaction();
        }

        $exchange->publish(
            $body,
            $routingKey,
            $amqpStamp ? $amqpStamp->getFlags() : \AMQP_NOPARAM,
            $attributes
        );
        
        if ($transactional) {
            $exchange->getChannel()->commitTransaction();
        }

        if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
            $this->channel()->waitForConfirm((float) $this->connectionOptions['confirm_timeout']);
        }
    }

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