/// <summary> /// Updates or inserts a session in the dictionary /// </summary> /// <param name="Key">Session Key</param> /// <param name="Session">Session object</param> /// <param name="InsertOnly">Indicates that session should only be inserted if it does not already exist</param> /// <param name="UpdateIfNotFound">Indicates whether session should be updated if the session was not found. If set to flase, this gives the caller a chance to query the network before trying again </param> /// <param name="LockedSessionInfo">Locked session information if session is locked</param> /// <returns>Result of Action</returns> private SessionActionResult UpSert(string Key, ISessionObject Session, bool InsertOnly, bool UpdateIfNotFound, out SessionResponseInfo LockedSessionInfo) { // Look for the session using a reader lock. // If session is not found, switch to a writer lock and insert item. // If session is found: // Perform an atomic compare exchange on the variable 'InUse' // If session is in Use, try Upsert again from the start. // If session is not in Use, Perform UpSert and reset InUse // Also update Sorted session list if (Key == null) { throw new ArgumentNullException("Key"); } if (Session == null) { throw new ArgumentNullException("Session"); } LockedSessionInfo = null; bool tryAgain; Diags.ResetDeadLockCounter(); do { tryAgain = false; AcquireReadLock(); ISessionObject entry; try { dict.TryGetValue(Key, out entry); } finally { ReleaseReadLock(); } if (entry == null) { if (!UpdateIfNotFound) { return(SessionActionResult.NotFound); } //Session not found -- insert brand new session object AcquireWriteLock(); try { //Check again to be sure now that the write-lock has been obtained dict.TryGetValue(Key, out entry); if (entry != null) { //ooops -- another thread inserted a seesion with this key while this thread was trying to obtain the write-lock //so try again tryAgain = true; continue; } Session.LockCookie = 1; //For some reason Lockcookie starts counting from 2 -- so set it to 1 now so that it increments to 2 when sought dict[Key] = Session; expiryList.Add(DateTime.UtcNow.Add(new TimeSpan(0, Session.TimeOut, 0)), Key, Key); Diags.LogNewSession(Key, Session); } finally { ReleaseWriteLock(); } } else { //Session Found if (InsertOnly) { Diags.LogSessionAlreadyExists(Key); return(SessionActionResult.AlreadyExists); //Do not perform an update if InsertOnly is requested } //There is no need to acquire a write lock here since the dictionary is not been modified. //Only the dictionary entry itself is been updated and such updates are guaranteed to be atomic //if the atomic InUse property is set. if (entry.CompareExchangeIsInUse(true, false) == false) { //the InUse flag is set, so this code section has exclusive access to this session object try { if (entry.IsLocked) { if (!entry.UnLock(Session.LockCookie)) { //Lockcookie did not match LockedSessionInfo = (SessionResponseInfo)entry.CreateResponseInfo(); Diags.LogSessionIsLocked(Key); return(SessionActionResult.Locked); } } Session.LockCookie = entry.LockCookie; //Overwrite the incoming session's lock-cookie with the internal one's so as not to let external input change the lockcookie Session.ExtraFlags = -1; //disable extra flags since an update is being performed entry.CopyFrom(Session); //Copy all information from Session to entry expiryList.Add(DateTime.UtcNow.Add(new TimeSpan(0, Session.TimeOut, 0)), Key, Key); //reset expiry timeout Diags.LogUpdatedSession(Key, Session); } finally { entry.CompareExchangeIsInUse(false, true); } } else { //Is this entry being exported? if (entry.IsExporting) { //This session is already been exported so leave Diags.ResetDeadLockCounter(); return(SessionActionResult.Exporting); } //Another thread is using this session and will be done soon so try again Thread.Sleep(1); //pause for 1 ms tryAgain = true; } } Diags.DetectDeadLock(Key, DeadLockIterationCount); //Signal a deadlock after 2000 iterations } while (tryAgain); Diags.ResetDeadLockCounter(); //Signal deadlock was freed return(SessionActionResult.OK); }