/// <summary> /// Creates a Task that waits for a client connection to occur and returns the connected /// <see cref="NamedPipeServerStream"/> object. Throws on any connection error. /// </summary> /// <param name="cancellationToken">Used to cancel the connection sequence.</param> private async Task <NamedPipeServerStream> CreateListenTaskCore(CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening. CompilerServerLogger.Log("Constructing pipe '{0}'.", _pipeName); var pipeStream = NamedPipeUtil.CreateServer(_pipeName); CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", _pipeName); CompilerServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); return(pipeStream); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
/// <summary> /// Checks to see if memory is available, and if it is creates a new /// Connection object, awaits the completion of the connection, then /// runs <see cref="ConnectionCompleted"/> for cleanup. /// </summary> private async Task DispatchConnection(NamedPipeServerStream pipeStream) { try { // There is always a race between timeout and connections because // there is no way to cancel listening on the pipe without // closing the pipe. We immediately increment the connection // semaphore while processing connections in order to narrow // the race window as much as possible. Interlocked.Increment(ref this.activeConnectionCount); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); Connection connection = new Connection(pipeStream, handler, this.keepAliveTimer); try { await connection.ServeConnection().ConfigureAwait(false); } catch (ObjectDisposedException e) { // If the client closes the pipe while we're reading or writing // we'll get an object disposed exception on the pipe // Log the failure and continue CompilerServerLogger.Log( "Client pipe closed: received exception " + e.Message); } } else { CompilerServerLogger.Log("Memory tight - rejecting connection."); // As long as we haven't written a response, the client has not // committed to this server instance and can look elsewhere. pipeStream.Close(); // We didn't create a connection -- decrement the semaphore Interlocked.Decrement(ref this.activeConnectionCount); } ConnectionCompleted(); } catch (Exception e) if (CompilerFatalError.Report(e)) { throw ExceptionUtilities.Unreachable; } }
/// <summary> /// Checks to see if memory is available, and if it is creates a new /// Connection object, awaits the completion of the connection, then /// runs <see cref="ConnectionCompleted"/> for cleanup. /// </summary> private async Task DispatchConnection(NamedPipeServerStream pipeStream) { try { // There is always a race between timeout and connections because // there is no way to cancel listening on the pipe without // closing the pipe. We immediately increment the connection // semaphore while processing connections in order to narrow // the race window as much as possible. Interlocked.Increment(ref this.activeConnectionCount); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); Connection connection = new Connection(pipeStream, handler); await connection.ServeConnection().ConfigureAwait(false); // The connection should be finished ConnectionCompleted(connection); } else { CompilerServerLogger.Log("Memory tight - rejecting connection."); // As long as we haven't written a response, the client has not // committed to this server instance and can look elsewhere. pipeStream.Close(); // We didn't create a connection -- decrement the semaphore Interlocked.Decrement(ref this.activeConnectionCount); // Start a terminate server timer if there are no active // connections StartTimeoutTimerIfNecessary(); } } catch (Exception e) if (CompilerFatalError.Report(e)) { throw ExceptionUtilities.Unreachable; } }
/// <summary> /// Creates a Task that waits for a client connection to occur and returns the connected /// <see cref="NamedPipeServerStream"/> object. Throws on any connection error. /// </summary> /// <param name="pipeName">Name of the pipe on which the instance will listen for requests.</param> /// <param name="cancellationToken">Used to cancel the connection sequence.</param> private async Task <NamedPipeServerStream> CreateListenTask(string pipeName, CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening. NamedPipeServerStream pipeStream = ConstructPipe(pipeName); // Unfortunately the version of .Net we are using doesn't support the WaitForConnectionAsync // method. When it is available it should absolutely be used here. In the meantime we // have to deal with the idea that this WaitForConnection call will block a thread // for a significant period of time. It is unadvisable to do this to a thread pool thread // hence we will use an explicit thread here. var listenSource = new TaskCompletionSource <NamedPipeServerStream>(); var listenTask = listenSource.Task; var listenThread = new Thread(() => { try { CompilerServerLogger.Log("Waiting for new connection"); pipeStream.WaitForConnection(); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); listenSource.SetResult(pipeStream); return; } try { pipeStream.Close(); } catch { // Okay for Close failure here. } listenSource.SetException(new Exception("Insufficient resources to process new connection.")); } catch (Exception ex) { listenSource.SetException(ex); } }); listenThread.Start(); // Create a tasks that waits indefinitely (-1) and completes only when cancelled. var waitCancellationTokenSource = new CancellationTokenSource(); var waitTask = Task.Delay( Timeout.Infinite, CancellationTokenSource.CreateLinkedTokenSource(waitCancellationTokenSource.Token, cancellationToken).Token); await Task.WhenAny(listenTask, waitTask).ConfigureAwait(false); if (listenTask.IsCompleted) { waitCancellationTokenSource.Cancel(); return(await listenTask.ConfigureAwait(false)); } // The listen operation was cancelled. Close the pipe stream throw a cancellation exception to // simulate the cancel operation. waitCancellationTokenSource.Cancel(); try { pipeStream.Close(); } catch { // Okay for Close failure here. } throw new OperationCanceledException(); }
internal async Task <CompletionData> ProcessAsync( Task <IClientConnection> clientConnectionTask, bool allowCompilationRequests = true, CancellationToken cancellationToken = default) { try { return(await ProcessCore().ConfigureAwait(false)); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Error processing request for client"); return(CompletionData.RequestError); } async Task <CompletionData> ProcessCore() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); if (request.ProtocolVersion != BuildProtocolConstants.ProtocolVersion) { return(await WriteBuildResponseAsync( clientConnection, new MismatchedVersionBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return(await WriteBuildResponseAsync( clientConnection, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { return(await WriteBuildResponseAsync( clientConnection, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested : true), cancellationToken).ConfigureAwait(false)); } if (!allowCompilationRequests) { return(await WriteBuildResponseAsync( clientConnection, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false)); } if (!Environment.Is64BitProcess && !MemoryHelper.IsMemoryAvailable()) { return(await WriteBuildResponseAsync( clientConnection, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } return(await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false)); } }