Fix reconnection hang on HTTP proxy connect failures and stalled handshakes#1275
Open
rishabhagl wants to merge 3 commits into
Open
Fix reconnection hang on HTTP proxy connect failures and stalled handshakes#1275rishabhagl wants to merge 3 commits into
rishabhagl wants to merge 3 commits into
Conversation
…dshakes - Handle exceptional ConnectFuture completion in pollConnectFuture() instead of treating it as still-pending, which previously left connectFuture non-null forever and blocked re-connection. - Add a connectTimeoutMillis watchdog to cancel and reset pending connects that never resolve (no session, no exception) — common with stalled HTTP CONNECT proxy handshakes. - Recreate the ProxyConnector via setupIoConnector() after cancelling a stuck pending attempt, and reset lastConnectTime/lastReconnectAttemptTime to avoid an immediate re-fire on the next reconnect tick. - Add unit tests covering the changes.
chrjohn
reviewed
Jul 3, 2026
chrjohn
reviewed
Jul 3, 2026
`pollConnect()` is only called from methods which originate from `run()` which is called by a single thread from the executor, so `synchronized` could be removed.
chrjohn
reviewed
Jul 6, 2026
| } | ||
|
|
||
| try { | ||
| setupIoConnector(); |
Member
There was a problem hiding this comment.
Why is this only done on a ProxyConnector? I assume because it has some internal state that needs to be handled differently? Maybe add some explanation?
Edit: I'm still thinking about if this could conflict with the dispose-logic.
Author
There was a problem hiding this comment.
I got this exception when reused IoConnector for proxy.
java.lang.IllegalStateException: handler cannot be set while the service is active.
at org.apache.mina.core.service.AbstractIoService.setHandler(...)
at org.apache.mina.proxy.ProxyConnector.connect0(...)
This exception does not occure when reusing the IoConnector in non-proxy connection timeout, where the server does not reply TCP SYN.
by explanation, you mean adding code comment?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1274 — reconnection permanently stalls when connecting through an HTTP proxy if the proxy connect fails with an exception, or if the CONNECT handshake stalls without resolving the ConnectFuture at all.
Problem
IoSessionInitiator.pollConnectFuture()only checkedfuture.getSession() != null. If the future instead completed exceptionally (e.g. proxy returns 407, or the CONNECT is rejected), the code fell through into the "still pending" branch, which just logged a message and never clearedconnectFutureor calledhandleConnectException(). SinceshouldReconnect()requiresconnectFuture == null, the initiator got stuck and never retried.Separately, a stalled proxy handshake (no session, no exception — MINA's proxy filter wedged mid-negotiation) had no timeout at all and could hang indefinitely.
Fix
pollConnectFuture()now explicitly checksfuture.getException()and routes it throughhandleConnectException(), clearingconnectFutureimmediately.connectTimeoutMillis-based watchdog: if a connect attempt has been pending longer than this,cancelAndResetPendingConnectAttempt()is invoked to:IoSessionand cancel the future,ioConnector instanceof ProxyConnector, cancel the proxy connector's internal connect future and recreate the connector viasetupIoConnector(),lastConnectTime/lastReconnectAttemptTimeto the current time so the next scheduled reconnect doesn't fire immediately (previouslypendingMilliswould already exceedReconnectInterval, causing a premature/rapid retry loop) or never (if left unset).Testing
Added
IoSessionInitiatorConnectTaskTest, which targets the privateIoSessionInitiator.ConnectTaskpolling state machine via reflection (the fix lives inside this private class, so tests constructConnectTaskdirectly and invokepollConnectFuture(),cancelAndResetPendingConnectAttempt(), andhandleConnectException()reflectively).Coverage includes:
connectTimeoutMillis— future is cancelled, timeout events are logged,onConnectExceptionfires,connectFutureclears, failure count increments once, and both timers reset soshouldReconnect()correctly waits out the fullReconnectInterval.lastConnectTimeupdated.TestProxyConnectorstub) — confirms the pending future is cancelled,cancelConnectFuture()is called, and the connector instance is recreated (not reused).cancelAndResetPendingConnectAttempt— closes a half-open session before cancelling the future and separately confirms proxy-connector cancellation/recreation in isolation.handleConnectException— unwraps nested causes, increments failure count, clears the future, and notifies the state listener.All existing tests in
quickfixj-corepass locally with this change.Related