Skip to content

ARTEMIS-6142 Ensure InVM connections are removed on graceful close#6566

Open
MrEasy wants to merge 1 commit into
apache:mainfrom
MrEasy:ARTEMIS-6142
Open

ARTEMIS-6142 Ensure InVM connections are removed on graceful close#6566
MrEasy wants to merge 1 commit into
apache:mainfrom
MrEasy:ARTEMIS-6142

Conversation

@MrEasy

@MrEasy MrEasy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Two defects could cause RemotingServiceImpl.removeConnection to be skipped even though ActiveMQConnection.close() was called over the InVM transport, leaking the server-side connection permanently (InVM connection-ttl is -1, so the failure-check reaper never removes it):

  1. InVMAcceptor/InVMConnector reported every close - including a graceful close() - to the peer as a failure (disconnect()), routing it through issueFailure() with a RemoteDisconnectException instead of issueClose(). The graceful/failure flag is now propagated so a graceful close stays graceful, matching Netty semantics.

  2. issueClose() gated removal on !isSupportReconnect(). A graceful close is an explicit teardown and must always remove the connection; reconnect/reattach support must not keep a gracefully closed connection lingering. The guard is removed for the graceful-close path.

Adds InVMConnectionLeakStressTest, a concurrent flood of reconnect-capable (confirmation-window enabled) connections that widens the timing window in which the leak occurs.

Fix race condition in InVMConnection

  • closed is now volatile (it's also read unsynchronized from the write executor at line 218).
  • internalClose is now fully atomic: the closed check, the connectionDestroyed callback, and the closing/closed state transitions all happen inside a single synchronized (this) block. The previous lock-free if (closing) return; fast path was removed. Why this matters: The exactly-once firing already held in the old code, so that alone wasn't the bug. The real latent defect was a premature return: the lock-free fast path let a losing concurrent caller's close()/disconnect() return before the winning thread had actually fired connectionDestroyed. Callers could then proceed believing the connection was fully torn down while the destroy callback (and thus the downstream RemotingServiceImpl.removeConnection chain) hadn't run yet. Now every caller blocks on the monitor until the destroy callback has completed. Test — InVMConnectionTest.java
  • Added testConcurrentCloseFiresConnectionDestroyedExactlyOnce: 16 threads aligned on a CyclicBarrier all call close()/disconnect() at once, over 50 rounds. The listener sleeps briefly inside connectionDestroyed to widen the race window. Each worker asserts, right after its call returns, that the destroy has already fired.
  • Asserts both exactly-once firing and zero premature returns.

Two defects could cause RemotingServiceImpl.removeConnection to be skipped
even though ActiveMQConnection.close() was called over the InVM transport,
leaking the server-side connection permanently (InVM connection-ttl is -1, so
the failure-check reaper never removes it):

1. InVMAcceptor/InVMConnector reported every close - including a graceful
   close() - to the peer as a failure (disconnect()), routing it through
   issueFailure() with a RemoteDisconnectException instead of issueClose().
   The graceful/failure flag is now propagated so a graceful close stays
   graceful, matching Netty semantics.

2. issueClose() gated removal on !isSupportReconnect(). A graceful close is an
   explicit teardown and must always remove the connection; reconnect/reattach
   support must not keep a gracefully closed connection lingering. The guard is
   removed for the graceful-close path.

Adds InVMConnectionLeakStressTest, a concurrent flood of reconnect-capable
(confirmation-window enabled) connections that widens the timing window in
which the leak occurs.

Fix race condition in InVMConnection

- closed is now volatile (it's also read unsynchronized from the write executor at line 218).
- internalClose is now fully atomic: the closed check, the connectionDestroyed callback, and the closing/closed state transitions all happen inside a single synchronized (this) block. The previous lock-free if (closing) return; fast path was removed.
Why this matters: The exactly-once firing already held in the old code, so that alone wasn't the bug. The real latent defect was a premature return: the lock-free fast path let a losing concurrent caller's close()/disconnect() return before the winning thread had actually fired connectionDestroyed. Callers could then proceed believing the connection was fully torn down while the destroy callback (and thus the downstream RemotingServiceImpl.removeConnection chain) hadn't run yet. Now every caller blocks on the monitor until the destroy callback has completed.
Test — InVMConnectionTest.java
- Added testConcurrentCloseFiresConnectionDestroyedExactlyOnce: 16 threads aligned on a CyclicBarrier all call close()/disconnect() at once, over 50 rounds. The listener sleeps briefly inside connectionDestroyed to widen the race window. Each worker asserts, right after its call returns, that the destroy has already fired.
- Asserts both exactly-once firing and zero premature returns.
}

closing = true;
closing = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closing field can be removed completely.

@jbertram

jbertram commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Overall this looks good. However, the comments and the commit message are extremely wordy (something I've noticed with LLMs). Also, there is significant overlap between the code comments and the commit message. The commit message should give a clear, concise overview of the change, and details about particular bits of code should be in the source. There should be minimal overlap between the two.

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