Index: core/src/test/java/quickfix/DefaultSessionFactoryTest.java =================================================================== --- core/src/test/java/quickfix/DefaultSessionFactoryTest.java (revision 739) +++ core/src/test/java/quickfix/DefaultSessionFactoryTest.java (working copy) @@ -105,6 +105,12 @@ "Session FIX.4.2:FOO->BAR: could not parse time 'yy'."); } + public void testTestRequestDeayMultiplier() throws Exception { + settings.setString(sessionID, Session.SETTING_TEST_REQUEST_DELAY_MULTIPLIER, "0.37"); + Session session = factory.create(sessionID, settings); + assertEquals(0.37, session.getTestRequestDelayMultiplier()); + } + private void createSessionAndAssertConfigError(String message, String pattern) { try { factory.create(sessionID, settings); Index: core/src/test/java/quickfix/SessionStateTest.java =================================================================== --- core/src/test/java/quickfix/SessionStateTest.java (revision 739) +++ core/src/test/java/quickfix/SessionStateTest.java (working copy) @@ -34,7 +34,7 @@ } public void testTimeoutDefaultsAreNonzero() throws Exception { - SessionState state = new SessionState(new Object(), null, 0, false, null); + SessionState state = new SessionState(new Object(), null, 0, false, null, Session.DEFAULT_TEST_REQUEST_DELAY_MULTIPLIER); state.setLastReceivedTime(900); assertFalse("logon timeout not init'ed", state.isLogonTimedOut()); @@ -42,4 +42,19 @@ state.setLastSentTime(900); assertFalse("logout timeout not init'ed", state.isLogoutTimedOut()); } + + public void testTestRequestTiming() throws Exception { + SessionState state = new SessionState(new Object(), null, 0, false, null, Session.DEFAULT_TEST_REQUEST_DELAY_MULTIPLIER); + state.setLastReceivedTime(950); + state.setHeartBeatInterval(50); + assertFalse("testRequest shouldn't be needed yet", state.isTestRequestNeeded()); + for(int i=0;i<5;i++) { + state.incrementTestRequestCounter(); + } + assertFalse("testRequest should be needed", state.isTestRequestNeeded()); + + // set the heartbeat interval to something small and we shouldn't need it again + state.setHeartBeatInterval(3); + assertFalse("testRequest shouldn't be needed yet", state.isTestRequestNeeded()); + } } Index: core/src/main/java/quickfix/Session.java =================================================================== --- core/src/main/java/quickfix/Session.java (revision 739) +++ core/src/main/java/quickfix/Session.java (working copy) @@ -83,6 +83,10 @@ public static final String SETTING_MAX_LATENCY = "MaxLatency"; /** + * Session setting for the test delay multiplier (0-1, as fraction of Heartbeat interval) + */ + public static final String SETTING_TEST_REQUEST_DELAY_MULTIPLIER = "TestRequestDelayMultiplier"; + /** * Session scheduling setting to specify first day of trading week. */ public static final String SETTING_START_DAY = "StartDay"; @@ -244,23 +248,25 @@ private final ListenerSupport stateListeners = new ListenerSupport(SessionStateListener.class); private final SessionStateListener stateListener = (SessionStateListener) stateListeners.getMulticaster(); - + public static final int DEFAULT_MAX_LATENCY = 120; + public static final double DEFAULT_TEST_REQUEST_DELAY_MULTIPLIER = 0.5; + Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID, DataDictionary dataDictionary, SessionSchedule sessionSchedule, LogFactory logFactory, MessageFactory messageFactory, int heartbeatInterval) { this(application, messageStoreFactory, sessionID, dataDictionary, sessionSchedule, - logFactory, messageFactory, heartbeatInterval, true, 120, true, false, false, - false, false, false, true, false, true, false, false); + logFactory, messageFactory, heartbeatInterval, true, DEFAULT_MAX_LATENCY, true, false, false, + false, false, true, false, true, false, DEFAULT_TEST_REQUEST_DELAY_MULTIPLIER); } Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID, DataDictionary dataDictionary, SessionSchedule sessionSchedule, LogFactory logFactory, MessageFactory messageFactory, int heartbeatInterval, boolean checkLatency, int maxLatency, boolean millisecondsInTimeStamp, boolean resetOnLogon, - boolean resetOnLogout, boolean resetOnDisconnect, boolean resetWhenInitiatingLogon, + boolean resetOnLogout, boolean resetOnDisconnect, boolean refreshMessageStoreAtLogon, boolean checkCompID, - boolean redundantResentRequestsAllowed, boolean persistMessages, boolean refreshOnLogon, - boolean useClosedRangeForResend) { + boolean redundantResentRequestsAllowed, boolean persistMessages, + boolean useClosedRangeForResend, double testRequestDelayMultiplier) { this.application = application; this.sessionID = sessionID; this.sessionSchedule = sessionSchedule; @@ -280,7 +286,7 @@ this.state = new SessionState(this, logFactory != null ? logFactory.create(sessionID) : null, heartbeatInterval, heartbeatInterval != 0, messageStoreFactory - .create(sessionID)); + .create(sessionID), testRequestDelayMultiplier); registerSession(this); @@ -383,8 +389,6 @@ String senderCompID = message.getHeader().getString(SenderCompID.FIELD); String targetCompID = message.getHeader().getString(TargetCompID.FIELD); return sendToTarget(message, senderCompID, targetCompID, qualifier); - } catch (SessionNotFound e) { - throw e; } catch (FieldNotFound e) { throw new SessionNotFound("missing sender or target company ID"); } @@ -456,8 +460,8 @@ static void unregisterSessions(List sessionIds) { synchronized (sessions) { - for (int i = 0; i < sessionIds.size(); i++) { - sessions.remove((SessionID) sessionIds.get(i)); + for (SessionID sessionId : sessionIds) { + sessions.remove((SessionID) sessionId); } } } @@ -813,8 +817,8 @@ int begin = 0; int current = beginSeqNo; - for (int i = 0; i < messages.size(); i++) { - Message msg = parseMessage((String) messages.get(i)); + for (String message : messages) { + Message msg = parseMessage((String) message); msgSeqNum = msg.getHeader().getInt(MsgSeqNum.FIELD); String msgType = msg.getHeader().getString(MsgType.FIELD); @@ -1860,6 +1864,10 @@ return state.getCreationTime(); } + public double getTestRequestDelayMultiplier() { + return state.getTestRequestDelayMultiplier(); + } + public String toString() { String s = sessionID.toString(); try { Index: core/src/main/java/quickfix/DefaultSessionFactory.java =================================================================== --- core/src/main/java/quickfix/DefaultSessionFactory.java (revision 739) +++ core/src/main/java/quickfix/DefaultSessionFactory.java (working copy) @@ -81,14 +81,14 @@ DataDictionary dataDictionary = null; if (useDataDictionary) { - String path = null; + String path; if (settings.isSetting(sessionID, Session.SETTING_DATA_DICTIONARY)) { path = settings.getString(sessionID, Session.SETTING_DATA_DICTIONARY); } else { path = settings.getString(sessionID, "BeginString").replaceAll("\\.", "") + ".xml"; } - dataDictionary = (DataDictionary) dictionaryCache.get(path); + dataDictionary = dictionaryCache.get(path); if (dataDictionary == null) { dataDictionary = new DataDictionary(path); dictionaryCache.put(path, dataDictionary); @@ -120,7 +120,11 @@ boolean checkLatency = getSetting(settings, sessionID, Session.SETTING_CHECK_LATENCY, true); - int maxLatency = getSetting(settings, sessionID, Session.SETTING_MAX_LATENCY, 120); + int maxLatency = getSetting(settings, sessionID, Session.SETTING_MAX_LATENCY, + Session.DEFAULT_MAX_LATENCY); + double testRequestDelayMultiplier = getSetting(settings, sessionID, + Session.SETTING_TEST_REQUEST_DELAY_MULTIPLIER, + Session.DEFAULT_TEST_REQUEST_DELAY_MULTIPLIER); boolean millisInTimestamp = getSetting(settings, sessionID, Session.SETTING_MILLISECONDS_IN_TIMESTAMP, true); @@ -155,9 +159,9 @@ Session session = new Session(application, messageStoreFactory, sessionID, dataDictionary, new SessionSchedule(settings, sessionID), logFactory, messageFactory, heartbeatInterval, checkLatency, maxLatency, millisInTimestamp, - resetOnLogon, resetOnLogout, resetOnDisconnect, resetOnLogon, refreshAtLogon, - checkCompID, redundantResentRequestAllowed, persistMessages, refreshAtLogon, - useClosedIntervalForResend); + resetOnLogon, resetOnLogout, resetOnDisconnect, refreshAtLogon, + checkCompID, redundantResentRequestAllowed, persistMessages, + useClosedIntervalForResend, testRequestDelayMultiplier); session.setLogonTimeout(logonTimeout); session.setLogoutTimeout(logoutTimeout); @@ -171,8 +175,6 @@ application.onCreate(sessionID); return session; - } catch (ConfigError e) { - throw e; } catch (FieldConvertError e) { throw new ConfigError(e.getMessage()); } @@ -190,4 +192,11 @@ : defaultValue; } + private double getSetting(SessionSettings settings, SessionID sessionID, String key, + double defaultValue) throws ConfigError, FieldConvertError { + return settings.isSetting(sessionID, key) + ? Double.parseDouble(settings.getString(sessionID, key)) + : defaultValue; + } + } Index: core/src/main/java/quickfix/SessionState.java =================================================================== --- core/src/main/java/quickfix/SessionState.java (revision 739) +++ core/src/main/java/quickfix/SessionState.java (working copy) @@ -55,6 +55,7 @@ private long lastSentTime; private long lastReceivedTime; private boolean withinHeartBeat; + private double testRequestDelayMultiplier; private long heartBeatMillis = Long.MAX_VALUE; private int heartBeatInterval; private HashMap messageQueue = new HashMap(); @@ -64,12 +65,13 @@ private String logoutReason; public SessionState(Object lock, Log log, int heartBeatInterval, boolean initiator, - MessageStore messageStore) { + MessageStore messageStore, double testRequestDelayMultiplier) { this.lock = lock; this.initiator = initiator; this.messageStore = messageStore; setHeartBeatInterval(heartBeatInterval); this.log = log == null ? new NullLog() : log; + this.testRequestDelayMultiplier = testRequestDelayMultiplier; } public int getHeartBeatInterval() { @@ -252,6 +254,10 @@ } } + public double getTestRequestDelayMultiplier() { + return testRequestDelayMultiplier; + } + public void clearTestRequestCounter() { synchronized (lock) { testRequestCounter = 0; @@ -266,7 +272,7 @@ public boolean isTestRequestNeeded() { long millisSinceLastReceivedTime = timeSinceLastReceivedMessage(); - return millisSinceLastReceivedTime >= (1.5 * (getTestRequestCounter() + 1)) + return millisSinceLastReceivedTime >= ((1 + testRequestDelayMultiplier) * (getTestRequestCounter() + 1)) * getHeartBeatMillis(); } Index: core/src/main/doc/usermanual/usage/configuration.html =================================================================== --- core/src/main/doc/usermanual/usage/configuration.html (revision 739) +++ core/src/main/doc/usermanual/usage/configuration.html (working copy) @@ -737,6 +737,13 @@ N + TestRequestDelayMultiplier + Used to caclulate the delay threshold from the last received message before sending out a Test Request message. + Think of the multiplier as the percentage of Heartbeat interval you want to wait, from 0 to 1. Default is 0.5. + double from 0 to 1 + 0.5 + + ContinueInitializationOnError Continue initializing sessions if an error occurs. Y or N