/// <summary> /// Checks the completed connection objects. /// </summary> /// <returns>False if the server needs to begin shutting down</returns> private void HandleCompletedConnections() { var shutdown = false; var processedCount = 0; var i = 0; while (i < _connectionList.Count) { var current = _connectionList[i]; if (!current.IsCompleted) { i++; continue; } _connectionList.RemoveAt(i); processedCount++; var connectionData = current.Result; ChangeKeepAlive(connectionData.KeepAlive); switch (connectionData.CompletionReason) { case CompletionReason.CompilationCompleted: case CompletionReason.CompilationNotStarted: // These are all normal shutdown states. Nothing to do here. break; case CompletionReason.ClientDisconnect: // Have to assume the worst here which is user pressing Ctrl+C at the command line and // hence wanting all compilation to end. _diagnosticListener.ConnectionRudelyEnded(); shutdown = true; break; case CompletionReason.ClientException: case CompletionReason.ClientShutdownRequest: _diagnosticListener.ConnectionRudelyEnded(); shutdown = true; break; default: throw new InvalidOperationException($"Unexpected enum value {connectionData.CompletionReason}"); } } if (processedCount > 0) { _diagnosticListener.ConnectionCompleted(processedCount); } if (shutdown) { _state = State.ShuttingDown; } }
/// <summary> /// This function will accept and process new connections until an event causes /// the server to enter a passive shut down mode. For example if analyzers change /// or the keep alive timeout is hit. At which point this function will cease /// accepting new connections and wait for existing connections to complete before /// returning. /// </summary> public void ListenAndDispatchConnections(TimeSpan?keepAlive, CancellationToken cancellationToken = default(CancellationToken)) { var isKeepAliveDefault = true; var connectionList = new List <Task <ConnectionData> >(); Task gcTask = null; Task timeoutTask = null; Task <IClientConnection> listenTask = null; CancellationTokenSource listenCancellationTokenSource = null; do { // While this loop is running there should be an active named pipe listening for a // connection. if (listenTask == null) { Debug.Assert(listenCancellationTokenSource == null); Debug.Assert(timeoutTask == null); listenCancellationTokenSource = new CancellationTokenSource(); listenTask = _clientConnectionHost.CreateListenTask(listenCancellationTokenSource.Token); } // If there are no active clients running then the server needs to be in a timeout mode. if (connectionList.Count == 0 && timeoutTask == null && keepAlive.HasValue) { Debug.Assert(listenTask != null); timeoutTask = Task.Delay(keepAlive.Value); } WaitForAnyCompletion(connectionList, new[] { listenTask, timeoutTask, gcTask }, cancellationToken); // If there is a connection event that has highest priority. if (listenTask.IsCompleted && !cancellationToken.IsCancellationRequested) { _diagnosticListener.ConnectionReceived(); var connectionTask = HandleClientConnection(listenTask, cancellationToken); connectionList.Add(connectionTask); listenTask = null; listenCancellationTokenSource = null; timeoutTask = null; gcTask = null; continue; } if ((timeoutTask != null && timeoutTask.IsCompleted) || cancellationToken.IsCancellationRequested) { _diagnosticListener.KeepAliveReached(); listenCancellationTokenSource.Cancel(); break; } if (gcTask != null && gcTask.IsCompleted) { gcTask = null; GC.Collect(); continue; } // Only other option is a connection event. Go ahead and clear out the dead connections if (!CheckConnectionTask(connectionList, ref keepAlive, ref isKeepAliveDefault)) { // If there is a client disconnection detected then the server needs to begin // the shutdown process. We have to assume that the client disconnected via // Ctrl+C and wants the server process to terminate. It's possible a compilation // is running out of control and the client wants their machine back. _diagnosticListener.ConnectionRudelyEnded(); listenCancellationTokenSource.Cancel(); break; } if (connectionList.Count == 0 && gcTask == null) { gcTask = Task.Delay(GCTimeout); } } while (true); try { if (connectionList.Count > 0) { Task.WaitAll(connectionList.ToArray()); TimeSpan?ignoredTimeSpan = null; bool ignoredBool = false; CheckConnectionTask(connectionList, ref ignoredTimeSpan, ref ignoredBool); } } catch { // Server is shutting down, don't care why the above failed and Exceptions // are expected here. For example AggregateException via, OperationCancelledException // is an expected case. } }