internal async Task <NotHttpConnection> GetRequestConnection(NHClientRequest caller) { NHClientEndpoint endpoint; Task <NotHttpConnection> blockTask; lock (this) { Uri url = caller.Url; Dictionary <int, NHClientEndpoint> hostPorts; if (!connections.TryGetValue(url.Host, out hostPorts)) { hostPorts = new Dictionary <int, NHClientEndpoint>(); connections.Add(url.Host, hostPorts); } if (!hostPorts.TryGetValue(url.Port, out endpoint)) { endpoint = new NHClientEndpoint(url.Host, url.Port, isOverlapped, maxConnections, idleTimeout, logger); hostPorts.Add(url.Port, endpoint); } blockTask = endpoint.WaitForRequestSlot(caller); } return(await blockTask); }
public Exception AssignRequest(NHClientRequest request) { lock (this) { if (currentRequest != null) { endpoint.Logger.Log("Error: callers out of sync"); throw new ApplicationException("Callers out of sync"); } // if exception is non-null the connection is faulted, and // will be discarded by the caller when this method returns if (exception == null) { currentRequest = request; if (outstandingRequests == 0) { idleTask.SetResult(false); } ++outstandingRequests; } return(exception); } }
public void StreamFaulted(Exception e) { lock (this) { if (stream == null) { // already faulted return; } StreamFaultedInternal(e); if (!endpoint.IsOverlapped) { currentRequest = null; } } if (!endpoint.IsOverlapped) { // take ourselves out of the endpoint's list of available connections; ReportConnectionReady // checks to see if we are faulted. For an overlapped connection this will have happened in // ReportRequestCompleted already endpoint.ReportConnectionReady(this); } }
public int NextSequenceNumber(NHClientRequest caller) { lock (this) { if (caller != currentRequest) { endpoint.Logger.Log("Error: callers out of sync"); throw new ApplicationException("Callers out of sync"); } // I don't believe in foo++ syntax obfuscation :) int sn = sequenceNumber; ++sequenceNumber; return(sn); } }
public void ReportRequestCompleted(NHClientRequest caller) { lock (this) { if (caller != currentRequest) { endpoint.Logger.Log("Error: callers out of sync"); throw new ApplicationException("Callers out of sync"); } if (endpoint.IsOverlapped) { currentRequest = null; } } if (endpoint.IsOverlapped) { endpoint.ReportConnectionReady(this); } }
public async Task <NotHttpConnection> WaitForRequestSlot(NHClientRequest caller) { Task <NotHttpConnection> blockTask = null; lock (this) { // this is where we garbage collection connections that are faulted, so // go through the idle connections throwing them away if they // aren't good until we match one while (blockTask == null && idleConnections.Count > 0) { NotHttpConnection connection = idleConnections.Pop(); Exception exception = connection.AssignRequest(caller); if (exception == null) { blockTask = Task.FromResult(connection); } else { --openedConnections; logger.Log("Removing faulted connection, " + openedConnections + " remaining: " + exception.Message); } } if (blockTask == null) { RequestWaiter waiter = new RequestWaiter(caller); requestWaiters.AddLast(waiter); blockTask = waiter.task.Task; } ReOpenConnections(); } return(await blockTask); }
public RequestWaiter(NHClientRequest caller) { request = caller; task = new TaskCompletionSource <NotHttpConnection>(); }
private async Task ReadStream() { int responseSequenceNumber = -1; while (true) { Task <bool> idleBlocker = null; lock (this) { if (outstandingRequests == 0) { // add ContinueWith so the continuation is asynchronous, since await generates // a synchronous continuation idleBlocker = idleTask.Task.ContinueWith((t) => t.Result); } } if (idleBlocker != null) { bool mustExit = await idleBlocker; if (mustExit) { endpoint.Logger.Log("Reader exiting due to idle connection"); return; } } if (!endpoint.IsOverlapped) { // sequence number is 0 for the first response ++responseSequenceNumber; } // for an overlapped connection we pass in responseSequenceNumber=-1 and the actual sequence // number is read from the protocol. For a sequential connection we pass in the actual sequence // number NHClientResponse response = new NHClientResponse(this, responseSequenceNumber, endpoint.Logger); try { NotHttp.NHError error = new NotHttp.NHError(); bool gotRequest = await response.Parse(null, stream, error); if (!gotRequest) { throw new ApplicationException("Server closed connection"); } if (response.StatusCode != HttpStatusCode.OK || endpoint.IsOverlapped) { // fetch the whole response now, so we're ready to wait for the next message even if // the caller isn't ready to read this one await response.BufferInput(); } lock (this) { TaskCompletionSource <IHttpResponse> tcs; if (pendingClients.TryGetValue(response.SequenceNumber, out tcs)) { // run this asynchronously outside the lock Task abandon = Task.Run(() => ReturnResult(tcs, response)); pendingClients.Remove(response.SequenceNumber); } else { if (receivedResponses.ContainsKey(response.SequenceNumber)) { StreamFaulted(new ApplicationException("Received duplicate sequence numbers " + response.SequenceNumber)); return; } receivedResponses.Add(response.SequenceNumber, response); } } // wait until the caller has consumed all the payload data (if we buffered it above this // falls through immediately) await response.Completed; string closeHeader = response.HeadersInternal[HttpRequestHeader.Connection]; if (closeHeader != null && closeHeader.ToLower() == "close") { endpoint.Logger.Log("Passing stream connection closed header"); StreamFaulted(new ApplicationException("Connection closed by server request")); return; } lock (this) { if (!endpoint.IsOverlapped) { currentRequest = null; } --outstandingRequests; if (outstandingRequests == 0) { idleTask = new TaskCompletionSource <bool>(); // capture member variable in local scope to give to closure TaskCompletionSource <bool> currentIdleTask = idleTask; Task abandon = Task.Delay(endpoint.IdleTimeout).ContinueWith((t) => ExitOnIdle(currentIdleTask)); } } if (!endpoint.IsOverlapped) { endpoint.ReportConnectionReady(this); } } catch (Exception e) { endpoint.Logger.Log("Passing exception status " + response.StatusCode); StreamFaulted(e); return; } } }