Skip to content

TraceableMessengerTransport strips capability interfaces from the wrapped transport #215

@gaelreyrol

Description

@gaelreyrol

Summary

TraceableMessengerTransport is a decorator over the user's real Messenger transport, but it only implements Symfony\Component\Messenger\Transport\TransportInterface. Messenger transports commonly implement several additional capability interfaces, and because those are not forwarded through the decorator, wrapping a transport with trace(...) silently removes functionality from the host application.

Reproduction

With symfony/doctrine-messenger installed:

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: 'trace(%env(MESSENGER_TRANSPORT_DSN)%)' # doctrine://default
$ php bin/console messenger:setup-transports
 ! [NOTE] The "async" transport does not support setup.

Without the trace(...) wrapper the same command creates the messenger_messages table as expected. The current workaround is either to set auto_setup=1 on the DSN (racy and not desirable in production) or to temporarily unwrap the decorator at deploy time.

Affected capabilities

Every Messenger capability interface that the underlying transport advertises is lost through the decorator:

Interface Consequence when stripped
SetupableTransportInterface messenger:setup-transports is a no-op; provisioning must rely on auto_setup=1 or be done out-of-band.
MessageCountAwareInterface messenger:stats reports nothing; rate-limited worker loses backpressure info.
ListableReceiverInterface messenger:failed:show / :retry / :remove do not work when the failed transport is wrapped.
QueueReceiverInterface messenger:consume --queues= filter is ignored.
KeepaliveReceiverInterface (Messenger >= 7.3) Long-running handlers can no longer keep the broker lease alive.
CloseableTransportInterface Graceful shutdown / connection cleanup is skipped.
Symfony\Contracts\Service\ResetInterface Worker per-message state reset is skipped (relevant for InMemoryTransport in tests).

Expected behavior

A decorator should be transparent with respect to the capabilities of the object it wraps. Options:

  1. Single class, guarded forwarding: TraceableMessengerTransport declares every capability interface and each method checks instanceof on the inner transport, forwarding when supported and throwing LogicException otherwise. Simple, but makes \$decorator instanceof ListableReceiverInterface structurally true even when the inner transport does not support it.
  2. Polymorphic decorators: the factory inspects the inner transport's implemented interfaces and returns a precisely-typed decorator so instanceof remains truthful. More code but no capability-advertisement lie.

For control-plane methods (setup, close, reset, getMessageCount, find, all) spans are probably not desirable. keepalive is the only additional method worth instrumenting alongside the existing send / get / ack / reject spans.

Test coverage to add

  • Unit: each capability method forwards to the inner transport when supported; behaves appropriately (throw or no-op, depending on the chosen design) when not.
  • Functional: a trace(doctrine://default) transport under tests/Functional/Application/ for which messenger:setup-transports creates the messenger_messages table. The existing MessengerTransportTracingTest already covers the TransportInterface surface with trace(in-memory://default) and can be extended to assert reset() forwarding.

Environment

  • symfony/messenger ^7.4
  • bundle current main

References

  • src/Instrumentation/Symfony/Messenger/TraceableMessengerTransport.php
  • src/Instrumentation/Symfony/Messenger/TraceableMessengerTransportFactory.php

Metadata

Metadata

Assignees

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions