/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs
204 строки
9 KB
Jared Parsons
Add compilation start / stop ETW events (#72031)
21 фев 2024, 22:19
Не верифицирован
21 фев 2024, 22:19
eb6374a
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CommandLine; namespace Microsoft.CodeAnalysis.CompilerServer { /// <summary> /// This class is responsible for processing a request from a client of the compiler server. /// </summary> internal sealed class ClientConnectionHandler { internal ICompilerServerHost CompilerServerHost { get; } internal ICompilerServerLogger Logger => CompilerServerHost.Logger; internal ClientConnectionHandler(ICompilerServerHost compilerServerHost) { CompilerServerHost = compilerServerHost; } /// <summary> /// Handles a client connection. The returned task here will never fail. Instead all exceptions will be wrapped /// in a <see cref="CompletionReason.RequestError"/> /// </summary> internal async Task<CompletionData> ProcessAsync( Task<IClientConnection> clientConnectionTask, bool allowCompilationRequests = true, CancellationToken cancellationToken = default) { try { return await ProcessCoreAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.LogException(ex, $"Error processing request for client"); return CompletionData.RequestError; } async Task<CompletionData> ProcessCoreAsync() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); Logger.Log($"Received request {request.RequestId} of type {request.GetType()}"); if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return await WriteBuildResponseAsync( clientConnection, request.RequestId, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { return await WriteBuildResponseAsync( clientConnection, request.RequestId, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested: true), cancellationToken).ConfigureAwait(false); } if (!allowCompilationRequests) { return await WriteBuildResponseAsync( clientConnection, request.RequestId, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false); } if (!Environment.Is64BitProcess && !MemoryHelper.IsMemoryAvailable(Logger)) { return await WriteBuildResponseAsync( clientConnection, request.RequestId, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); } return await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false); } } private async Task<CompletionData> WriteBuildResponseAsync(IClientConnection clientConnection, string requestId, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) { var message = response switch { RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {requestId}", _ => $"Writing {response.Type} response for {requestId}" }; Logger.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 occurring. It could be the // client hit Ctrl-C due to a run away analyzer. var buildCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var compilationTask = ProcessCompilationRequestCoreAsync(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(); } static Task<BuildResponse> ProcessCompilationRequestCoreAsync(ICompilerServerHost compilerServerHost, BuildRequest buildRequest, CancellationToken cancellationToken) { Func<BuildResponse> func = () => { var request = BuildProtocolUtil.GetRunRequest(buildRequest); var response = compilerServerHost.RunCompilation(request, cancellationToken); return response; }; var task = new Task<BuildResponse>(func, cancellationToken, TaskCreationOptions.LongRunning); task.Start(); return task; } } /// <summary> /// Check the request arguments for a new keep alive time. If one is present, /// set the server timer to the new time. /// </summary> private static TimeSpan? CheckForNewKeepAlive(BuildRequest request) { TimeSpan? timeout = null; foreach (var arg in request.Arguments) { if (arg.ArgumentId == BuildProtocolConstants.ArgumentId.KeepAlive) { int result; // If the value is not a valid integer for any reason, // ignore it and continue with the current timeout. The client // is responsible for validating the argument. if (int.TryParse(arg.Value, out result)) { // Keep alive times are specified in seconds timeout = TimeSpan.FromSeconds(result); } } } return timeout; } } }