private void CreateListenTask() { Debug.Assert(_listenTask == null); Debug.Assert(_timeoutTask == null); _listenCancellationTokenSource = new CancellationTokenSource(); _listenTask = _clientConnectionHost.CreateListenTask(_listenCancellationTokenSource.Token); _diagnosticListener.ConnectionListening(); }
/// <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. } }