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();
            }
Exemple #4
0
        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();
            }
Exemple #6
0
            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));
                }
            }