/// <summary> /// Sends data via the socket asynchronously /// </summary> /// <param name="Message">Data to transmit</param> public void Send(ResponseData Message) { IBuffer sendBuffer = null; //TODO: ENHANCEMENT: Log consecutive bad request response types and use that information to disconnect socket after 3 try { lock (syncSocket) { if (sentMsgs.Count > 1) { sentMsgs.Dequeue(); } sentMsgs.Enqueue(Message); //Log error if .Data is null -- this will help raise a flag if the message is being resent after .ClearData was called Diags.Assert(Message.Data != null, "ASSERTION FAILED: Message Data is null", new System.Diagnostics.StackTrace().ToString()); sendBuffer = bufferPool.GetBuffer(Message.Data.LongLength); sendBuffer.CopyFrom(Message.Data); socket.BeginSend(sendBuffer.GetSegments(), SocketFlags.None, CompleteSend, sendBuffer); Message.ClearData(); //free some memory } } catch (Exception ex) { Diags.LogSocketException(ex); if (sendBuffer != null) { sendBuffer.Dispose(); sendBuffer = null; } } }
/// <summary> /// Completes an asynchronous send /// </summary> /// <param name="ar">AsyncResul obtained from BeginSend</param> private void CompleteSend(IAsyncResult ar) { // Complete asynchronous send IBuffer sendBuffer = (IBuffer)ar.AsyncState; try { if (!socket.Connected) { return; } lock (syncSocket) { socket.EndSend(ar); } sendBuffer.Dispose(); sendBuffer = null; } catch (Exception ex) { Diags.LogSocketException(ex); if (sendBuffer != null) { sendBuffer.Dispose(); sendBuffer = null; } } }
/// <summary> /// Closes the socket gracefully /// </summary> public void Close() { //TODO: ENHANCEMENT: Seems like the graceful shutdown process in this method is not working well //Is it because of the 1 ms timeout? //I see a lot more stale Peer connections than stale web server connections, so it looks like //Thw web server connections are closing better than the peer connections. Investigate this. try { lock (syncSocket) { if (socket.Connected) { socket.Shutdown(SocketShutdown.Both); } isClosing = true; socket.Close(1); } if (recvBuffer != null) { recvBuffer.Dispose(); } } catch (Exception ex) { Diags.LogSocketException(ex); } }
/// <summary> /// Begins an asynchronous receive /// </summary> /// <param name="Buffer">Buffer to store received data</param> /// <param name="ReadCallBack">Method to call on receiving data</param> /// <param name="StateObject">State object to be passed to ReadCallBack</param> /// <returns>AsyncResult for the asynchronous operation</returns> public IAsyncResult BeginReceive(int BufferLength, AsyncCallback ReadCallBack, object StateObject) { try { if (recvBuffer == null || recvBuffer.IsDisposed) { recvBuffer = bufferPool.GetBuffer(BufferLength); } else if (recvBuffer.Size < BufferLength) { recvBuffer.Dispose(); recvBuffer = bufferPool.GetBuffer(BufferLength); } lock (syncSocket) { return(socket.BeginReceive(recvBuffer.GetSegments(), SocketFlags.None, ReadCallBack, StateObject)); } } catch (Exception ex) { Diags.LogSocketException(ex); return(null); } }
/// <summary> /// Ends an asynchronous Receive /// </summary> /// <param name="ar">AsyncResult obtained from BeginReive</param> /// <param name="Error">Indicates an error occured while receiving data</param> /// <returns>Received Data</returns> public byte[] EndReceive(IAsyncResult ar, out bool Error) { Error = false; int bytesRead = 0; try { lock (syncSocket) { bytesRead = socket.EndReceive(ar); } } catch (ObjectDisposedException ex) { if (!isClosing) { Diags.LogSocketException(ex); } Error = true; } catch (Exception ex) { Diags.LogSocketException(ex); Error = true; } byte[] readData; if (Error || bytesRead < 0) { readData = new byte[0]; } else { readData = new byte[bytesRead]; } if (recvBuffer != null && !recvBuffer.IsDisposed) { if (!Error && bytesRead > 0) { recvBuffer.CopyTo(readData, 0, bytesRead); } //Dispose buffer if it's greater than a specified threshold if (recvBuffer.Size > BufferRenewalSizeThreshold) { recvBuffer.Dispose(); } } return(readData); }
/// <summary> /// Ends an asynchronous Connect /// </summary> /// <param name="ar">AsyncResult obtained from BeginConnect</param> public void EndConnect(IAsyncResult ar) { try { lock (syncSocket) { isOutbound = true; socket.EndConnect(ar); } } catch (Exception ex) { Diags.LogSocketException(ex); } }
/// <summary> /// Begins an asynchronous Connect /// </summary> /// <param name="Host">Host to connect to</param> /// <param name="Port">Port number to connect to</param> /// <param name="ConnectCallBack">Callback to call on connecting</param> /// <param name="StateObject">State object to pass to ConnectCallback</param> /// <returns>AsyncResult for the asynchronous operation</returns> public IAsyncResult BeginConnect(string Host, int Port, AsyncCallback ConnectCallBack, object StateObject) { try { lock (syncSocket) { return(socket.BeginConnect(Host, Port, ConnectCallBack, StateObject)); } } catch (Exception ex) { Diags.LogSocketException(ex); return(null); } }
/// <summary> /// Listens on a specified port on the machine /// </summary> /// <param name="Port">Port number</param> /// <param name="AcceptCallback">Callback for accepting new connections</param> /// <returns>.NET Socket if successful, Null if not</returns> public static Socket Listen(int Port, AsyncCallback AcceptCallback) { Socket listener; // Start Listening on Web Server Socket IPEndPoint wsEndPoint = new IPEndPoint(IPAddress.Any, Port); listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { listener.Bind(wsEndPoint); listener.Listen(MaxConnections); listener.BeginAccept(AcceptCallback, listener); } catch (Exception ex) { Diags.LogSocketException(ex); return(null); } return(listener); }
/// <summary> /// Terminates a connection /// </summary> public void Abort() { try { lock (syncSocket) { if (socket.Connected) { socket.Shutdown(SocketShutdown.Send); } isClosing = true; socket.Close(); } if (recvBuffer != null) { recvBuffer.Dispose(); } } catch (Exception ex) { Diags.LogSocketException(ex); } }
/// <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); }
/// <summary> /// Ends an external session export /// </summary> /// <param name="Key">Session key</param> /// <param name="RemoveSession">True to remove session from dictionary</param> public void EndExport(string Key, bool RemoveSession) { //This method resets the inuse property if the isExporting property is true if (Key == null) { throw new ArgumentNullException("Key"); } AcquireReadLock(); ISessionObject entry; try { dict.TryGetValue(Key, out entry); } finally { ReleaseReadLock(); } if (entry == null) { //Session not found -- it's okay, don't freak out, session may have expired. return; } else { //Session Found if (entry.IsInUse) { //The InUse flag, now check the isExporting flag if (!entry.IsExporting) { Exception ex = new InvalidOperationException("EndExport must be called after a call to BeginExport"); Diags.LogApplicationError("EndExport must be called after a call to BeginExport -- Entry is InUse but IsExporting is false", ex); throw ex; } try { //Delete session if (RemoveSession) { AcquireWriteLock(); try { if (dict.Remove(Key)) { expiryList.Remove(Key); Diags.LogSessionDeleted(Key); } } finally { ReleaseWriteLock(); } } } finally { entry.IsExporting = false; Diags.LogSessionExported(Key); entry.CompareExchangeIsInUse(false, true); } } else { Exception ex = new InvalidOperationException("EndExport must be called after a call to BeginExport"); Diags.LogApplicationError("EndExport must be called after a call to BeginExport -- Entry is not in use", ex); throw ex; } } }
/// <summary> /// Reads a stored session /// </summary> /// <param name="Key">Session Key</param> /// <param name="Reader">Method to call to complete read</param> /// <param name="StateObject">State object</param> /// <param name="isExporting">Indicates if the session is to be exported</param> /// <returns>Result of read action</returns> private SessionActionResult Read(string Key, SessionReadHandler Reader, object StateObject, bool isExporting) { if (Key == null) { throw new ArgumentNullException("Key"); } bool tryAgain; bool sessionIslocked = false; Diags.ResetDeadLockCounter(); //Reset Dead lock counter do { tryAgain = false; AcquireReadLock(); ISessionObject entry; try { dict.TryGetValue(Key, out entry); } finally { ReleaseReadLock(); } if (entry == null) { //Session not found Diags.LogSessionNotFound(Key); return(SessionActionResult.NotFound); } else { //Session Found if (entry.CompareExchangeIsInUse(true, false) == false) { //The InUse flag has been set and now this thread has exclusive access to this session object try { //Set IsExporting flag for this entry if item is to be exported if (isExporting) { entry.IsExporting = true; } //Call Reader Delegate if (Reader != null) { Reader(entry, StateObject); } if (isExporting) { Diags.LogSessionExporting(Key, entry); } else { Diags.LogSessionRead(Key, entry); } sessionIslocked = entry.IsLocked; } finally { if (!isExporting) //Remove inUse property if not exporting { entry.CompareExchangeIsInUse(false, true); } } } else { //Nope, it's still there so check if it's been exported and try again if (entry.IsExporting) { //This session is already been exported so leave Diags.ResetDeadLockCounter(); return(SessionActionResult.Exporting); } 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 if (sessionIslocked && !isExporting) { Diags.LogSessionIsLocked(Key); return(SessionActionResult.Locked); } else { return(SessionActionResult.OK); } }
/// <summary> /// Removes a session from the dictionary /// </summary> /// <param name="Key">Session Key</param> /// <param name="LockCookie">Lock Cookie</param> /// <param name="IsExpiring">Indicates that the item is being removed because it's expiring</param> /// <param name="ExpiryDate">The Item expiry date (for comparison)</param> /// <param name="LockedSessionInfo">Locked session information if session is locked</param> /// <returns>Result of Action</returns> private SessionActionResult Remove(string Key, uint LockCookie, bool IsExpiring, DateTime ExpiryDate, out SessionResponseInfo LockedSessionInfo) { if (Key == null) { throw new ArgumentNullException("Key"); } LockedSessionInfo = null; bool tryAgain; Diags.ResetDeadLockCounter(); do { tryAgain = false; AcquireReadLock(); ISessionObject entry; try { dict.TryGetValue(Key, out entry); } finally { ReleaseReadLock(); } if (entry == null) { //Session not found Diags.LogSessionNotFound(Key); return(SessionActionResult.NotFound); } else { //Session Found if (entry.CompareExchangeIsInUse(true, false) == false) { try { //The InUse flag is set and so this code section has exclusive access to this session object AcquireWriteLock(); try { //Check again to be sure, now that the write-lock has been obtained ISessionObject oldEntry = entry; if (!dict.TryGetValue(Key, out entry)) { //ooops -- another thread deleted the session from the dictionary while this thread //was either trying to do the compareExchange (or if buggy, while obtaining the write-lock) //so try again oldEntry.CompareExchangeIsInUse(false, true); //unlock the previously locked item tryAgain = true; continue; } if (IsExpiring) { DateTime timeStamp; if (expiryList.TryGetTimeStamp(Key, out timeStamp)) { if (timeStamp != ExpiryDate) { //The expiration date on this session was updated, so leave return(SessionActionResult.OK); } } } if (!IsExpiring && entry.IsLocked) //Locked items DO expire. if not expiring, LockCookie has to match session's { if (!entry.UnLock(LockCookie)) { //Lockcookie did not match LockedSessionInfo = (SessionResponseInfo)entry.CreateResponseInfo(); Diags.LogSessionIsLocked(Key); return(SessionActionResult.Locked); } } if (dict.Remove(Key)) { expiryList.Remove(Key); if (IsExpiring) { Diags.LogSessionExpired(Key); } else { Diags.LogSessionDeleted(Key); } } else { //This should never happen Diags.Fail("ASSERTION Failed -- Session dictionary was unable to remove key\r\n"); } } finally { ReleaseWriteLock(); } } finally { if (entry != null) { 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); }