From 9298f975e8039968f8675e30337fd5d0d6e13fcd Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Sun, 16 Nov 2014 15:47:48 +0000 Subject: [PATCH 01/20] Add RefreshOnLogon functionality. Add the missing calls to refresh the MessageStore if RefreshOnLogon config option has been specfiied. --- QuickFIXn/Session.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 470be3acd..3ab1041d2 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -673,6 +673,8 @@ protected void NextLogon(Message logon) if (!state_.IsInitiator && this.ResetOnLogon) state_.Reset("ResetOnLogon"); + if (this.RefreshOnLogon) + Refresh(); if (!Verify(logon, false, true)) return; @@ -981,9 +983,9 @@ public void SetResponder(IResponder responder) } } - /// FIXME public void Refresh() { + state_.Refresh(); } [Obsolete("Use Reset(reason) instead.")] From 4bc599bde671253f52348315967f316b44a7eb18 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Mon, 17 Nov 2014 14:41:02 +0000 Subject: [PATCH 02/20] Add new Application Callback (non-breaking) for early intercept of inbound messages. Add an IApplicationExt interface that extends IApplication and facilitates early intercept of inbound messages on the part of the Application. This is a bit similar to the late outbound intercept afforded by ToApp/ToAdmin, and permits alteration of field values prior to their being validated by the engine. The difference is that, with the new interface, only one callback is provided for both App and Admin messages. Usage of the existing IApplication interface is unaffected. --- QuickFIXn/IApplicationExt.cs | 20 ++++++++++ QuickFIXn/QuickFix.csproj | 3 +- QuickFIXn/Session.cs | 5 +++ UnitTests/SessionTest.cs | 74 +++++++++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 QuickFIXn/IApplicationExt.cs diff --git a/QuickFIXn/IApplicationExt.cs b/QuickFIXn/IApplicationExt.cs new file mode 100644 index 000000000..35d34d484 --- /dev/null +++ b/QuickFIXn/IApplicationExt.cs @@ -0,0 +1,20 @@ +using System; + +namespace QuickFix +{ + /// + /// This is the optional extension interface for processing session messages, and facilitates early interception of inbound messages. + /// 'Early', in this context, means after structure, length and checksum have been validated, but before any further validation has been performed. + /// + public interface IApplicationExt : IApplication + { + /// + /// This callback provides early notification of when an administrative or application message is sent from a counterparty to your FIX engine. + /// This can be useful for doing pre-processing of an inbound message after its structure, checksum and length have been validated, but before + /// any further validation has been performed on it. + /// + /// received message + /// session on which message received + void FromEarlyIntercept(Message message, SessionID sessionID); + } +} diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index c0ef8831b..90f45af6a 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -67,6 +67,7 @@ + @@ -141,4 +142,4 @@ --> - + \ No newline at end of file diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 470be3acd..a5295c1e1 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -22,6 +22,7 @@ public class Session : IDisposable private SessionSchedule schedule_; private SessionState state_; private IMessageFactory msgFactory_; + private bool appDoesEarlyIntercept_; private static readonly HashSet AdminMsgTypes = new HashSet() { "0", "A", "1", "2", "3", "4", "5" }; #endregion @@ -211,6 +212,7 @@ public Session( this.DataDictionaryProvider = new DataDictionaryProvider(dataDictProvider); this.schedule_ = sessionSchedule; this.msgFactory_ = msgFactory; + appDoesEarlyIntercept_ = app is IApplicationExt; this.SenderDefaultApplVerID = senderDefaultApplVerID; @@ -547,6 +549,9 @@ public void Next(Message message) return; } + if (appDoesEarlyIntercept_) + ((IApplicationExt)Application).FromEarlyIntercept(message, this.SessionID); + if (IsNewSession) state_.Reset("New session (detected in Next(Message))"); diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index 328b684cf..b88cc143d 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -112,8 +112,49 @@ public void OnLogout(QuickFix.SessionID sessionID) public void OnLogon(QuickFix.SessionID sessionID) { } + #endregion + } + + class MockApplicationExt : QuickFix.IApplicationExt + { + public HashSet InterceptedMessageTypes = new HashSet(); + + #region Application Members + + public void ToAdmin(QuickFix.Message message, QuickFix.SessionID sessionID) + { + } + + public void FromAdmin(QuickFix.Message message, QuickFix.SessionID sessionID) + { + } + + public void ToApp(QuickFix.Message message, QuickFix.SessionID sessionId) + { + } + + public void FromApp(QuickFix.Message message, QuickFix.SessionID sessionID) + { + } + + public void OnCreate(QuickFix.SessionID sessionID) + { + } + + public void OnLogout(QuickFix.SessionID sessionID) + { + } + public void OnLogon(QuickFix.SessionID sessionID) + { + } + + public void FromEarlyIntercept(QuickFix.Message message, QuickFix.SessionID sessionID) + { + InterceptedMessageTypes.Add(message.Header.GetString(QuickFix.Fields.Tags.MsgType)); + } #endregion + } [TestFixture] @@ -126,6 +167,7 @@ public class SessionTest MockApplication application = null; QuickFix.Session session = null; QuickFix.Session session2 = null; + QuickFix.Dictionary config = null; int seqNum = 1; Regex msRegex = new Regex(@"\.[\d]{1,3}$"); @@ -137,7 +179,7 @@ public void setup() application = new MockApplication(); settings = new QuickFix.SessionSettings(); - QuickFix.Dictionary config = new QuickFix.Dictionary(); + config = new QuickFix.Dictionary(); config.SetBool(QuickFix.SessionSettings.PERSIST_MESSAGES, false); config.SetString(QuickFix.SessionSettings.CONNECTION_TYPE, "initiator"); config.SetString(QuickFix.SessionSettings.START_TIME, "00:00:00"); @@ -719,5 +761,35 @@ public void TestToAppResendDoNotSend() SendResendRequest(1, 0); Assert.False(SENT_NOS()); } + + + [Test] + public void TestApplicationExtension() + { + var mockApp = new MockApplicationExt(); + session = new QuickFix.Session(mockApp, new QuickFix.MemoryStoreFactory(), sessionID, + new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, new QuickFix.ScreenLogFactory(settings), new QuickFix.DefaultMessageFactory(), "blah"); + session.SetResponder(responder); + session.CheckLatency = false; + + Logon(); + QuickFix.FIX42.NewOrderSingle order = new QuickFix.FIX42.NewOrderSingle( + new QuickFix.Fields.ClOrdID("1"), + new QuickFix.Fields.HandlInst(QuickFix.Fields.HandlInst.MANUAL_ORDER), + new QuickFix.Fields.Symbol("IBM"), + new QuickFix.Fields.Side(QuickFix.Fields.Side.BUY), + new QuickFix.Fields.TransactTime(), + new QuickFix.Fields.OrdType(QuickFix.Fields.OrdType.LIMIT)); + + order.Header.SetField(new QuickFix.Fields.TargetCompID(sessionID.SenderCompID)); + order.Header.SetField(new QuickFix.Fields.SenderCompID(sessionID.TargetCompID)); + order.Header.SetField(new QuickFix.Fields.MsgSeqNum(2)); + + session.Next(order); + + Assert.That(mockApp.InterceptedMessageTypes.Count, Is.EqualTo(2)); + Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.LOGON)); + Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.NEWORDERSINGLE)); + } } } From 4ed14c5d40e75a671336752fa72b5ab157ba1f96 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 2 Dec 2014 20:12:37 +0000 Subject: [PATCH 03/20] Dynamic session configuration. Allow new sessions to be added and existing sessions to be deleted while the engine is running. --- QuickFIXn/AbstractInitiator.cs | 81 +++++++++++++++++++++----- QuickFIXn/IAcceptor.cs | 16 +++++ QuickFIXn/IInitiator.cs | 16 +++++ QuickFIXn/Session.cs | 5 ++ QuickFIXn/ThreadedSocketAcceptor.cs | 69 ++++++++++++++++++---- QuickFIXn/Transport/SocketInitiator.cs | 9 +++ 6 files changed, 172 insertions(+), 24 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index d4fcf1b72..56be64d3a 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -12,6 +12,7 @@ public abstract class AbstractInitiator : IInitiator private ILogFactory _logFactory = null; private IMessageFactory _msgFactory = null; + private object sync_ = new object(); private bool _disposed = false; private Dictionary sessions_ = new Dictionary(); @@ -21,6 +22,7 @@ public abstract class AbstractInitiator : IInitiator private HashSet disconnected_ = new HashSet(); private bool isStopped_ = true; private Thread thread_; + private SessionFactory sessionFactory_ = null; #region Properties @@ -59,18 +61,11 @@ public void Start() throw new System.ObjectDisposedException(this.GetType().Name); // create all sessions - SessionFactory factory = new SessionFactory(_app, _storeFactory, _logFactory, _msgFactory); + sessionFactory_ = new SessionFactory(_app, _storeFactory, _logFactory, _msgFactory); foreach (SessionID sessionID in _settings.GetSessions()) { Dictionary dict = _settings.Get(sessionID); - string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); - - if ("initiator".Equals(connectionType)) - { - sessionIDs_.Add(sessionID); - sessions_[sessionID] = factory.Create(sessionID, dict); - SetDisconnected(sessionID); - } + AddSession(sessionID, dict); } if (0 == sessions_.Count) @@ -83,6 +78,55 @@ public void Start() thread_.Start(); } + /// + /// Add new session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added succesfully, false if session already exists or is of wrong type + public bool AddSession(SessionID sessionID, Dictionary dict) + { + string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); + if (!sessionIDs_.Contains(sessionID) && "initiator" == connectionType) + { + sessionIDs_.Add(sessionID); + sessions_[sessionID] = sessionFactory_.Create(sessionID, dict); + SetDisconnected(sessionID); + return true; + } + return false; + } + + /// + /// Ad-hoc removal of an existing sssion + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) + { + if (sessionIDs_.Contains(sessionID)) + { + Session session = sessions_[sessionID]; + bool isDisconnected = false; + lock (sync_) + { + isDisconnected = IsDisconnected(sessionID); + if (!(isDisconnected || terminateActiveSession)) + return false; + sessionIDs_.Remove(sessionID); + sessions_.Remove(sessionID); + SetDisconnected(sessionID); + disconnected_.Remove(sessionID); + OnRemove(sessionID); + } + if (!isDisconnected) + session.Reset("Session reconfigured(AbstractInitiator.RemoveSession)", "Session disabled"); + session.Dispose(); + } + return true; + } + /// /// Logout existing session and close connection. Attempt graceful disconnect first. /// @@ -178,7 +222,15 @@ public bool IsLoggedOn protected virtual void OnConfigure(SessionSettings settings) { } - [System.Obsolete("This method's intended purpose is unclear. Don't use it.")] + + /// + /// Override this to handle ad-hoc session removal + /// + /// ID of session being remvoed + protected virtual void OnRemove(SessionID sessionID) + { } + + [System.Obsolete("This method's intended purpose is unclear. Don't use it.")] protected virtual void OnInitialize(SessionSettings settings) { } @@ -254,9 +306,12 @@ protected void SetDisconnected(SessionID sessionID) { lock (sync_) { - pending_.Remove(sessionID); - connected_.Remove(sessionID); - disconnected_.Add(sessionID); + if (sessionIDs_.Contains(sessionID)) + { + pending_.Remove(sessionID); + connected_.Remove(sessionID); + disconnected_.Add(sessionID); + } } } diff --git a/QuickFIXn/IAcceptor.cs b/QuickFIXn/IAcceptor.cs index 76fdab26b..030c2b281 100644 --- a/QuickFIXn/IAcceptor.cs +++ b/QuickFIXn/IAcceptor.cs @@ -41,6 +41,22 @@ public interface IAcceptor /// /// a map of SessionIDs to EndPoints Dictionary GetAcceptorAddresses(); + + /// + /// Add a new session after acceptor has been started + /// + /// ID of session to be added + /// session settings + /// >true if session added succesfully, false if session already exists or is of wrong type + bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); + + /// + /// Remove an existing session after acceptor has been started + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } /// diff --git a/QuickFIXn/IInitiator.cs b/QuickFIXn/IInitiator.cs index eb25a3b25..ff121a681 100644 --- a/QuickFIXn/IInitiator.cs +++ b/QuickFIXn/IInitiator.cs @@ -41,6 +41,22 @@ public interface IInitiator : IDisposable /// /// the SessionIDs for the sessions managed by this initiator HashSet GetSessionIDs(); + + /// + /// Add a new session after initiator has been started + /// + /// ID of session to be added + /// session settings + /// >true if session added succesfully, false if session already exists or is of wrong type + bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); + + /// + /// Remove an existing session after initiator has been started + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } /// diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 470be3acd..79bcffd8d 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -1624,6 +1624,11 @@ protected bool SendRaw(Message message, int seqNum) public void Dispose() { if (state_ != null) { state_.Dispose(); } + lock (sessions_) + { + sessions_.Remove(this.SessionID); + } + } } } diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index a2cc39043..b06249581 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -48,6 +48,16 @@ public void AcceptSession(Session session) acceptedSessions_[session.SessionID] = session; } + /// + /// Remove a session from those tied to this socket. + /// + /// ID of session to be removed + /// true if session removed, false if not found + public bool RemoveSession(SessionID sessionID) + { + return acceptedSessions_.Remove(sessionID); + } + public Dictionary GetAcceptedSessions() { return new Dictionary(acceptedSessions_); @@ -56,6 +66,7 @@ public Dictionary GetAcceptedSessions() private Dictionary sessions_ = new Dictionary(); private Dictionary socketDescriptorForAddress_ = new Dictionary(); + private SessionFactory sessionFactory_; private bool isStarted_ = false; private object sync_ = new object(); @@ -91,27 +102,19 @@ public ThreadedSocketAcceptor(SessionFactory sessionFactory, SessionSettings set private void CreateSessions(SessionSettings settings, SessionFactory sessionFactory) { + sessionFactory_ = sessionFactory; foreach (SessionID sessionID in settings.GetSessions()) { QuickFix.Dictionary dict = settings.Get(sessionID); - string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); - - if ("acceptor".Equals(connectionType)) - { - AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(settings, sessionID); - Session session = sessionFactory.Create(sessionID, dict); - descriptor.AcceptSession(session); - sessions_[sessionID] = session; - } + AddSession(sessionID, dict); } if (0 == socketDescriptorForAddress_.Count) throw new ConfigError("No acceptor sessions found in SessionSettings."); } - private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(SessionSettings settings, SessionID sessionID) + private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(Dictionary dict) { - QuickFix.Dictionary dict = settings.Get(sessionID); int port = System.Convert.ToInt32(dict.GetLong(SessionSettings.SOCKET_ACCEPT_PORT)); SocketSettings socketSettings = new SocketSettings(); @@ -142,6 +145,50 @@ private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(SessionSettings set return descriptor; } + /// + /// Add new session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added succesfully, false if session already exists or is of wrong type + public bool AddSession(SessionID sessionID, Dictionary dict) + { + if (!sessions_.ContainsKey(sessionID)) + { + string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); + if ("acceptor" == connectionType) + { + AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(dict); + Session session = sessionFactory_.Create(sessionID, dict); + descriptor.AcceptSession(session); + sessions_[sessionID] = session; + return true; + } + } + return false; + } + + /// + /// Ad-hoc removal of an existing sssion + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) + { + Session session = null; + if (sessions_.TryGetValue(sessionID, out session)) + { + foreach (AcceptorSocketDescriptor descriptor in socketDescriptorForAddress_.Values) + if (descriptor.RemoveSession(sessionID)) + break; + sessions_.Remove(sessionID); + session.Reset("Session reconfigured(ThreadeSocketAcceptor.RemoveSession)", "Session disabled"); + session.Dispose(); + } + return true; + } + private void StartAcceptingConnections() { lock (sync_) diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 1ed81e13e..669f6afc4 100755 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -166,6 +166,15 @@ protected override void OnStart() } } + /// + /// Ad-hoc session removal + /// + /// ID of session being remvoed + protected override void OnRemove(SessionID sessionID) + { + sessionToHostNum_.Remove(sessionID); + } + protected override bool OnPoll(double timeout) { throw new NotImplementedException("FIXME - SocketInitiator.OnPoll not implemented!"); From 42ddebe52efcb9e2965d4b47b20af92a946100a4 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Sun, 14 Dec 2014 16:26:22 +0000 Subject: [PATCH 04/20] Dynamic sessions Allows initiator and acceptor session config to be added to, and removed from, a running engine. In the case of removal, caller can specify whether a fully logged on connection belonging to the relevant session should be terminated, or whether the attempt to remove should fail under this condition. --- QuickFIXn/AbstractInitiator.cs | 134 +++++++++++++------------ QuickFIXn/Dictionary.cs | 41 +++++++- QuickFIXn/SessionSettings.cs | 15 +++ QuickFIXn/ThreadedSocketAcceptor.cs | 4 +- QuickFIXn/Transport/SocketInitiator.cs | 16 +-- 5 files changed, 136 insertions(+), 74 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 56be64d3a..604018e03 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -22,7 +22,7 @@ public abstract class AbstractInitiator : IInitiator private HashSet disconnected_ = new HashSet(); private bool isStopped_ = true; private Thread thread_; - private SessionFactory sessionFactory_ = null; + private SessionFactory sessionFactory_ = null; #region Properties @@ -65,7 +65,7 @@ public void Start() foreach (SessionID sessionID in _settings.GetSessions()) { Dictionary dict = _settings.Get(sessionID); - AddSession(sessionID, dict); + AddSession(sessionID, dict); } if (0 == sessions_.Count) @@ -78,54 +78,61 @@ public void Start() thread_.Start(); } - /// - /// Add new session, either at start-up or as an ad-hoc operation - /// - /// ID of new session - /// config settings for new session - /// true if session added succesfully, false if session already exists or is of wrong type - public bool AddSession(SessionID sessionID, Dictionary dict) - { - string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); - if (!sessionIDs_.Contains(sessionID) && "initiator" == connectionType) - { - sessionIDs_.Add(sessionID); - sessions_[sessionID] = sessionFactory_.Create(sessionID, dict); - SetDisconnected(sessionID); - return true; - } - return false; - } - - /// - /// Ad-hoc removal of an existing sssion - /// - /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection - public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) - { - if (sessionIDs_.Contains(sessionID)) - { - Session session = sessions_[sessionID]; - bool isDisconnected = false; - lock (sync_) - { - isDisconnected = IsDisconnected(sessionID); - if (!(isDisconnected || terminateActiveSession)) - return false; - sessionIDs_.Remove(sessionID); - sessions_.Remove(sessionID); - SetDisconnected(sessionID); - disconnected_.Remove(sessionID); - OnRemove(sessionID); - } - if (!isDisconnected) - session.Reset("Session reconfigured(AbstractInitiator.RemoveSession)", "Session disabled"); - session.Dispose(); - } - return true; - } + /// + /// Add new session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added succesfully, false if session already exists or is of wrong type + public bool AddSession(SessionID sessionID, Dictionary dict) + { + if (dict.GetString(SessionSettings.CONNECTION_TYPE) == "initiator" && !sessionIDs_.Contains(sessionID)) + { + Session session = sessionFactory_.Create(sessionID, dict); + lock (sync_) + { + if (!_settings.Has(sessionID)) // session won't be in settings if ad-hoc creation after startup + _settings.Set(sessionID, dict); + sessionIDs_.Add(sessionID); + sessions_[sessionID] = session; + SetDisconnected(sessionID); + } + return true; + } + return false; + } + + /// + /// Ad-hoc removal of an existing sssion + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) + { + if (sessionIDs_.Contains(sessionID)) + { + Session session = sessions_[sessionID]; + if (session.IsLoggedOn && !terminateActiveSession) + return false; + bool disconnectRequired = false; + lock (sync_) + { + sessionIDs_.Remove(sessionID); + sessions_.Remove(sessionID); + _settings.Remove(sessionID); + disconnectRequired = IsConnected(sessionID) || IsPending(sessionID); + if (disconnectRequired) + SetDisconnected(sessionID); + disconnected_.Remove(sessionID); + OnRemove(sessionID); + } + if (disconnectRequired) + session.Disconnect("Disabled via dynamic config update"); + session.Dispose(); + } + return true; + } /// /// Logout existing session and close connection. Attempt graceful disconnect first. @@ -222,15 +229,14 @@ public bool IsLoggedOn protected virtual void OnConfigure(SessionSettings settings) { } + /// + /// Override this to handle ad-hoc session removal + /// + /// ID of session being remvoed + protected virtual void OnRemove(SessionID sessionID) + { } - /// - /// Override this to handle ad-hoc session removal - /// - /// ID of session being remvoed - protected virtual void OnRemove(SessionID sessionID) - { } - - [System.Obsolete("This method's intended purpose is unclear. Don't use it.")] + [System.Obsolete("This method's intended purpose is unclear. Don't use it.")] protected virtual void OnInitialize(SessionSettings settings) { } @@ -306,12 +312,12 @@ protected void SetDisconnected(SessionID sessionID) { lock (sync_) { - if (sessionIDs_.Contains(sessionID)) - { - pending_.Remove(sessionID); - connected_.Remove(sessionID); - disconnected_.Add(sessionID); - } + if (sessionIDs_.Contains(sessionID)) + { + pending_.Remove(sessionID); + connected_.Remove(sessionID); + disconnected_.Add(sessionID); + } } } diff --git a/QuickFIXn/Dictionary.cs b/QuickFIXn/Dictionary.cs index a7026c16b..1e605cd29 100755 --- a/QuickFIXn/Dictionary.cs +++ b/QuickFIXn/Dictionary.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Collections.Generic; using System.Globalization; namespace QuickFix @@ -37,7 +39,8 @@ public int Size } #endregion - + + #region Public Methods public Dictionary() { } @@ -195,6 +198,42 @@ public void Merge(Dictionary toMerge) if(!data_.ContainsKey(entry.Key)) data_[entry.Key] = entry.Value; } + #endregion + + #region Public Overrides + /// + /// Test Dictionary objects for equality. + /// Dictionaries are deemed to be equal if their names and dictionary contents are the same + /// + /// Dictionary to compare against + /// true if the two Dictionary objects are the same in terms of contents, else false + public override bool Equals(object other) + { + //Check whether the compared objects reference the same data. + if (Object.ReferenceEquals(this, other)) + return true; + + //Check whether the compared object is null. + if (Object.ReferenceEquals(other, null)) + return false; + + //Check whether the names and dictionary contents are the same + var otherDict = (Dictionary)other; + return Name == otherDict.Name && Count == otherDict.Count && data_.SequenceEqual(otherDict.data_); + } + + /// + /// Generate hash code for the Dictionary. + /// If Equals() returns true for a compared objects, + /// then GetHashCode() must return the same value this object and the compared objects. + /// + /// hash code + public override int GetHashCode() + { + int nameHash = Object.ReferenceEquals(Name, null) ? 1 : Name.GetHashCode(); + return nameHash + 100 * Count; + } + #endregion #region IEnumerable Members diff --git a/QuickFIXn/SessionSettings.cs b/QuickFIXn/SessionSettings.cs index a1c8f51ff..a4d2bcc84 100755 --- a/QuickFIXn/SessionSettings.cs +++ b/QuickFIXn/SessionSettings.cs @@ -179,6 +179,21 @@ public void Set(QuickFix.Dictionary defaults) entry.Value.Merge(defaults_); } + /// + /// Remove existing session config from the settings + /// + /// ID of session for which config to be removed + /// true if removed, false if config for the session does not exist + public bool Remove(SessionID sessionID) + { + return settings_.Remove(sessionID); + } + + /// + /// Add new session config + /// + /// ID of session for which to add config + /// session config public void Set(SessionID sessionID, QuickFix.Dictionary settings) { if (Has(sessionID)) diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index b06249581..018700b4a 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -179,11 +179,13 @@ public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) Session session = null; if (sessions_.TryGetValue(sessionID, out session)) { + if (session.IsLoggedOn && !terminateActiveSession) + return false; + session.Disconnect("Disabled via dynamic config update"); foreach (AcceptorSocketDescriptor descriptor in socketDescriptorForAddress_.Values) if (descriptor.RemoveSession(sessionID)) break; sessions_.Remove(sessionID); - session.Reset("Session reconfigured(ThreadeSocketAcceptor.RemoveSession)", "Session disabled"); session.Dispose(); } return true; diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 669f6afc4..de8b7c1ea 100755 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -166,14 +166,14 @@ protected override void OnStart() } } - /// - /// Ad-hoc session removal - /// - /// ID of session being remvoed - protected override void OnRemove(SessionID sessionID) - { - sessionToHostNum_.Remove(sessionID); - } + /// + /// Ad-hoc session removal + /// + /// ID of session being remvoed + protected override void OnRemove(SessionID sessionID) + { + sessionToHostNum_.Remove(sessionID); + } protected override bool OnPoll(double timeout) { From 234ff56711d67307c599f7903aec593a80f21131 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 2 Jan 2015 16:24:21 -0600 Subject: [PATCH 05/20] Revert "Fix distributed deadlock between two sessions" --- QuickFIXn/Session.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 470be3acd..ec50b9343 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -336,15 +336,13 @@ public virtual bool Send(Message message) /// public bool Send(string message) { - IResponder responder; lock (sync_) { if (null == responder_) return false; - responder = responder_; + this.Log.OnOutgoing(message); + return responder_.Send(message); } - this.Log.OnOutgoing(message); - return responder.Send(message); } // TODO for v2 - rename, make internal @@ -1577,8 +1575,6 @@ private bool IsAdminMessage(Message msg) protected bool SendRaw(Message message, int seqNum) { - string messageString; - lock (sync_) { string msgType = message.Header.GetField(Fields.Tags.MsgType); @@ -1614,11 +1610,11 @@ protected bool SendRaw(Message message, int seqNum) } } - messageString = message.ToString(); + string messageString = message.ToString(); if (0 == seqNum) Persist(message, messageString); + return Send(messageString); } - return Send(messageString); } public void Dispose() From 2e6117179a2b08b432d87793a3016c72a98f4d72 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 2 Jan 2015 16:31:38 -0600 Subject: [PATCH 06/20] release notes --- NEXT_VERSION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_VERSION.md b/NEXT_VERSION.md index f05ba3fc2..73b6772e8 100644 --- a/NEXT_VERSION.md +++ b/NEXT_VERSION.md @@ -27,5 +27,6 @@ Changes since the last version (oldest first): * (minor) #286 - FieldBase.Equals() and .GetHashcode() (steffanu) * (patch) #287 - sync fix in Session class (steffanu) * (patch) #275 - SessionID.IsSet() should be used instead of !=SessionID.NOT_SET (akamyshanov) +* (patch) #297 - revert #287 From 4f3abdc2a7ee6fa528731579e30024eeddaab6da Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Thu, 19 Mar 2015 13:59:35 -0500 Subject: [PATCH 07/20] these should have been committed long ago --- web/views/download.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/views/download.md b/web/views/download.md index 57eda840d..a982b7a8a 100644 --- a/web/views/download.md +++ b/web/views/download.md @@ -1,9 +1,9 @@ Download ======== -The latest version of QuickFIX/n is v1.5.0 +The latest version of QuickFIX/n is v1.4.0 -You can download it [here] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.5.0.zip) +You can download it [here] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.4.0.zip) Source Code ----------- @@ -11,7 +11,6 @@ View the code on [github] [0] Previous Versions ----------------- - * [v1.4.0] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.4.0.zip) * [v1.3.0] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.3.0.zip) * [v1.2.0] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.2.0.zip) * [v1.1.0] (http://quickfixn.s3.amazonaws.com/quickfixn-v1.1.0.zip) From ddea7491109d4396c2d7046b2ffde1134d5770c5 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Thu, 7 May 2015 17:34:16 -0500 Subject: [PATCH 08/20] attribution for #290 --- NEXT_VERSION.md | 1 + web/views/about/credits.md | 1 + 2 files changed, 2 insertions(+) diff --git a/NEXT_VERSION.md b/NEXT_VERSION.md index 73b6772e8..e53358af0 100644 --- a/NEXT_VERSION.md +++ b/NEXT_VERSION.md @@ -28,5 +28,6 @@ Changes since the last version (oldest first): * (patch) #287 - sync fix in Session class (steffanu) * (patch) #275 - SessionID.IsSet() should be used instead of !=SessionID.NOT_SET (akamyshanov) * (patch) #297 - revert #287 +* (patch) #290 - support for RefreshOnLogon (martinadams) diff --git a/web/views/about/credits.md b/web/views/about/credits.md index 6fb10ca88..9b200c10c 100644 --- a/web/views/about/credits.md +++ b/web/views/about/credits.md @@ -46,4 +46,5 @@ Contributors - Jac Steyn - Timothy Caro - Steffan Ulfburg +- Martin Adams From 5820c4e488885348cd993f50dee4221d04cc796a Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Wed, 13 May 2015 19:04:22 +0100 Subject: [PATCH 09/20] Changes as requested to tab/indents and comments --- QuickFIXn/IApplicationExt.cs | 32 +++++++++++++++++++------------- UnitTests/SessionTest.cs | 18 +++++++++--------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/QuickFIXn/IApplicationExt.cs b/QuickFIXn/IApplicationExt.cs index 35d34d484..2555472cc 100644 --- a/QuickFIXn/IApplicationExt.cs +++ b/QuickFIXn/IApplicationExt.cs @@ -3,18 +3,24 @@ namespace QuickFix { /// - /// This is the optional extension interface for processing session messages, and facilitates early interception of inbound messages. - /// 'Early', in this context, means after structure, length and checksum have been validated, but before any further validation has been performed. + /// This is the optional extension interface for processing inbound messages, + /// and facilitates early interception of such messages. 'Early', in this context, + /// means after structure, length and checksum have been validated, but before any + /// further validation has been performed. + /// This interface will not normally be required, and it should be used only with caution: + /// it allows modfications to be made to irregular inbound messages that would otherwise + /// fail validation against the Fix dictionary, an provides an alternative to dictionary + /// customisation as a means of dealing with such messages. /// - public interface IApplicationExt : IApplication - { - /// - /// This callback provides early notification of when an administrative or application message is sent from a counterparty to your FIX engine. - /// This can be useful for doing pre-processing of an inbound message after its structure, checksum and length have been validated, but before - /// any further validation has been performed on it. - /// - /// received message - /// session on which message received - void FromEarlyIntercept(Message message, SessionID sessionID); - } + public interface IApplicationExt : IApplication + { + /// + /// This callback provides early notification of when an administrative or application message is sent from a counterparty to your FIX engine. + /// This can be useful for doing pre-processing of an inbound message after its structure, checksum and length have been validated, but before + /// any further validation has been performed on it. + /// + /// received message + /// session on which message received + void FromEarlyIntercept(Message message, SessionID sessionID); + } } diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index b88cc143d..444140cc8 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -119,7 +119,7 @@ class MockApplicationExt : QuickFix.IApplicationExt { public HashSet InterceptedMessageTypes = new HashSet(); - #region Application Members + #region Application Members public void ToAdmin(QuickFix.Message message, QuickFix.SessionID sessionID) { @@ -768,18 +768,18 @@ public void TestApplicationExtension() { var mockApp = new MockApplicationExt(); session = new QuickFix.Session(mockApp, new QuickFix.MemoryStoreFactory(), sessionID, - new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, new QuickFix.ScreenLogFactory(settings), new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, new QuickFix.ScreenLogFactory(settings), new QuickFix.DefaultMessageFactory(), "blah"); session.SetResponder(responder); session.CheckLatency = false; Logon(); QuickFix.FIX42.NewOrderSingle order = new QuickFix.FIX42.NewOrderSingle( - new QuickFix.Fields.ClOrdID("1"), - new QuickFix.Fields.HandlInst(QuickFix.Fields.HandlInst.MANUAL_ORDER), - new QuickFix.Fields.Symbol("IBM"), - new QuickFix.Fields.Side(QuickFix.Fields.Side.BUY), - new QuickFix.Fields.TransactTime(), - new QuickFix.Fields.OrdType(QuickFix.Fields.OrdType.LIMIT)); + new QuickFix.Fields.ClOrdID("1"), + new QuickFix.Fields.HandlInst(QuickFix.Fields.HandlInst.MANUAL_ORDER), + new QuickFix.Fields.Symbol("IBM"), + new QuickFix.Fields.Side(QuickFix.Fields.Side.BUY), + new QuickFix.Fields.TransactTime(), + new QuickFix.Fields.OrdType(QuickFix.Fields.OrdType.LIMIT)); order.Header.SetField(new QuickFix.Fields.TargetCompID(sessionID.SenderCompID)); order.Header.SetField(new QuickFix.Fields.SenderCompID(sessionID.TargetCompID)); @@ -790,6 +790,6 @@ public void TestApplicationExtension() Assert.That(mockApp.InterceptedMessageTypes.Count, Is.EqualTo(2)); Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.LOGON)); Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.NEWORDERSINGLE)); - } + } } } From 646f43d6d95eca00fcbfef2c041f59dda8dfe738 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 19 May 2015 00:23:22 +0100 Subject: [PATCH 10/20] Unit Test + minor code updates for dynamic sessions --- QuickFIXn/AbstractInitiator.cs | 36 +-- QuickFIXn/Dictionary.cs | 14 +- QuickFIXn/IAcceptor.cs | 30 +- QuickFIXn/IInitiator.cs | 30 +- QuickFIXn/Session.cs | 8 +- QuickFIXn/ThreadedSocketAcceptor.cs | 110 ++++---- UnitTests/DictionaryTest.cs | 33 ++- UnitTests/SessionDynamicTest.cs | 407 ++++++++++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 9 files changed, 558 insertions(+), 111 deletions(-) create mode 100644 UnitTests/SessionDynamicTest.cs diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 604018e03..f58e74836 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -110,27 +110,30 @@ public bool AddSession(SessionID sessionID, Dictionary dict) /// true if session removed or was already not present, false if could not be removed because of active connection public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) { - if (sessionIDs_.Contains(sessionID)) + Session session = null; + bool disconnectRequired = false; + lock (sync_) { - Session session = sessions_[sessionID]; - if (session.IsLoggedOn && !terminateActiveSession) - return false; - bool disconnectRequired = false; - lock (sync_) + if (sessionIDs_.Contains(sessionID)) { - sessionIDs_.Remove(sessionID); + session = sessions_[sessionID]; + if (session.IsLoggedOn && !terminateActiveSession) + return false; + sessions_.Remove(sessionID); _settings.Remove(sessionID); disconnectRequired = IsConnected(sessionID) || IsPending(sessionID); if (disconnectRequired) - SetDisconnected(sessionID); + SetDisconnected(sessionID); disconnected_.Remove(sessionID); + sessionIDs_.Remove(sessionID); OnRemove(sessionID); } - if (disconnectRequired) - session.Disconnect("Disabled via dynamic config update"); - session.Dispose(); } + if (disconnectRequired) + session.Disconnect("Removed dynamically"); + if (session != null) + session.Dispose(); return true; } @@ -195,12 +198,13 @@ public void Stop(bool force) { foreach (Session s in sessions_.Values) s.Dispose(); + + sessions_.Clear(); + sessionIDs_.Clear(); + pending_.Clear(); + connected_.Clear(); + disconnected_.Clear(); } - sessions_.Clear(); - sessionIDs_.Clear(); - pending_.Clear(); - connected_.Clear(); - disconnected_.Clear(); } public bool IsLoggedOn diff --git a/QuickFIXn/Dictionary.cs b/QuickFIXn/Dictionary.cs index 1e605cd29..6b03b5520 100755 --- a/QuickFIXn/Dictionary.cs +++ b/QuickFIXn/Dictionary.cs @@ -219,13 +219,21 @@ public override bool Equals(object other) //Check whether the names and dictionary contents are the same var otherDict = (Dictionary)other; - return Name == otherDict.Name && Count == otherDict.Count && data_.SequenceEqual(otherDict.data_); + if (Name != otherDict.Name || Count != otherDict.Count) + return false; + + // Could use LINQ query here, but this is probably faster! + string otherDictValue = null; + foreach (var kvp in data_) + if (!otherDict.data_.TryGetValue(kvp.Key, out otherDictValue) || otherDictValue != kvp.Value) + return false; + return true; } /// /// Generate hash code for the Dictionary. - /// If Equals() returns true for a compared objects, - /// then GetHashCode() must return the same value this object and the compared objects. + /// If Equals() returns true for a compared object, + /// then GetHashCode() must return the same value for this object and the compared object. /// /// hash code public override int GetHashCode() diff --git a/QuickFIXn/IAcceptor.cs b/QuickFIXn/IAcceptor.cs index 030c2b281..5226c4922 100644 --- a/QuickFIXn/IAcceptor.cs +++ b/QuickFIXn/IAcceptor.cs @@ -41,22 +41,22 @@ public interface IAcceptor /// /// a map of SessionIDs to EndPoints Dictionary GetAcceptorAddresses(); - - /// - /// Add a new session after acceptor has been started - /// - /// ID of session to be added - /// session settings - /// >true if session added succesfully, false if session already exists or is of wrong type - bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); + + /// + /// Add a new session after acceptor has been started + /// + /// ID of session to be added + /// session settings + /// >true if session added succesfully, false if session already exists or is of wrong type + bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); - /// - /// Remove an existing session after acceptor has been started - /// - /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection - bool RemoveSession(SessionID sessionID, bool terminateActiveSession); + /// + /// Remove an existing session after acceptor has been started + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } /// diff --git a/QuickFIXn/IInitiator.cs b/QuickFIXn/IInitiator.cs index ff121a681..8d2b6d3f8 100644 --- a/QuickFIXn/IInitiator.cs +++ b/QuickFIXn/IInitiator.cs @@ -42,21 +42,21 @@ public interface IInitiator : IDisposable /// the SessionIDs for the sessions managed by this initiator HashSet GetSessionIDs(); - /// - /// Add a new session after initiator has been started - /// - /// ID of session to be added - /// session settings - /// >true if session added succesfully, false if session already exists or is of wrong type - bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); - - /// - /// Remove an existing session after initiator has been started - /// - /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection - bool RemoveSession(SessionID sessionID, bool terminateActiveSession); + /// + /// Add a new session after initiator has been started + /// + /// ID of session to be added + /// session settings + /// >true if session added succesfully, false if session already exists or is of wrong type + bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); + + /// + /// Remove an existing session after initiator has been started + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } /// diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 155a1d44f..0952b401d 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -1631,10 +1631,10 @@ protected bool SendRaw(Message message, int seqNum) public void Dispose() { if (state_ != null) { state_.Dispose(); } - lock (sessions_) - { - sessions_.Remove(this.SessionID); - } + lock (sessions_) + { + sessions_.Remove(this.SessionID); + } } } diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 018700b4a..cd4a73ac3 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -48,15 +48,15 @@ public void AcceptSession(Session session) acceptedSessions_[session.SessionID] = session; } - /// - /// Remove a session from those tied to this socket. - /// - /// ID of session to be removed - /// true if session removed, false if not found - public bool RemoveSession(SessionID sessionID) - { - return acceptedSessions_.Remove(sessionID); - } + /// + /// Remove a session from those tied to this socket. + /// + /// ID of session to be removed + /// true if session removed, false if not found + public bool RemoveSession(SessionID sessionID) + { + return acceptedSessions_.Remove(sessionID); + } public Dictionary GetAcceptedSessions() { @@ -66,7 +66,7 @@ public Dictionary GetAcceptedSessions() private Dictionary sessions_ = new Dictionary(); private Dictionary socketDescriptorForAddress_ = new Dictionary(); - private SessionFactory sessionFactory_; + private SessionFactory sessionFactory_; private bool isStarted_ = false; private object sync_ = new object(); @@ -102,18 +102,18 @@ public ThreadedSocketAcceptor(SessionFactory sessionFactory, SessionSettings set private void CreateSessions(SessionSettings settings, SessionFactory sessionFactory) { - sessionFactory_ = sessionFactory; + sessionFactory_ = sessionFactory; foreach (SessionID sessionID in settings.GetSessions()) { QuickFix.Dictionary dict = settings.Get(sessionID); - AddSession(sessionID, dict); + AddSession(sessionID, dict); } if (0 == socketDescriptorForAddress_.Count) throw new ConfigError("No acceptor sessions found in SessionSettings."); } - private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(Dictionary dict) + private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(Dictionary dict) { int port = System.Convert.ToInt32(dict.GetLong(SessionSettings.SOCKET_ACCEPT_PORT)); SocketSettings socketSettings = new SocketSettings(); @@ -145,51 +145,51 @@ private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(Dictionary dict) return descriptor; } - /// - /// Add new session, either at start-up or as an ad-hoc operation - /// - /// ID of new session - /// config settings for new session - /// true if session added succesfully, false if session already exists or is of wrong type - public bool AddSession(SessionID sessionID, Dictionary dict) - { - if (!sessions_.ContainsKey(sessionID)) - { - string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); - if ("acceptor" == connectionType) - { - AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(dict); - Session session = sessionFactory_.Create(sessionID, dict); - descriptor.AcceptSession(session); - sessions_[sessionID] = session; - return true; - } - } - return false; - } - - /// - /// Ad-hoc removal of an existing sssion - /// - /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection - public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) - { - Session session = null; - if (sessions_.TryGetValue(sessionID, out session)) - { + /// + /// Add new session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added succesfully, false if session already exists or is of wrong type + public bool AddSession(SessionID sessionID, Dictionary dict) + { + if (!sessions_.ContainsKey(sessionID)) + { + string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); + if ("acceptor" == connectionType) + { + AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(dict); + Session session = sessionFactory_.Create(sessionID, dict); + descriptor.AcceptSession(session); + sessions_[sessionID] = session; + return true; + } + } + return false; + } + + /// + /// Ad-hoc removal of an existing sssion + /// + /// ID of session to be removed + /// true if sesion to be removed even if it has an active connection + /// true if session removed or was already not present, false if could not be removed because of active connection + public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) + { + Session session = null; + if (sessions_.TryGetValue(sessionID, out session)) + { if (session.IsLoggedOn && !terminateActiveSession) return false; session.Disconnect("Disabled via dynamic config update"); - foreach (AcceptorSocketDescriptor descriptor in socketDescriptorForAddress_.Values) - if (descriptor.RemoveSession(sessionID)) - break; - sessions_.Remove(sessionID); - session.Dispose(); - } - return true; - } + foreach (AcceptorSocketDescriptor descriptor in socketDescriptorForAddress_.Values) + if (descriptor.RemoveSession(sessionID)) + break; + sessions_.Remove(sessionID); + session.Dispose(); + } + return true; + } private void StartAcceptingConnections() { diff --git a/UnitTests/DictionaryTest.cs b/UnitTests/DictionaryTest.cs index 7c8ae3380..6cd241a9b 100755 --- a/UnitTests/DictionaryTest.cs +++ b/UnitTests/DictionaryTest.cs @@ -58,7 +58,7 @@ public void GetDouble() System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fr-FR"); QuickFix.Dictionary d = new QuickFix.Dictionary(); - d.SetString("DOUBLEKEY1","12.3"); + d.SetString("DOUBLEKEY1", "12.3"); d.SetString("DOUBLEKEY2", "987362.987362"); d.SetString("BADDOUBLEKEY", "AB12.3"); d.SetString("FOREIGNFORMAT", "44,44"); @@ -87,7 +87,7 @@ public void SetGetBool() public void SetGetDay() { QuickFix.Dictionary d = new QuickFix.Dictionary(); - + d.SetString("DAY1", "SU"); d.SetString("DAY2", "MO"); d.SetString("DAY3", "TU"); @@ -118,7 +118,7 @@ public void SetGetDay() Assert.That(d.GetDay("NEXTDAY6"), Is.EqualTo(System.DayOfWeek.Friday)); Assert.That(d.GetDay("NEXTDAY7"), Is.EqualTo(System.DayOfWeek.Saturday)); } - + [Test] public void Merge() { @@ -135,5 +135,32 @@ public void Merge() Assert.That(first.GetString("SECONDKEY"), Is.EqualTo("SECONDVALUE")); Assert.That(first.GetString("THIRDKEY"), Is.EqualTo("FIRST")); } + + [Test] + public void ValueEquality() + { + QuickFix.Dictionary first = new QuickFix.Dictionary("Name"); + QuickFix.Dictionary second = new QuickFix.Dictionary("Name"); + Assert.True(first.Equals(second)); + + first.SetString("THIRDKEY", "FIRST"); + second.SetString("THIRDKEY", "SECOND"); + Assert.False(first.Equals(second)); + + first.SetString("THIRDKEY", "SECOND"); + Assert.True(first.Equals(second)); + + first.SetString("FIRSTKEY", "FIRSTVALUE"); + second.SetString("SECONDKEY", "SECONDVALUE"); + Assert.False(first.Equals(second)); + + first.SetString("SECONDKEY", "SECONDVALUE"); + second.SetString("FIRSTKEY", "FIRSTVALUE"); + Assert.True(first.Equals(second)); + + QuickFix.Dictionary third = new QuickFix.Dictionary("Name1"); + QuickFix.Dictionary fourth = new QuickFix.Dictionary("Name2"); + Assert.False(third.Equals(fourth)); + } } } diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs new file mode 100644 index 000000000..fa25a13ff --- /dev/null +++ b/UnitTests/SessionDynamicTest.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using System.Text.RegularExpressions; + +using NUnit.Framework; +using QuickFix; +using QuickFix.Transport; + +namespace UnitTests +{ + [TestFixture] + class SessionDynamicTest + { + public class TestApplication : IApplication + { + Action _logonNotify; + Action _logoffNotify; + public TestApplication(Action logonNotify, Action logoffNotify) + { + _logonNotify = logonNotify; + _logoffNotify = logoffNotify; + } + public void FromAdmin(Message message, SessionID sessionID) + { } + + public void FromApp(Message message, SessionID sessionID) + { } + + public void OnCreate(SessionID sessionID) { } + public void OnLogout(SessionID sessionID) + { + _logoffNotify(sessionID.TargetCompID); + } + public void OnLogon(SessionID sessionID) + { + _logonNotify(sessionID.TargetCompID); + } + + public void ToAdmin(Message message, SessionID sessionID) { } + public void ToApp(Message message, SessionID sessionID) { } + } + class SocketState + { + public SocketState(Socket s) + { + _socket = s; + } + public Socket _socket; + public byte[] _rxBuffer = new byte[1024]; + public string _messageFragment = string.Empty; + public string _exMessage; + } + + const string Host = "127.0.0.1"; + const int ConnectPort = 55100; + const int AcceptPort = 55101; + const string ServerCompID = "dummy"; + const string StaticInitiatorCompID = "ini01"; + const string StaticAcceptorCompID = "acc01"; + + const string FIXMessageEnd = @"\x0110=\d{3}\x01"; + const string FIXMessageDelimit = @"(8=FIX|\A).*?(" + FIXMessageEnd + @"|\z)"; + + SocketInitiator _initiator; + ThreadedSocketAcceptor _acceptor; + Dictionary _sessions; + HashSet _loggedOnCompIDs; + Socket _listenSocket; + + Dictionary CreateSessionConfig(string targetCompID, bool isInitiator) + { + Dictionary settings = new Dictionary(); + settings.SetString(SessionSettings.CONNECTION_TYPE, isInitiator ? "initiator" : "acceptor"); + settings.SetString(SessionSettings.USE_DATA_DICTIONARY, "N"); + settings.SetString(SessionSettings.START_TIME, "12:00:00"); + settings.SetString(SessionSettings.END_TIME, "12:00:00"); + settings.SetString(SessionSettings.HEARTBTINT, "300"); + settings.SetString(SessionSettings.SOCKET_CONNECT_HOST, Host); + settings.SetString(SessionSettings.SOCKET_CONNECT_PORT, ConnectPort.ToString()); + settings.SetString(SessionSettings.SOCKET_ACCEPT_HOST, Host); + settings.SetString(SessionSettings.SOCKET_ACCEPT_PORT, AcceptPort.ToString()); + return settings; + } + + SessionID CreateSessionID(string targetCompID) + { + return new SessionID(QuickFix.Values.BeginString_FIX42, ServerCompID, targetCompID); + } + + void LogonCallback(string compID) + { + lock (_loggedOnCompIDs) + { + _loggedOnCompIDs.Add(compID); + Monitor.Pulse(_loggedOnCompIDs); + } + } + void LogoffCallback(string compID) + { + lock (_loggedOnCompIDs) + { + _loggedOnCompIDs.Remove(compID); + Monitor.Pulse(_loggedOnCompIDs); + } + } + + void StartEngine(bool initiator) + { + TestApplication application = new TestApplication(LogonCallback, LogoffCallback); + IMessageStoreFactory storeFactory = new MemoryStoreFactory(); + ILogFactory logFactory = new ScreenLogFactory(false, false, false); + SessionSettings settings = new SessionSettings(); + + if (initiator) + { + Dictionary defaults = new Dictionary(); + defaults.SetString(SessionSettings.RECONNECT_INTERVAL, "1"); + settings.Set(defaults); + settings.Set(CreateSessionID(StaticInitiatorCompID), CreateSessionConfig(StaticInitiatorCompID, true)); + _initiator = new SocketInitiator(application, storeFactory, settings, logFactory); + _initiator.Start(); + } + else + { + settings.Set(CreateSessionID(StaticAcceptorCompID), CreateSessionConfig(StaticAcceptorCompID, false)); + _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, logFactory); + _acceptor.Start(); + } + } + + void StartListener() + { + var address = IPAddress.Parse(Host); + var listenEndpoint = new IPEndPoint(address, ConnectPort); + _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _listenSocket.Bind(listenEndpoint); + _listenSocket.Listen(10); + _listenSocket.BeginAccept(new AsyncCallback(ProcessInboundConnect), null); + } + + void ProcessInboundConnect(IAsyncResult ar) + { + Socket handler = null; + try + { + handler = _listenSocket.EndAccept(ar); + } + catch + { + _listenSocket = null; // Assume listener has been closed + } + + if (handler != null) + { + ReceiveAsync(new SocketState(handler)); + _listenSocket.BeginAccept(new AsyncCallback(ProcessInboundConnect), null); + } + } + + void ProcessRXData(IAsyncResult ar) + { + SocketState socketState = (SocketState)ar.AsyncState; + int bytesReceived = 0; + try + { + bytesReceived = socketState._socket.EndReceive(ar); + } + catch (Exception ex) + { + socketState._exMessage = ex.InnerException == null ? ex.Message : ex.InnerException.Message; + } + + if (bytesReceived == 0) + { + socketState._socket.Close(); + lock (socketState._socket) + Monitor.Pulse(socketState._socket); + return; + } + string msgText = Encoding.ASCII.GetString(socketState._rxBuffer, 0, bytesReceived); + foreach (Match m in Regex.Matches(msgText, FIXMessageDelimit)) + { + socketState._messageFragment += m.Value; + if (Regex.IsMatch(socketState._messageFragment, FIXMessageEnd)) + { + Message message = new Message(socketState._messageFragment); + socketState._messageFragment = string.Empty; + string targetCompID = message.Header.GetField(QuickFix.Fields.Tags.TargetCompID); + if (message.Header.GetField(QuickFix.Fields.Tags.MsgType) == QuickFix.Fields.MsgType.LOGON) + lock (_sessions) + { + _sessions[targetCompID] = socketState; + Monitor.Pulse(_sessions); + } + } + } + ReceiveAsync(socketState); + } + + void ReceiveAsync(SocketState socketState) + { + socketState._socket.BeginReceive(socketState._rxBuffer, 0, socketState._rxBuffer.Length, SocketFlags.None, new AsyncCallback(ProcessRXData), socketState);; + } + + Socket ConnectToEngine() + { + var address = IPAddress.Parse(Host); + var endpoint = new IPEndPoint(address, AcceptPort); + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + try + { + socket.Connect(endpoint); + ReceiveAsync(new SocketState(socket)); + return socket; + } + catch (Exception ex) + { + Assert.Fail(string.Format("Failed to connect: {0}", ex.Message)); + return null; + } + } + + Socket GetSocket(string compID) + { + lock (_sessions) + return _sessions[compID]._socket; + } + + bool WaitForLogonStatus(string targetCompID) + { + lock (_loggedOnCompIDs) + { + if (!_loggedOnCompIDs.Contains(targetCompID)) + Monitor.Wait(_loggedOnCompIDs, 10000); + return _loggedOnCompIDs.Contains(targetCompID); + } + } + + bool WaitForLogonMessage(string targetCompID) + { + lock (_sessions) + { + if (!_sessions.ContainsKey(targetCompID)) + Monitor.Wait(_sessions, 10000); + return _sessions.ContainsKey(targetCompID); + } + } + + bool WaitForDisconnect(Socket s) + { + lock (s) + { + if (s.Connected) + Monitor.Wait(s, 10000); + return !s.Connected; + } + } + + bool WaitForDisconnect(string compID) + { + return WaitForDisconnect(GetSocket(compID)); + } + + bool HasReceivedMessage(string compID) + { + lock (_sessions) + return _sessions.ContainsKey(compID); + } + + bool IsLoggedOn(string compID) + { + lock (_loggedOnCompIDs) + return _loggedOnCompIDs.Contains(compID); + } + + void SendInitiatorLogon(string senderCompID) + { + SendLogon(GetSocket(senderCompID), senderCompID); + } + + void SendLogon(Socket s, string senderCompID) + { + var msg = new QuickFix.FIX42.Logon(); + msg.Header.SetField(new QuickFix.Fields.TargetCompID(ServerCompID)); + msg.Header.SetField(new QuickFix.Fields.SenderCompID(senderCompID)); + msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(1)); + msg.Header.SetField(new QuickFix.Fields.SendingTime(System.DateTime.UtcNow)); + msg.SetField(new QuickFix.Fields.HeartBtInt(300)); + s.Send(Encoding.ASCII.GetBytes(msg.ToString())); + } + + [SetUp] + public void Setup() + { + _sessions = new Dictionary(); + _loggedOnCompIDs = new HashSet(); + } + + [TearDown] + public void TearDown() + { + if (_listenSocket != null) + _listenSocket.Close(); + if (_initiator != null) + _initiator.Stop(true); + if (_acceptor != null) + _acceptor.Stop(true); + + _initiator = null; + _acceptor = null; + } + + [Test] + public void DynamicAcceptor() + { + StartEngine(false); + + // Ensure we can log on statically (normally) configured acceptor + var socket01 = ConnectToEngine(); + SendLogon(socket01, StaticAcceptorCompID); + Assert.IsTrue(WaitForLogonStatus(StaticAcceptorCompID), "Failed to logon static acceptor session"); + + // Ensure that attempt to log on as yet un-added dynamic acceptor fails + var socket02 = ConnectToEngine(); + string dynamicCompID = "acc10"; + SendLogon(socket02, dynamicCompID); + Assert.IsTrue(WaitForDisconnect(socket02), "Server failed to disconnect unconfigured CompID"); + Assert.False(HasReceivedMessage(dynamicCompID), "Unexpected messaage received for unconfigured CompID"); + + // Add the dynamic acceptor and ensure that we can now log on + var sessionID = CreateSessionID(dynamicCompID); + Assert.IsTrue(_acceptor.AddSession(sessionID, CreateSessionConfig(dynamicCompID, false)), "Failed to add dynamic session to acceptor"); + var socket03 = ConnectToEngine(); + SendLogon(socket03, dynamicCompID); + Assert.IsTrue(WaitForLogonStatus(dynamicCompID), "Failed to logon dynamic acceptor session"); + + // Now that dynamic acceptor is logged on, ensure that unforced attempt to remove session fails + Assert.IsFalse(_acceptor.RemoveSession(sessionID, false), "Unexpected success removing active session"); + Assert.IsTrue(socket03.Connected, "Unexpected loss of connection"); + + // Ensure that forced attempt to remove session dynamic sesison succeeds, even though it is in logged on state + Assert.IsTrue(_acceptor.RemoveSession(sessionID, true), "Failed to remove active session"); + Assert.IsTrue(WaitForDisconnect(socket03), "Socket still connected after session removed"); + Assert.IsFalse(IsLoggedOn(dynamicCompID), "Session still logged on after being removed"); + + // Ensure that we can perform unforced removal of a dynamic sesion that is not logged on. + string dynamicCompID2 = "acc20"; + var sessionID2 = CreateSessionID(dynamicCompID2); + Assert.IsTrue(_acceptor.AddSession(sessionID2, CreateSessionConfig(dynamicCompID2, false)), "Failed to add dynamic session to acceptor"); + Assert.IsTrue(_acceptor.RemoveSession(sessionID2, false), "Failed to remove inactive session"); + + // Ensure that we can remove statically configured session + Assert.IsTrue(IsLoggedOn(StaticAcceptorCompID), "Unexpected logoff"); + Assert.IsTrue(_acceptor.RemoveSession(CreateSessionID(StaticAcceptorCompID), true), "Failed to remove active session"); + Assert.IsTrue(WaitForDisconnect(socket01), "Socket still connected after session removed"); + Assert.IsFalse(IsLoggedOn(StaticAcceptorCompID), "Session still logged on after being removed"); + } + + [Test] + public void DynamicInitiator() + { + StartListener(); + StartEngine(true); + + // Ensure we can log on statically (normally) configured initiator + Assert.IsTrue(WaitForLogonMessage(StaticInitiatorCompID), "Failed to get logon message for static initiator session"); + SendInitiatorLogon(StaticInitiatorCompID); + + // Add the dynamic initator and ensure that we can log on + string dynamicCompID = "ini10"; + var sessionID = CreateSessionID(dynamicCompID); + Assert.IsTrue(_initiator.AddSession(sessionID, CreateSessionConfig(dynamicCompID, true)), "Failed to add dynamic session to initiator"); + Assert.IsTrue(WaitForLogonMessage(dynamicCompID), "Failed to get logon message for dynamic initiator session"); + SendInitiatorLogon(dynamicCompID); + Assert.IsTrue(WaitForLogonStatus(dynamicCompID), "Failed to logon dynamic initiator session"); + + // Now that dynamic initiator is logged on, ensure that unforced attempt to remove session fails + Assert.IsFalse(_initiator.RemoveSession(sessionID, false), "Unexpected success removing active session"); + Assert.IsTrue(IsLoggedOn(dynamicCompID), "Unexpected logoff"); + + // Ensure that forced attempt to remove session dynamic sesison succeeds, even though it is in logged on state + Assert.IsTrue(_initiator.RemoveSession(sessionID, true), "Failed to remove active session"); + Assert.IsTrue(WaitForDisconnect(dynamicCompID), "Socket still connected after session removed"); + Assert.IsFalse(IsLoggedOn(dynamicCompID), "Session still logged on after being removed"); + + // Ensure that we can perform unforced removal of a dynamic sesion that is not logged on. + string dynamicCompID2 = "ini20"; + var sessionID2 = CreateSessionID(dynamicCompID2); + Assert.IsTrue(_initiator.AddSession(sessionID2, CreateSessionConfig(dynamicCompID2, true)), "Failed to add dynamic session to initiator"); + Assert.IsTrue(WaitForLogonMessage(dynamicCompID2), "Failed to get logon message for dynamic initiator session"); + Assert.IsFalse(IsLoggedOn(dynamicCompID2), "Session logged on"); + Assert.IsTrue(_initiator.RemoveSession(sessionID2, false), "Failed to remove inactive session"); + Assert.IsTrue(WaitForDisconnect(dynamicCompID2), "Socket still connected after session removed"); + + // Ensure that we can remove statically configured session + Assert.IsTrue(IsLoggedOn(StaticInitiatorCompID), "Unexpected loss of connection"); + Assert.IsTrue(_initiator.RemoveSession(CreateSessionID(StaticInitiatorCompID), true), "Failed to remove active session"); + Assert.IsTrue(WaitForDisconnect(StaticInitiatorCompID), "Socket still connected after session removed"); + Assert.IsFalse(IsLoggedOn(StaticInitiatorCompID), "Session still logged on after being removed"); + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index f664c6e66..cc1610546 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -98,6 +98,7 @@ + From aaacb0419f85cdb6f3193dc30bf667a5c14b10b4 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 19 May 2015 19:25:59 +0100 Subject: [PATCH 11/20] Revert "Merge branch 'rx_intercept' into dynamic_sessions" This reverts commit 446d42e2a8d1f057d6a33bca36faf336a04bacdf, reversing changes made to 469392fd7a6105c1612607940a16c58b7fe3371a. Unintended merge --- QuickFIXn/IApplicationExt.cs | 20 ---------- QuickFIXn/QuickFix.csproj | 3 +- QuickFIXn/Session.cs | 5 --- UnitTests/SessionTest.cs | 74 +----------------------------------- 4 files changed, 2 insertions(+), 100 deletions(-) delete mode 100644 QuickFIXn/IApplicationExt.cs diff --git a/QuickFIXn/IApplicationExt.cs b/QuickFIXn/IApplicationExt.cs deleted file mode 100644 index 35d34d484..000000000 --- a/QuickFIXn/IApplicationExt.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace QuickFix -{ - /// - /// This is the optional extension interface for processing session messages, and facilitates early interception of inbound messages. - /// 'Early', in this context, means after structure, length and checksum have been validated, but before any further validation has been performed. - /// - public interface IApplicationExt : IApplication - { - /// - /// This callback provides early notification of when an administrative or application message is sent from a counterparty to your FIX engine. - /// This can be useful for doing pre-processing of an inbound message after its structure, checksum and length have been validated, but before - /// any further validation has been performed on it. - /// - /// received message - /// session on which message received - void FromEarlyIntercept(Message message, SessionID sessionID); - } -} diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index 90f45af6a..c0ef8831b 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -67,7 +67,6 @@ - @@ -142,4 +141,4 @@ --> - \ No newline at end of file + diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 0952b401d..704723cf9 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -22,7 +22,6 @@ public class Session : IDisposable private SessionSchedule schedule_; private SessionState state_; private IMessageFactory msgFactory_; - private bool appDoesEarlyIntercept_; private static readonly HashSet AdminMsgTypes = new HashSet() { "0", "A", "1", "2", "3", "4", "5" }; #endregion @@ -212,7 +211,6 @@ public Session( this.DataDictionaryProvider = new DataDictionaryProvider(dataDictProvider); this.schedule_ = sessionSchedule; this.msgFactory_ = msgFactory; - appDoesEarlyIntercept_ = app is IApplicationExt; this.SenderDefaultApplVerID = senderDefaultApplVerID; @@ -549,9 +547,6 @@ public void Next(Message message) return; } - if (appDoesEarlyIntercept_) - ((IApplicationExt)Application).FromEarlyIntercept(message, this.SessionID); - if (IsNewSession) state_.Reset("New session (detected in Next(Message))"); diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index b88cc143d..328b684cf 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -112,49 +112,8 @@ public void OnLogout(QuickFix.SessionID sessionID) public void OnLogon(QuickFix.SessionID sessionID) { } - #endregion - } - - class MockApplicationExt : QuickFix.IApplicationExt - { - public HashSet InterceptedMessageTypes = new HashSet(); - - #region Application Members - - public void ToAdmin(QuickFix.Message message, QuickFix.SessionID sessionID) - { - } - - public void FromAdmin(QuickFix.Message message, QuickFix.SessionID sessionID) - { - } - - public void ToApp(QuickFix.Message message, QuickFix.SessionID sessionId) - { - } - - public void FromApp(QuickFix.Message message, QuickFix.SessionID sessionID) - { - } - - public void OnCreate(QuickFix.SessionID sessionID) - { - } - - public void OnLogout(QuickFix.SessionID sessionID) - { - } - public void OnLogon(QuickFix.SessionID sessionID) - { - } - - public void FromEarlyIntercept(QuickFix.Message message, QuickFix.SessionID sessionID) - { - InterceptedMessageTypes.Add(message.Header.GetString(QuickFix.Fields.Tags.MsgType)); - } #endregion - } [TestFixture] @@ -167,7 +126,6 @@ public class SessionTest MockApplication application = null; QuickFix.Session session = null; QuickFix.Session session2 = null; - QuickFix.Dictionary config = null; int seqNum = 1; Regex msRegex = new Regex(@"\.[\d]{1,3}$"); @@ -179,7 +137,7 @@ public void setup() application = new MockApplication(); settings = new QuickFix.SessionSettings(); - config = new QuickFix.Dictionary(); + QuickFix.Dictionary config = new QuickFix.Dictionary(); config.SetBool(QuickFix.SessionSettings.PERSIST_MESSAGES, false); config.SetString(QuickFix.SessionSettings.CONNECTION_TYPE, "initiator"); config.SetString(QuickFix.SessionSettings.START_TIME, "00:00:00"); @@ -761,35 +719,5 @@ public void TestToAppResendDoNotSend() SendResendRequest(1, 0); Assert.False(SENT_NOS()); } - - - [Test] - public void TestApplicationExtension() - { - var mockApp = new MockApplicationExt(); - session = new QuickFix.Session(mockApp, new QuickFix.MemoryStoreFactory(), sessionID, - new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, new QuickFix.ScreenLogFactory(settings), new QuickFix.DefaultMessageFactory(), "blah"); - session.SetResponder(responder); - session.CheckLatency = false; - - Logon(); - QuickFix.FIX42.NewOrderSingle order = new QuickFix.FIX42.NewOrderSingle( - new QuickFix.Fields.ClOrdID("1"), - new QuickFix.Fields.HandlInst(QuickFix.Fields.HandlInst.MANUAL_ORDER), - new QuickFix.Fields.Symbol("IBM"), - new QuickFix.Fields.Side(QuickFix.Fields.Side.BUY), - new QuickFix.Fields.TransactTime(), - new QuickFix.Fields.OrdType(QuickFix.Fields.OrdType.LIMIT)); - - order.Header.SetField(new QuickFix.Fields.TargetCompID(sessionID.SenderCompID)); - order.Header.SetField(new QuickFix.Fields.SenderCompID(sessionID.TargetCompID)); - order.Header.SetField(new QuickFix.Fields.MsgSeqNum(2)); - - session.Next(order); - - Assert.That(mockApp.InterceptedMessageTypes.Count, Is.EqualTo(2)); - Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.LOGON)); - Assert.True(mockApp.InterceptedMessageTypes.Contains(QuickFix.Fields.MsgType.NEWORDERSINGLE)); - } } } From 43b73f6e0ee7da349aa04546680773a005c4895c Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 19 May 2015 19:55:31 +0100 Subject: [PATCH 12/20] Dynamic sessions: whitespace fix only --- QuickFIXn/AbstractInitiator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index f58e74836..b325162f4 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -12,7 +12,6 @@ public abstract class AbstractInitiator : IInitiator private ILogFactory _logFactory = null; private IMessageFactory _msgFactory = null; - private object sync_ = new object(); private bool _disposed = false; private Dictionary sessions_ = new Dictionary(); From fdfe74864bbb14836e93d867426814f4eb497baa Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Mon, 8 Jun 2015 15:02:45 -0500 Subject: [PATCH 13/20] (#80) AT: logon with reset after disconnect --- AcceptanceTest/cfg/.gitignore | 1 - AcceptanceTest/cfg/at_44_noreset.cfg | 15 +++++++ .../SessionResetAfterDisconnect.def | 40 +++++++++++++++++++ QuickFIXn/Session.cs | 8 ++-- acceptance_test.bat | 14 ++++++- 5 files changed, 71 insertions(+), 7 deletions(-) delete mode 100644 AcceptanceTest/cfg/.gitignore create mode 100644 AcceptanceTest/cfg/at_44_noreset.cfg create mode 100644 AcceptanceTest/definitions/server/fix44noreset/SessionResetAfterDisconnect.def diff --git a/AcceptanceTest/cfg/.gitignore b/AcceptanceTest/cfg/.gitignore deleted file mode 100644 index 7103328ab..000000000 --- a/AcceptanceTest/cfg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cfg diff --git a/AcceptanceTest/cfg/at_44_noreset.cfg b/AcceptanceTest/cfg/at_44_noreset.cfg new file mode 100644 index 000000000..ca2bab43f --- /dev/null +++ b/AcceptanceTest/cfg/at_44_noreset.cfg @@ -0,0 +1,15 @@ +[DEFAULT] +Verbose=Y +FileLogPath=log +ConnectionType=acceptor +SocketAcceptPort=5005 +SocketReuseAddress=Y +StartTime=00:00:00 +EndTime=00:00:00 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=N +FileStorePath=store +[SESSION] +BeginString=FIX.4.4 +DataDictionary=..\spec\fix\FIX44.xml diff --git a/AcceptanceTest/definitions/server/fix44noreset/SessionResetAfterDisconnect.def b/AcceptanceTest/definitions/server/fix44noreset/SessionResetAfterDisconnect.def new file mode 100644 index 000000000..6b22775ab --- /dev/null +++ b/AcceptanceTest/definitions/server/fix44noreset/SessionResetAfterDisconnect.def @@ -0,0 +1,40 @@ +# Issue #80: logon with reset _after_disconnect_ + +iCONNECT +# logon with reset, because this config has ResetOnLogon=N +I8=FIX.4.435=A34=149=TW52= /// ID of new session /// config settings for new session - /// true if session added succesfully, false if session already exists or is of wrong type + /// true if session added successfully, false if session already exists or is not an initiator public bool AddSession(SessionID sessionID, Dictionary dict) { if (dict.GetString(SessionSettings.CONNECTION_TYPE) == "initiator" && !sessionIDs_.Contains(sessionID)) @@ -102,11 +102,11 @@ public bool AddSession(SessionID sessionID, Dictionary dict) } /// - /// Ad-hoc removal of an existing sssion + /// Ad-hoc removal of an existing session /// /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) { Session session = null; @@ -130,7 +130,7 @@ public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) } } if (disconnectRequired) - session.Disconnect("Removed dynamically"); + session.Disconnect("Dynamic session removal"); if (session != null) session.Dispose(); return true; @@ -233,9 +233,10 @@ protected virtual void OnConfigure(SessionSettings settings) { } /// - /// Override this to handle ad-hoc session removal + /// Implement this to provide custom reaction behavior to an ad-hoc session removal. + /// (This is called after the session is removed.) /// - /// ID of session being remvoed + /// ID of session that was removed protected virtual void OnRemove(SessionID sessionID) { } diff --git a/QuickFIXn/IAcceptor.cs b/QuickFIXn/IAcceptor.cs index 3d7989345..ac06236a0 100644 --- a/QuickFIXn/IAcceptor.cs +++ b/QuickFIXn/IAcceptor.cs @@ -47,15 +47,15 @@ public interface IAcceptor /// /// ID of session to be added /// session settings - /// >true if session added succesfully, false if session already exists or is of wrong type + /// >true if session added successfully, false if session already exists or is not an acceptor bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); /// /// Remove an existing session after acceptor has been started /// /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } diff --git a/QuickFIXn/IInitiator.cs b/QuickFIXn/IInitiator.cs index 8d2b6d3f8..6747b1569 100644 --- a/QuickFIXn/IInitiator.cs +++ b/QuickFIXn/IInitiator.cs @@ -47,15 +47,15 @@ public interface IInitiator : IDisposable /// /// ID of session to be added /// session settings - /// >true if session added succesfully, false if session already exists or is of wrong type + /// true if session added successfully, false if session already exists or is not an initiator bool AddSession(SessionID sessionID, QuickFix.Dictionary dict); /// /// Remove an existing session after initiator has been started /// /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection bool RemoveSession(SessionID sessionID, bool terminateActiveSession); } diff --git a/QuickFIXn/SessionSettings.cs b/QuickFIXn/SessionSettings.cs index a4d2bcc84..9c52fd03b 100755 --- a/QuickFIXn/SessionSettings.cs +++ b/QuickFIXn/SessionSettings.cs @@ -182,7 +182,7 @@ public void Set(QuickFix.Dictionary defaults) /// /// Remove existing session config from the settings /// - /// ID of session for which config to be removed + /// ID of session for which config is to be removed /// true if removed, false if config for the session does not exist public bool Remove(SessionID sessionID) { diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index cd4a73ac3..c76329991 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -150,7 +150,7 @@ private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(Dictionary dict) /// /// ID of new session /// config settings for new session - /// true if session added succesfully, false if session already exists or is of wrong type + /// true if session added successfully, false if session already exists or is not an acceptor public bool AddSession(SessionID sessionID, Dictionary dict) { if (!sessions_.ContainsKey(sessionID)) @@ -169,11 +169,11 @@ public bool AddSession(SessionID sessionID, Dictionary dict) } /// - /// Ad-hoc removal of an existing sssion + /// Ad-hoc removal of an existing session /// /// ID of session to be removed - /// true if sesion to be removed even if it has an active connection - /// true if session removed or was already not present, false if could not be removed because of active connection + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) { Session session = null; @@ -181,7 +181,7 @@ public bool RemoveSession(SessionID sessionID, bool terminateActiveSession) { if (session.IsLoggedOn && !terminateActiveSession) return false; - session.Disconnect("Disabled via dynamic config update"); + session.Disconnect("Dynamic session removal"); foreach (AcceptorSocketDescriptor descriptor in socketDescriptorForAddress_.Values) if (descriptor.RemoveSession(sessionID)) break; diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index de8b7c1ea..030c17fcd 100755 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -169,7 +169,7 @@ protected override void OnStart() /// /// Ad-hoc session removal /// - /// ID of session being remvoed + /// ID of session being removed protected override void OnRemove(SessionID sessionID) { sessionToHostNum_.Remove(sessionID); diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs index ff938a4b5..3f3f01e6f 100644 --- a/UnitTests/SessionDynamicTest.cs +++ b/UnitTests/SessionDynamicTest.cs @@ -330,7 +330,7 @@ public void DynamicAcceptor() string dynamicCompID = "acc10"; SendLogon(socket02, dynamicCompID); Assert.IsTrue(WaitForDisconnect(socket02), "Server failed to disconnect unconfigured CompID"); - Assert.False(HasReceivedMessage(dynamicCompID), "Unexpected messaage received for unconfigured CompID"); + Assert.False(HasReceivedMessage(dynamicCompID), "Unexpected message received for unconfigured CompID"); // Add the dynamic acceptor and ensure that we can now log on var sessionID = CreateSessionID(dynamicCompID); @@ -343,12 +343,12 @@ public void DynamicAcceptor() Assert.IsFalse(_acceptor.RemoveSession(sessionID, false), "Unexpected success removing active session"); Assert.IsTrue(socket03.Connected, "Unexpected loss of connection"); - // Ensure that forced attempt to remove session dynamic sesison succeeds, even though it is in logged on state + // Ensure that forced attempt to remove session dynamic session succeeds, even though it is in logged on state Assert.IsTrue(_acceptor.RemoveSession(sessionID, true), "Failed to remove active session"); Assert.IsTrue(WaitForDisconnect(socket03), "Socket still connected after session removed"); Assert.IsFalse(IsLoggedOn(dynamicCompID), "Session still logged on after being removed"); - // Ensure that we can perform unforced removal of a dynamic sesion that is not logged on. + // Ensure that we can perform unforced removal of a dynamic session that is not logged on. string dynamicCompID2 = "acc20"; var sessionID2 = CreateSessionID(dynamicCompID2); Assert.IsTrue(_acceptor.AddSession(sessionID2, CreateSessionConfig(dynamicCompID2, false)), "Failed to add dynamic session to acceptor"); @@ -383,12 +383,12 @@ public void DynamicInitiator() Assert.IsFalse(_initiator.RemoveSession(sessionID, false), "Unexpected success removing active session"); Assert.IsTrue(IsLoggedOn(dynamicCompID), "Unexpected logoff"); - // Ensure that forced attempt to remove session dynamic sesison succeeds, even though it is in logged on state + // Ensure that forced attempt to remove session dynamic session succeeds, even though it is in logged on state Assert.IsTrue(_initiator.RemoveSession(sessionID, true), "Failed to remove active session"); Assert.IsTrue(WaitForDisconnect(dynamicCompID), "Socket still connected after session removed"); Assert.IsFalse(IsLoggedOn(dynamicCompID), "Session still logged on after being removed"); - // Ensure that we can perform unforced removal of a dynamic sesion that is not logged on. + // Ensure that we can perform unforced removal of a dynamic session that is not logged on. string dynamicCompID2 = "ini20"; var sessionID2 = CreateSessionID(dynamicCompID2); Assert.IsTrue(_initiator.AddSession(sessionID2, CreateSessionConfig(dynamicCompID2, true)), "Failed to add dynamic session to initiator"); From a8153fe91c03663dbae97939c30a5d0308dbdfdd Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Wed, 10 Jun 2015 16:06:36 -0500 Subject: [PATCH 20/20] attribution for #314 --- NEXT_VERSION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_VERSION.md b/NEXT_VERSION.md index a163a3c78..919f7471d 100644 --- a/NEXT_VERSION.md +++ b/NEXT_VERSION.md @@ -30,4 +30,5 @@ Changes since the last version (oldest first): * (patch) #297 - revert #287 * (patch) #290 - support for RefreshOnLogon (martinadams) * (patch) #80 - fixes to tag-141-related sequence resets (TomasVetrovsky,akamyshanov,gbirchmeier) +* (minor) #314 - New feature: add/remove sessions dynamically (martinadams)