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

Skip to content

[Messenger] widen retry catch to \AMQPException#2

Merged
modess merged 1 commit into
sweetspotio:amqps-supportfrom
gustaf-ag47:fix/amqp-connection-exception-rethrow-type
Apr 10, 2026
Merged

[Messenger] widen retry catch to \AMQPException#2
modess merged 1 commit into
sweetspotio:amqps-supportfrom
gustaf-ag47:fix/amqp-connection-exception-rethrow-type

Conversation

@gustaf-ag47
Copy link
Copy Markdown

@gustaf-ag47 gustaf-ag47 commented Apr 10, 2026

Problem

PR #1 backported withConnectionExceptionRetry() from symfony/symfony#54167, catching \AMQPConnectionException around publish(). After deploying to production, the errors continued unchanged.

Production logs show bare \AMQPException — not \AMQPConnectionException — thrown from ext-amqp's C code during declareExchange():

AMQPException: "Library error: a SSL error occurred"
  at Connection.php:366  ($this->exchange()->declareExchange())

The retry wrapper caught \AMQPConnectionException only, so it never fired.

Why bare \AMQPException?

ext-amqp's C code has a central php_amqp_zend_throw_exception() that maps AMQP_STATUS_SSL_ERRORAMQPConnectionException. But declareExchange() uses php_amqp_zend_throw_exception_short() which may not have the same override — unmapped library_error codes fall to the default branch → bare AMQPException.

We confirmed the exception originates from C code (not PHP) via Exception::getFile()/getLine() semantics: C extension exceptions report the PHP call site that invoked the C function. Line 366 = declareExchange() call site, not the PHP-level channel() rethrow at line 397 (which would report line 397).

Root cause was also misdiagnosed

PR #1 assumed 350s NLB idle TCP timeout killing stale connections. Investigation proved this wrong for HTTP publishers:

  • public/index.php creates new Kernel() per request — no service persistence across FPM requests
  • There is no stale connection to kill on the publisher side
  • 5.47ms failure duration confirms instant rejection, not a timeout

The actual failure: intermittent TLS errors on fresh connections during declareExchange().

Fix

Two-line change:

  1. Widen catch: catch (\AMQPConnectionException $e)catch (\AMQPException $e) — catches all ext-amqp exceptions during publish, including bare AMQPException from declareExchange()
  2. Rename helper: withConnectionExceptionRetrywithAmqpExceptionRetry — name matches new behavior

That's it. +3 / -3 lines. No other behavioral changes.

Why not also fix the channel() rethrow?

channel() catches \AMQPConnectionException and rethrows as \AMQPException (stock Symfony behavior). With the wider catch on \AMQPException, this is already handled — both types are caught. Changing the rethrow type would diverge from stock Symfony for zero practical benefit.

Why not move clearWhenDisconnected() inside the retry?

Codex review flagged that clearWhenDisconnected() runs before the retry wrapper, meaning a connect() failure there is unretried. True, but the retry wrapper already handles stale connections via catch + clear()clearWhenDisconnected() is just an optimization to avoid wasting one retry attempt. With 3 retries available, this isn't worth the code churn.

Safety

clear() drops all cached state (channel, queues, exchange, delay exchange) between retries. Max 3 retries bounds the cost. Worst case: 3 extra reconnection attempts before the final exception propagates.

Divergence from upstream

Upstream symfony/symfony#54167 catches only \AMQPConnectionException. Upstream has the same bug — their tests mock at the exchange level and never exercise real C code exception paths.

Consumer

sweetspotio/sweetspot-api-platform will bump composer.lock after merge.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f315bd1-09a7-4ef2-b273-2e911553f748

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@gustaf-ag47 gustaf-ag47 marked this pull request as ready for review April 10, 2026 11:54
Production logs show bare \AMQPException (not \AMQPConnectionException)
thrown from ext-amqp C code during declareExchange() on fresh
connections. The C extension doesn't always promote SSL errors to
AMQPConnectionException — unmapped library_error codes fall to the
default branch in php_amqp_zend_throw_exception().

Widen the catch from \AMQPConnectionException to \AMQPException so the
retry wrapper catches all ext-amqp failures during publish. Rename the
helper from withConnectionExceptionRetry to withAmqpExceptionRetry to
match the new behavior.

Safe: clear() drops all cached state between retries, and max 3 retries
bounds the cost. Worst case is 3 extra reconnection attempts.
@gustaf-ag47 gustaf-ag47 force-pushed the fix/amqp-connection-exception-rethrow-type branch from 6b2edd3 to 8c77699 Compare April 10, 2026 12:13
@gustaf-ag47 gustaf-ag47 changed the title [Messenger] preserve AMQPConnectionException type in channel() rethrow [Messenger] widen retry catch to \AMQPException Apr 10, 2026
@modess modess merged commit b570aba into sweetspotio:amqps-support Apr 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants