From 4ed14c5d40e75a671336752fa72b5ab157ba1f96 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 2 Dec 2014 20:12:37 +0000 Subject: [PATCH 1/5] 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 2/5] 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 646f43d6d95eca00fcbfef2c041f59dda8dfe738 Mon Sep 17 00:00:00 2001 From: MartinAdams Date: Tue, 19 May 2015 00:23:22 +0100 Subject: [PATCH 3/5] 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 4/5] 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 5/5] 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();