Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: add DoNotFulfillResendRequest exception to allow applications to prevent resend #667

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion quickfixj-core/src/main/java/quickfix/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public interface Application {
* @throws RejectLogon causes a logon reject
*/
void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon;
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest;

/**
* This is a callback for application messages that you are sending to a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) quickfixengine.org All rights reserved.
*
* This file is part of the QuickFIX FIX Engine
*
* This file may be distributed under the terms of the quickfixengine.org
* license as defined by quickfixengine.org and appearing in the file
* LICENSE included in the packaging of this file.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
* THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE.
*
* See http://www.quickfixengine.org/LICENSE for licensing information.
*
* Contact [email protected] if any conditions of this licensing
* are not clear to you.
******************************************************************************/

package quickfix;

/**
* Applications can throw this exception to abort the fulfillment of an
* incoming ResendRequest.
*
* Example use case: the counterparty sends an excessive number of
* ResendRequests in error, and we want to prevent quickfix from fulfilling
* them, spiking cpu and disk I/O, overwhelming the server. Further action,
* such as sending Rejects to alert the counterparty to the problem, are left
* up to Applications.
*/
public class DoNotFulfillResendRequest extends Exception {
private final static String defaultMsg = "Fulfillment of ResendRequest aborted by Application";

public DoNotFulfillResendRequest() {
super(defaultMsg);
}

public DoNotFulfillResendRequest(String msg) {
super(defaultMsg + ": " + msg);
}
}
14 changes: 12 additions & 2 deletions quickfixj-core/src/main/java/quickfix/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -1856,7 +1856,17 @@ private boolean verify(Message msg, boolean checkTooHigh, boolean checkTooLow)
return false;
}

fromCallback(msgType, msg, sessionID);
try {
fromCallback(msgType, msg, sessionID);
} catch (final DoNotFulfillResendRequest e) {
getLog().onErrorEvent(e.getClass().getName() + ": " + e.getMessage());
// Only increment seqnum if we are at the expected seqnum
if (getExpectedTargetNum() == msg.getHeader().getInt(MsgSeqNum.FIELD)) {
state.incrNextTargetMsgSeqNum();
}
return false;
}

return true;
}

Expand Down Expand Up @@ -1904,7 +1914,7 @@ private boolean isGoodTime(Message message) throws FieldNotFound {

private void fromCallback(String msgType, Message msg, SessionID sessionID2)
throws RejectLogon, FieldNotFound, IncorrectDataFormat, IncorrectTagValue,
UnsupportedMessageType {
UnsupportedMessageType, DoNotFulfillResendRequest {
// Application exceptions will prevent the incoming sequence number from being incremented
// and may result in resend requests and the next startup. This way, a buggy application
// can be fixed and then reprocess previously sent messages.
Expand Down
55 changes: 48 additions & 7 deletions quickfixj-core/src/test/java/quickfix/SessionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ public void testRejectLogon() throws Exception {
@Override
public void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon {
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
throw new RejectLogon("FOR TEST");
}
Expand Down Expand Up @@ -1176,7 +1176,7 @@ public void testRejectLogonWithSessionStatus() throws Exception {
@Override
public void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon {
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
throw new RejectLogon("FOR TEST", SessionStatus.SESSION_ACTIVE);
}
Expand All @@ -1192,7 +1192,7 @@ public void fromAdmin(Message message, SessionID sessionId)
@Override
public void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon {
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
throw new RejectLogon("FOR TEST", -1);
}
Expand Down Expand Up @@ -1266,7 +1266,7 @@ public void fromApp(Message message, SessionID sessionId)
@Override
public void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon {
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
if (message.getHeader().getString(MsgType.FIELD)
.equals(MsgType.LOGON)) {
Expand Down Expand Up @@ -1757,7 +1757,7 @@ public void testCatchErrorsFromCallbackAndSendReject() throws Exception {
@Override
public void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, RejectLogon {
IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
final String msgType = message.getHeader().getString(
MsgType.FIELD);
Expand Down Expand Up @@ -2460,7 +2460,7 @@ public void testResendSeqWithReject() throws Exception {

@Override
public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
if (message.getHeader().getString(MsgType.FIELD).equals(Logon.MSGTYPE)) {
logonCount += 1;
Expand Down Expand Up @@ -2772,7 +2772,7 @@ public void msgseqnum_getting_reset_in_rejected_logon_scenario_fix() throws Exce
int logonCount = 0;
@Override
public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
if (message.getHeader().getString(MsgType.FIELD).equals(Logon.MSGTYPE)) {
logonCount += 1;
Expand Down Expand Up @@ -3145,4 +3145,45 @@ public void testSend_ShouldKeepPossDupFlagAndOrigSendingTime_GivenAllowPosDupCon
assertTrue(sentMessage.getHeader().isSetField(PossDupFlag.FIELD));
assertTrue(sentMessage.getHeader().isSetField(OrigSendingTime.FIELD));
}

@Test
public void testDoNotFulfillResendRequest() throws Exception {
// Given an application that does not fulfill ResendRequests
final UnitTestApplication application = new UnitTestApplication() {
@Override
public void fromAdmin(quickfix.Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
if ("2".contentEquals(message.getHeader().getString(35))) {
throw new DoNotFulfillResendRequest("No resend for you!");
}
}
};

// And the session is logged on
final SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "SENDER", "TARGET");
Session session = SessionFactoryTestSupport.createSession(sessionID, application, false, false, true, true, null);
UnitTestResponder responder = new UnitTestResponder();
session.setResponder(responder);
final SessionState state = getSessionState(session);
logonTo(session, 1);
assertEquals(2, state.getNextTargetMsgSeqNum());
assertEquals(true, session.isLoggedOn());

// And the following app-level messages have been sent
session.send(createAppMessage(2));
session.send(createAppMessage(3));
session.send(createAppMessage(4));
assertEquals(3, application.toAppMessages.size());

// When the counterparty requests resend
Message incomingResendRequest = createResendRequest(2, 1);
incomingResendRequest.toString(); // calculate length/checksum
processMessage(session, incomingResendRequest);

// Then no messages should be resent
assertEquals(3, application.toAppMessages.size());

// And the session should remain logged on
assertEquals(true, session.isLoggedOn());
session.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void toApp(Message message, SessionID sessionId) throws DoNotSend {

@Override
public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
log.info("from admin [{}] {}", sessionId, message);
fromAdminMessages.add(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import quickfix.MemoryStoreFactory;
import quickfix.Message;
import quickfix.RejectLogon;
import quickfix.DoNotFulfillResendRequest;
import quickfix.Responder;
import quickfix.SLF4JLogFactory;
import quickfix.Session;
Expand Down Expand Up @@ -116,7 +117,7 @@ public void testEventHandling() throws Exception {
final UnitTestApplication application = new UnitTestApplication() {
@Override
public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
latch.countDown();
}
Expand Down Expand Up @@ -185,7 +186,7 @@ public void testEventHandlingOnDisconnect() throws Exception {
final UnitTestApplication application = new UnitTestApplication() {
@Override
public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
IncorrectDataFormat, IncorrectTagValue, RejectLogon, DoNotFulfillResendRequest {
super.fromAdmin(message, sessionId);
latch.countDown();
}
Expand Down