private async Task <ConnectionData> HandleRejectedRequest(CancellationToken cancellationToken) { var response = new RejectedBuildResponse(); await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); return(new ConnectionData(CompletionReason.CompilationNotStarted)); }
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) { var response = new MismatchedVersionBuildResponse(); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { var response = new IncorrectHashBuildResponse(); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { var id = Process.GetCurrentProcess().Id; var response = new ShutdownBuildResponse(id); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(new CompletionData(CompletionReason.RequestCompleted, shutdownRequested: true)); } if (!allowCompilationRequests) { var response = new RejectedBuildResponse("Compilation not allowed at this time"); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } return(await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false)); } }
private async Task <CompletionData> ProcessCompilationRequestAsync(IClientConnection clientConnection, BuildRequest request, CancellationToken cancellationToken) { // Need to wait for the compilation and client disconnection in parallel. If the client // suddenly disconnects we need to cancel the compilation that is occurring. It could be the // client hit Ctrl-C due to a run away analyzer. var buildCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var compilationTask = ProcessCompilationRequestCore(CompilerServerHost, request, buildCancellationTokenSource.Token); await Task.WhenAny(compilationTask, clientConnection.DisconnectTask).ConfigureAwait(false); try { if (compilationTask.IsCompleted) { BuildResponse response; CompletionData completionData; try { response = await compilationTask.ConfigureAwait(false); completionData = response switch { // Once there is an analyzer inconsistency the assembly load space is polluted. The // request is an error. AnalyzerInconsistencyBuildResponse _ => CompletionData.RequestError, _ => new CompletionData(CompletionReason.RequestCompleted, newKeepAlive: CheckForNewKeepAlive(request)) }; } catch (Exception ex) { // The compilation task should never throw. If it does we need to assume that the compiler is // in a bad state and need to issue a RequestError Logger.LogException(ex, $"Exception running compilation for {request.RequestId}"); response = new RejectedBuildResponse($"Exception during compilation: {ex.Message}"); completionData = CompletionData.RequestError; } return(await WriteBuildResponseAsync( clientConnection, request.RequestId, response, completionData, cancellationToken).ConfigureAwait(false)); } else { return(CompletionData.RequestError); } } finally { buildCancellationTokenSource.Cancel(); }
private static async Task <CompletionData> WriteBuildResponseAsync(IClientConnection clientConnection, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) { var message = response switch { RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {clientConnection.LoggingIdentifier}", _ => $"Writing {response.Type} response for {clientConnection.LoggingIdentifier}" }; CompilerServerLogger.Log(message); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(completionData); }
private async Task <CompletionData> ProcessCompilationRequestAsync(IClientConnection clientConnection, BuildRequest request, CancellationToken cancellationToken) { // Need to wait for the compilation and client disconnection in parallel. If the client // suddenly disconnects we need to cancel the compilation that is occuring. It could be the // client hit Ctrl-C due to a run away analyzer. var buildCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var compilationTask = ProcessCompilationRequestCore(CompilerServerHost, request, buildCancellationTokenSource.Token); await Task.WhenAny(compilationTask, clientConnection.DisconnectTask).ConfigureAwait(false); try { if (compilationTask.IsCompleted) { BuildResponse response; try { response = await compilationTask.ConfigureAwait(false); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Exception running compilation for {clientConnection.LoggingIdentifier}"); response = new RejectedBuildResponse($"Exception during compilation: {ex.Message}"); } await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); var newKeepAlive = CheckForNewKeepAlive(request); var completionReason = response switch { AnalyzerInconsistencyBuildResponse _ => CompletionReason.RequestError, RejectedBuildResponse _ => CompletionReason.RequestError, _ => CompletionReason.RequestCompleted }; return(new CompletionData(completionReason, newKeepAlive)); } else { return(CompletionData.RequestError); } } finally { buildCancellationTokenSource.Cancel(); }
internal async Task <ConnectionResult> AcceptConnection(Task <Connection> task, bool accept, CancellationToken cancellationToken) { Connection connection; try { connection = await task; } catch (Exception ex) { // Unable to establish a connection with the client. The client is responsible for // handling this case. Nothing else for us to do here. CompilerServerLogger.LogException(ex, "Error creating client named pipe"); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } try { using (connection) { BuildRequest request; try { CompilerServerLogger.Log("Begin reading request."); request = await BuildRequest.ReadAsync(connection.Stream, cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("End reading request."); } catch (Exception e) { CompilerServerLogger.LogException(e, "Error reading build request."); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } if (request.IsShutdownRequest()) { // Reply with the PID of this process so that the client can wait for it to exit. var response = new ShutdownBuildResponse(Process.GetCurrentProcess().Id); await response.WriteAsync(connection.Stream, cancellationToken); // We can safely disconnect the client, then when this connection gets cleaned up by the event loop // the server will go to a shutdown state. return(new ConnectionResult(ConnectionResult.Reason.ClientShutdownRequest)); } else if (!accept) { // We're already in shutdown mode, respond gracefully so the client can run in-process. var response = new RejectedBuildResponse(); await response.WriteAsync(connection.Stream, cancellationToken).ConfigureAwait(false); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } else { // If we get here then this is a real request that we will accept and process. // // Kick off both the compilation and a task to monitor the pipe for closing. var buildCancelled = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var watcher = connection.WaitForDisconnectAsync(buildCancelled.Token); var worker = ExecuteRequestAsync(request, buildCancelled.Token); // await will end when either the work is complete or the connection is closed. await Task.WhenAny(worker, watcher); // Do an 'await' on the completed task, preference being compilation, to force // any exceptions to be realized in this method for logging. ConnectionResult.Reason reason; if (worker.IsCompleted) { var response = await worker; try { CompilerServerLogger.Log("Begin writing response."); await response.WriteAsync(connection.Stream, cancellationToken); CompilerServerLogger.Log("End writing response."); reason = ConnectionResult.Reason.CompilationCompleted; } catch { reason = ConnectionResult.Reason.ClientDisconnect; } } else { await watcher; reason = ConnectionResult.Reason.ClientDisconnect; } // Begin the tear down of the Task which didn't complete. buildCancelled.Cancel(); return(new ConnectionResult(reason, request.KeepAlive)); } } } catch (Exception ex) { CompilerServerLogger.LogException(ex, "Error handling connection"); return(new ConnectionResult(ConnectionResult.Reason.ClientException)); } }