Skip to content

java: a refused listener subscription closes the connection it started (#7264) - #7267

Open
delchev wants to merge 1 commit into
masterfrom
issue-7264-listener-connection-leak
Open

java: a refused listener subscription closes the connection it started (#7264)#7267
delchev wants to merge 1 commit into
masterfrom
issue-7264-listener-connection-leak

Conversation

@delchev

@delchev delchev commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #7264

ActiveMQConnectionArtifactsFactory.createConnection start()s the connection before returning it, so by the time createSession / createTopic / configureRedeliveryPolicy / createDurableSubscriber / setMessageListener can throw, a transport socket and its ActiveMQ thread exist. The catch in ListenerClassConsumer.subscribe reported the failure and returned null without closing them.

Before #7220 that ran once per rebuild, so the cost was one orphaned connection per refused subscription. Now JavaConsumersReconciler re-runs subscribe every 30 s for as long as the refusal lasts, and the realistic refusals at exactly this point are permanent: an external broker (DIRIGIBLE_MESSAGING_BROKER_URL) whose destination authorization refuses the consumer with a JMSSecurityException, or a destination name its policy rejects. One started connection per subscription per tenant per tick, at DEBUG after the first WARN — an outage the retry itself manufactures.

Change

  • connection is held in a local declared before the try, and the catch calls a new closeQuietly(label, connection).
  • The close failure is logged at DEBUG with the throwable: the refusal is already reported, and a broker that just refused the subscription is likely to refuse the close too, so it must not add a second line per tick to a lasting outage.

Tests

Three cases in ListenerClassConsumerRetryTest — none of the existing ones makes anything after createConnection throw, so the path was untested. The mock factory now hands out the connection and refuses the session with a JMSSecurityException, which is where a broker's destination authorization really says no:

  • aRefusalAfterTheConnectionWasStartedClosesIt — nothing subscribed, and both tenants' connections closed.
  • everyRetryOfALastingRefusalClosesItsOwnConnection — three passes close three per tenant; the retry accumulates nothing.
  • theSubscriptionStillOpensAfterAClosedAttempt — closing an attempt is not recording it; the subscription opens once on the first pass the broker accepts.

mvn -pl components/engine/engine-java test -Dtest='ListenerClassConsumer*Test' → 24 tests, all green. formatter:validate and the release javadoc profile pass on the module.

🤖 Generated with Claude Code

#7264)

The connection factory start()s the connection before returning it, so a
refusal that arrives after that - the session, the destination, the
redelivery policy, the durable subscriber, the listener - leaves a live
transport socket and its ActiveMQ thread behind. The catch reported the
failure and returned null without closing it.

Before the reconciliation retry (#7220) that cost one orphaned connection
per refused subscription per rebuild. Now the attach repeats every 30
seconds for as long as the refusal lasts, and the realistic refusals at
exactly this point are permanent ones - an external broker whose
destination authorization refuses createDurableSubscriber with a
JMSSecurityException, or a destination name its policy rejects - so the
retry manufactures the outage it was added to prevent: one started
connection per subscription per tenant per tick until the file-descriptor
or thread limit is reached.

The connection is held in a local declared before the try and closed in
the catch. The close failure is DEBUG: the refusal is already reported and
a broker that just said no is likely to refuse the close too, so it must
not add a second line per tick to a lasting outage.

Three retry-test cases cover it - none of the existing ones makes anything
after createConnection throw. Closing an attempt is also not recording it:
the subscription still opens on the first pass the broker accepts.
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.

java: the listener retry leaks a started JMS connection on every reconciliation tick when the refusal comes after createConnection (#7220)

1 participant