diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index edce5adf9..19bede7b8 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -2402,7 +2402,12 @@ private void resendMessages(Message receivedMessage, int beginSeqNo, int endSeqN generateSequenceReset(receivedMessage, begin, msgSeqNum); } getLog().onEvent("Resending message: " + msgSeqNum); - send(msg.toString()); + boolean sent = send(msg.toString()); + if (!sent) { + // Abort resend operation immediately - don't send any more messages + getLog().onWarnEvent("Resending messages aborted."); + return; + } begin = 0; appMessageJustSent = true; } else { diff --git a/quickfixj-core/src/test/java/quickfix/SessionTest.java b/quickfixj-core/src/test/java/quickfix/SessionTest.java index 1e5483635..aa393eb06 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionTest.java @@ -53,6 +53,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -3095,6 +3096,35 @@ public void disconnect() { } } + private class FailingResponder implements Responder { + public int sendCallCount = 0; + public int maxSuccessfulSends; + public List sentMessages = new ArrayList<>(); + + public FailingResponder(int maxSuccessfulSends) { + this.maxSuccessfulSends = maxSuccessfulSends; + } + + @Override + public boolean send(String data) { + sendCallCount++; + if (sendCallCount <= maxSuccessfulSends) { + sentMessages.add(data); + return true; + } + return false; + } + + @Override + public String getRemoteAddress() { + return null; + } + + @Override + public void disconnect() { + } + } + @Test public void testSendWithAllowPosDupAsFalse_ShouldRemovePossDupFlagAndOrigSendingTime() throws Exception { final UnitTestApplication application = new UnitTestApplication(); @@ -3161,4 +3191,52 @@ public void testSend_ShouldKeepPossDupFlagAndOrigSendingTime_GivenAllowPosDupCon assertTrue(sentMessage.getHeader().isSetField(PossDupFlag.FIELD)); assertTrue(sentMessage.getHeader().isSetField(OrigSendingTime.FIELD)); } + + /** + * https://github.com/quickfix-j/quickfixj/issues/646 + * Verify that resend operations abort when send() returns false. + * When a responder disconnects mid-resend, the resend operation should stop + * immediately rather than attempting to send all remaining messages. + */ + @Test + public void testResendAbortsWhenSendReturnsFalse() throws Exception { + final UnitTestApplication application = new UnitTestApplication(); + final SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "SENDER", "TARGET"); + try (Session session = SessionFactoryTestSupport.createSession(sessionID, application, false, false, true, true, null)) { + // Create a responder that will succeed for first 2 sends, then fail + FailingResponder responder = new FailingResponder(2); + session.setResponder(responder); + final SessionState state = getSessionState(session); + + // Logon + final Logon logon = new Logon(); + setUpHeader(session.getSessionID(), logon, true, 1); + logon.setInt(HeartBtInt.FIELD, 30); + logon.setInt(EncryptMethod.FIELD, EncryptMethod.NONE_OTHER); + logon.toString(); // calculate length/checksum + session.next(logon); + + // Send 5 application messages + session.send(createAppMessage(2)); + session.send(createAppMessage(3)); + session.send(createAppMessage(4)); + session.send(createAppMessage(5)); + session.send(createAppMessage(6)); + + // Reset the responder to simulate disconnect and reconnect + responder = new FailingResponder(2); + session.setResponder(responder); + + // Request resend of messages 2-6 + Message resendRequest = createResendRequest(2, 2); + resendRequest.toString(); // calculate length/checksum + processMessage(session, resendRequest); + + // With the fix, only the first 2 messages should be sent successfully before aborting + // The 3rd send attempt will fail and cause the abort + // Without the fix, all 5 messages would be attempted (but 3+ would fail) + assertEquals("Should attempt 3 sends (2 succeed, 1 fails and aborts)", 3, responder.sendCallCount); + assertEquals("Only 2 messages should succeed", 2, responder.sentMessages.size()); + } + } }