From 6e0c3b722d9cf5655ca9ea263729ed25720450a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:07:19 +0000 Subject: [PATCH 1/6] Initial plan From 4d5ec1fe2993ae99f3ec0444a7693b527cffeca8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:35:36 +0000 Subject: [PATCH 2/6] Implement resend abort when send() returns false with test Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- .../src/main/java/quickfix/Session.java | 7 +- .../src/test/java/quickfix/SessionTest.java | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index edce5adf97..0dc69823df 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) { + LOG.warn("Resend aborted: send() returned false for message " + msgSeqNum); + // Abort resend operation immediately - don't send any more messages + 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 1e5483635b..1b855c4af0 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,47 @@ public void testSend_ShouldKeepPossDupFlagAndOrigSendingTime_GivenAllowPosDupCon assertTrue(sentMessage.getHeader().isSetField(PossDupFlag.FIELD)); assertTrue(sentMessage.getHeader().isSetField(OrigSendingTime.FIELD)); } + + @Test + public void testResendAbortsWhenSendReturnsFalse() throws Exception { + // QFJ-646: Stop sending resend-messages when the responder has gone away + 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()); + } + } } From 17a4eaace0b1742c841ee058c0e4957a30ca9db8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:11:42 +0000 Subject: [PATCH 3/6] Use session log instead of static LOG for resend abort warning Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/main/java/quickfix/Session.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index 0dc69823df..7accd8d9ca 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -2404,7 +2404,7 @@ private void resendMessages(Message receivedMessage, int beginSeqNo, int endSeqN getLog().onEvent("Resending message: " + msgSeqNum); boolean sent = send(msg.toString()); if (!sent) { - LOG.warn("Resend aborted: send() returned false for message " + msgSeqNum); + getLog().onWarnEvent("Resend aborted: send() returned false for message " + msgSeqNum); // Abort resend operation immediately - don't send any more messages return; } From 6c07eae262211a62219d2184f7d519a3eb102b80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:06:39 +0000 Subject: [PATCH 4/6] Add JavaDoc comment for QFJ-646 test and remove inline comment Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/test/java/quickfix/SessionTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/quickfixj-core/src/test/java/quickfix/SessionTest.java b/quickfixj-core/src/test/java/quickfix/SessionTest.java index 1b855c4af0..435870da97 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionTest.java @@ -3192,9 +3192,13 @@ public void testSend_ShouldKeepPossDupFlagAndOrigSendingTime_GivenAllowPosDupCon assertTrue(sentMessage.getHeader().isSetField(OrigSendingTime.FIELD)); } + /** + * QFJ-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 { - // QFJ-646: Stop sending resend-messages when the responder has gone away 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)) { From 44982fb9b08fe957b62472efa3db95d9b0e1c2e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:42:57 +0000 Subject: [PATCH 5/6] Fix issue reference format to use GitHub issue URL instead of QFJ-646 Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/test/java/quickfix/SessionTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickfixj-core/src/test/java/quickfix/SessionTest.java b/quickfixj-core/src/test/java/quickfix/SessionTest.java index 435870da97..aa393eb06c 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionTest.java @@ -3193,7 +3193,8 @@ public void testSend_ShouldKeepPossDupFlagAndOrigSendingTime_GivenAllowPosDupCon } /** - * QFJ-646: Verify that resend operations abort when send() returns false. + * 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. */ From 31bcb68feb6bb7a673493784b6409a5fba0fa56f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:53:51 +0000 Subject: [PATCH 6/6] Simplify resend abort warning message Co-authored-by: chrjohn <6644028+chrjohn@users.noreply.github.com> --- quickfixj-core/src/main/java/quickfix/Session.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index 7accd8d9ca..19bede7b8b 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -2404,8 +2404,8 @@ private void resendMessages(Message receivedMessage, int beginSeqNo, int endSeqN getLog().onEvent("Resending message: " + msgSeqNum); boolean sent = send(msg.toString()); if (!sent) { - getLog().onWarnEvent("Resend aborted: send() returned false for message " + msgSeqNum); // Abort resend operation immediately - don't send any more messages + getLog().onWarnEvent("Resending messages aborted."); return; } begin = 0;