/// <summary> /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function. /// </summary> public static void OnUpdate() { // We will try to acquire a lock. If it fails, we will skip this frame without calling any callback. if (System.Threading.Monitor.TryEnter(Locker)) { try { IsCallingCallbacks = true; try { for (int i = 0; i < ActiveConnections.Count; ++i) { ConnectionBase conn = ActiveConnections[i]; switch (conn.State) { case HTTPConnectionStates.Processing: conn.HandleProgressCallback(); if (conn.CurrentRequest.UseStreaming && conn.CurrentRequest.Response != null && conn.CurrentRequest.Response.HasStreamedFragments()) { conn.HandleCallback(); } try { if (((!conn.CurrentRequest.UseStreaming && conn.CurrentRequest.UploadStream == null) || conn.CurrentRequest.EnableTimoutForStreaming) && DateTime.UtcNow - conn.StartTime > conn.CurrentRequest.Timeout) { conn.Abort(HTTPConnectionStates.TimedOut); } } catch (OverflowException) { HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow"); } break; case HTTPConnectionStates.TimedOut: // The connection is still in TimedOut state, and if we waited enough time, we will dispatch the // callback and recycle the connection try { if (DateTime.UtcNow - conn.TimedOutStart > TimeSpan.FromMilliseconds(500)) { HTTPManager.Logger.Information("HTTPManager", "Hard aborting connection because of a long waiting TimedOut state"); conn.CurrentRequest.Response = null; conn.CurrentRequest.State = HTTPRequestStates.TimedOut; conn.HandleCallback(); // this will set the connection's CurrentRequest to null RecycleConnection(conn); } } catch (OverflowException) { HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow"); } break; case HTTPConnectionStates.Redirected: // If the server redirected us, we need to find or create a connection to the new server and send out the request again. SendRequest(conn.CurrentRequest); RecycleConnection(conn); break; case HTTPConnectionStates.WaitForRecycle: // If it's a streamed request, it's finished now conn.CurrentRequest.FinishStreaming(); // Call the callback conn.HandleCallback(); // Then recycle the connection RecycleConnection(conn); break; case HTTPConnectionStates.Upgraded: // The connection upgraded to an other protocol conn.HandleCallback(); break; case HTTPConnectionStates.WaitForProtocolShutdown: var ws = conn.CurrentRequest.Response as IProtocol; if (ws != null) { ws.HandleEvents(); } if (ws == null || ws.IsClosed) { conn.HandleCallback(); // After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and MUST close the underlying TCP connection. conn.Dispose(); RecycleConnection(conn); } break; case HTTPConnectionStates.AbortRequested: // Corner case: we aborted a WebSocket connection { ws = conn.CurrentRequest.Response as IProtocol; if (ws != null) { ws.HandleEvents(); if (ws.IsClosed) { conn.HandleCallback(); conn.Dispose(); RecycleConnection(conn); } } } if (conn.CurrentRequest != null) { // Still process any callbacks. goto case HTTPConnectionStates.Processing; } else { break; } case HTTPConnectionStates.Closed: // If it's a streamed request, it's finished now conn.CurrentRequest.FinishStreaming(); // Call the callback conn.HandleCallback(); // It will remove from the ActiveConnections RecycleConnection(conn); break; case HTTPConnectionStates.Free: RecycleConnection(conn); break; } } } finally { IsCallingCallbacks = false; } // Just try to grab the lock, if we can't have it we can wait another turn because it isn't // critical to do it right now. if (System.Threading.Monitor.TryEnter(RecycledConnections)) { try { if (RecycledConnections.Count > 0) { for (int i = 0; i < RecycledConnections.Count; ++i) { var connection = RecycledConnections[i]; // If in a callback made a request that acquired this connection, then we will not remove it from the // active connections. if (connection.IsFree) { ActiveConnections.Remove(connection); FreeConnections.Add(connection); } } RecycledConnections.Clear(); } } finally { System.Threading.Monitor.Exit(RecycledConnections); } } if (FreeConnections.Count > 0) { for (int i = 0; i < FreeConnections.Count; i++) { var connection = FreeConnections[i]; if (connection.IsRemovable) { // Remove the connection from the connection reference table List <ConnectionBase> connections = null; if (Connections.TryGetValue(connection.ServerAddress, out connections)) { connections.Remove(connection); } // Dispose the connection connection.Dispose(); FreeConnections.RemoveAt(i); i--; } } } if (CanProcessFromQueue()) { // Sort the queue by priority, only if we have to if (RequestQueue.Find((req) => req.Priority != 0) != null) { RequestQueue.Sort((req1, req2) => req1.Priority - req2.Priority); } // Create an array from the queue and clear it. When we call the SendRequest while still no room for new connections, the same queue will be rebuilt. var queue = RequestQueue.ToArray(); RequestQueue.Clear(); for (int i = 0; i < queue.Length; ++i) { SendRequest(queue[i]); } } } finally { System.Threading.Monitor.Exit(Locker); } } if (heartbeats != null) { heartbeats.Update(); } VariableSizedBufferPool.Maintain(); }