internal ServerDispatcher(ICompilerServerHost compilerServerHost, IClientConnectionHost clientConnectionHost, IDiagnosticListener?diagnosticListener = null) { _compilerServerHost = compilerServerHost; _logger = compilerServerHost.Logger; _clientConnectionHost = clientConnectionHost; _diagnosticListener = diagnosticListener ?? new EmptyDiagnosticListener(); }
internal static ServerData Create( ICompilerServerLogger logger, string pipeName = null, ICompilerServerHost compilerServerHost = null, IClientConnectionHost clientConnectionHost = null, TimeSpan?keepAlive = null) { // The total pipe path must be < 92 characters on Unix, so trim this down to 10 chars pipeName ??= ServerUtil.GetPipeName(); compilerServerHost ??= BuildServerController.CreateCompilerServerHost(logger); clientConnectionHost ??= BuildServerController.CreateClientConnectionHost(pipeName, logger); keepAlive ??= TimeSpan.FromMilliseconds(-1); var listener = new TestableDiagnosticListener(); var serverListenSource = new TaskCompletionSource <bool>(); var cts = new CancellationTokenSource(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); var task = Task.Run(() => { BuildServerController.CreateAndRunServer( pipeName, compilerServerHost, clientConnectionHost, listener, keepAlive: keepAlive, cancellationToken: cts.Token); return(listener); }); return(new ServerData(cts, pipeName, logger, task)); }
/// <summary> /// Shutting down the server is an inherently racy operation. The server can be started or stopped by /// external parties at any time. /// /// This function will return success if at any time in the function the server is determined to no longer /// be running. /// </summary> internal static async Task <bool> RunServerShutdownRequestAsync( string pipeName, int?timeoutOverride, bool waitForProcess, ICompilerServerLogger logger, CancellationToken cancellationToken) { if (wasServerRunning(pipeName) == false) { // The server holds the mutex whenever it is running, if it's not open then the // server simply isn't running. return(true); } try { var request = BuildRequest.CreateShutdown(); // Don't create the server when sending a shutdown request. That would defeat the // purpose a bit. var response = await RunServerBuildRequestAsync( request, pipeName, timeoutOverride, tryCreateServerFunc : (_, _) => false, logger, cancellationToken).ConfigureAwait(false); if (response is ShutdownBuildResponse shutdownBuildResponse) { if (waitForProcess) { try { var process = Process.GetProcessById(shutdownBuildResponse.ServerProcessId); #if NET5_0_OR_GREATER await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); #else process.WaitForExit(); #endif } catch (Exception) { // There is an inherent race here with the server process. If it has already shutdown // by the time we try to access it then the operation has succeed. } } return(true); } return(wasServerRunning(pipeName) == false); } catch (Exception) { // If the server was in the process of shutting down when we connected then it's reasonable // for an exception to happen. If the mutex has shutdown at this point then the server // is shut down. return(wasServerRunning(pipeName) == false); }
public ulong Reserved; //always 0 public static bool IsMemoryAvailable(ICompilerServerLogger logger) { if (!PlatformInformation.IsWindows) { // assume we have enough memory on non-Windows machines return(true); } MemoryHelper status = new MemoryHelper(); GlobalMemoryStatusEx(status); ulong max = status.MaxVirtual; ulong free = status.AvailableVirtual; int shift = 20; string unit = "MB"; if (free >> shift == 0) { shift = 10; unit = "KB"; } logger.Log("Free memory: {1}{0} of {2}{0}.", unit, free >> shift, max >> shift); return(free >= 800 << 20); // Value (500MB) is arbitrary; feel free to improve. }
internal static void Log(this ICompilerServerLogger logger, string format, params object?[] arguments) { if (logger.IsLogging) { logger.Log(string.Format(format, arguments)); } }
internal static ICompilerServerHost CreateCompilerServerHost(ICompilerServerLogger logger) { var clientDirectory = BuildClient.GetClientDirectory(); var sdkDirectory = BuildClient.GetSystemSdkDirectory(); return(new CompilerServerHost(clientDirectory, sdkDirectory, logger)); }
/// <summary> /// Log an exception. Also logs information about inner exceptions. /// </summary> internal static void LogException(this ICompilerServerLogger logger, Exception exception, string reason) { if (!logger.IsLogging) { return; } var builder = new StringBuilder(); builder.Append("Error "); AppendException(exception); int innerExceptionLevel = 0; Exception?e = exception.InnerException; while (e != null) { builder.Append($"Inner exception[{innerExceptionLevel}] "); AppendException(e); e = e.InnerException; innerExceptionLevel += 1; } logger.Log(builder.ToString()); void AppendException(Exception exception) { builder.AppendLine($"Error: '{exception.GetType().Name}' '{exception.Message}' occurred during '{reason}'"); builder.AppendLine("Stack trace:"); builder.AppendLine(exception.StackTrace); } }
/// <summary> /// Used to log a message that should go into both the compiler server log as well as the MSBuild logs /// /// These are intended to be processed by automation in the binlog hence do not change the structure of /// the messages here. /// </summary> private void LogCompilationMessage( ICompilerServerLogger logger, Guid requestId, CompilationKind kind, string diagnostic ) { var category = kind switch { CompilationKind.Server => "server", CompilationKind.Tool => "tool", CompilationKind.ToolFallback => "server failed", CompilationKind.FatalError => "fatal error", _ => throw new Exception($"Unexpected value {kind}"), }; var message = $"CompilerServer: {category} - {diagnostic} - {requestId}"; if (kind == CompilationKind.FatalError) { logger.LogError(message); Log.LogError(message); } else { logger.Log(message); Log.LogMessage(message); } }
public static Task <BuildResponse> RunServerCompilationAsync( Guid requestId, RequestLanguage language, string?sharedCompilationId, List <string> arguments, BuildPathsAlt buildPaths, string?keepAlive, string?libEnvVariable, ICompilerServerLogger logger, CancellationToken cancellationToken) { var pipeNameOpt = sharedCompilationId ?? GetPipeNameForPath(buildPaths.ClientDirectory); return(RunServerCompilationCoreAsync( requestId, language, arguments, buildPaths, pipeNameOpt, keepAlive, libEnvVariable, timeoutOverride: null, createServerFunc: TryCreateServerCore, logger: logger, cancellationToken: cancellationToken)); }
/// <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 static async Task ListenCoreAsync( string pipeName, ICompilerServerLogger logger, AsyncQueue <ListenResult> queue, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { NamedPipeServerStream?pipeStream = null; try { // 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 logger.Log($"Constructing pipe and waiting for connections '{pipeName}'"); pipeStream = NamedPipeUtil.CreateServer(pipeName); // The WaitForConnectionAsync API does not fully respect the provided CancellationToken // on all platforms: // // https://github.com/dotnet/runtime/issues/40289 // // To mitigate this we need to setup a cancellation Task and dispose the NamedPipeServerStream // if it ever completes. Once all of the NamedPipeServerStream for the given pipe name are // disposed they will all exit the WaitForConnectionAsync method var connectTask = pipeStream.WaitForConnectionAsync(cancellationToken); if (!PlatformInformation.IsWindows) { var cancelTask = Task.Delay(TimeSpan.FromMilliseconds(-1), cancellationToken); var completedTask = await Task.WhenAny(new[] { connectTask, cancelTask }).ConfigureAwait(false); if (completedTask == cancelTask) { throw new OperationCanceledException(); } } await connectTask.ConfigureAwait(false); logger.Log("Pipe connection established."); var connection = new NamedPipeClientConnection(pipeStream, logger); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) { // Expected when the host is shutting down. logger.Log($"Pipe connection cancelled"); pipeStream?.Dispose(); } catch (Exception ex) { logger.LogException(ex, $"Pipe connection error"); queue.Enqueue(new ListenResult(exception: ex)); pipeStream?.Dispose(); } } }
private ServerData(CancellationTokenSource cancellationTokenSource, string pipeName, ICompilerServerLogger logger, Task <TestableDiagnosticListener> serverTask) { CancellationTokenSource = cancellationTokenSource; PipeName = pipeName; Logger = logger; ServerTask = serverTask; }
internal static void LogError(this ICompilerServerLogger logger, string format, params object?[] arguments) { if (logger.IsLogging) { logger.Log($"Error: {format}", arguments); } }
internal BuildServerController( NameValueCollection appSettings, ICompilerServerLogger logger ) { _appSettings = appSettings; _logger = logger; }
internal TestableCompilerServerHost( Func <RunRequest, CancellationToken, BuildResponse> runCompilation = null, ICompilerServerLogger logger = null ) { RunCompilation = runCompilation; Logger = logger ?? EmptyCompilerServerLogger.Instance; }
internal NamedPipeClientConnection( NamedPipeServerStream stream, ICompilerServerLogger logger ) { Stream = stream; Logger = logger; }
internal static async Task <BuildResponse> RunServerCompilationCoreAsync( Guid requestId, RequestLanguage language, List <string> arguments, BuildPathsAlt buildPaths, string?pipeName, string?keepAlive, string?libDirectory, int?timeoutOverride, CreateServerFunc createServerFunc, ICompilerServerLogger logger, CancellationToken cancellationToken) { if (pipeName is null) { throw new ArgumentException(nameof(pipeName)); } if (buildPaths.TempDirectory == null) { throw new ArgumentException(nameof(buildPaths)); } // early check for the build hash. If we can't find it something is wrong; no point even trying to go to the server if (string.IsNullOrWhiteSpace(BuildProtocolConstants.GetCommitHash())) { return(new IncorrectHashBuildResponse()); } var pipeTask = tryConnectToServer(pipeName, buildPaths, timeoutOverride, createServerFunc, logger, cancellationToken); if (pipeTask is null) { return(new RejectedBuildResponse("Failed to connect to server")); } else { using var pipe = await pipeTask.ConfigureAwait(false); if (pipe is null) { return(new RejectedBuildResponse("Failed to connect to server")); } else { var request = BuildRequest.Create(language, arguments, workingDirectory: buildPaths.WorkingDirectory, tempDirectory: buildPaths.TempDirectory, compilerHash: BuildProtocolConstants.GetCommitHash() ?? "", requestId: requestId, keepAlive: keepAlive, libDirectory: libDirectory); return(await TryCompileAsync(pipe, request, logger, cancellationToken).ConfigureAwait(false)); } }
internal CompilerServerHost( string clientDirectory, string sdkDirectory, ICompilerServerLogger logger ) { ClientDirectory = clientDirectory; SdkDirectory = sdkDirectory; Logger = logger; }
internal static BuildClient CreateBuildClient( RequestLanguage language, ICompilerServerLogger logger, CompileFunc compileFunc = null, TextWriter textWriter = null, int?timeoutOverride = null) { compileFunc = compileFunc ?? GetCompileFunc(language); textWriter = textWriter ?? new StringWriter(); return(new BuildClient(language, compileFunc, logger, timeoutOverride: timeoutOverride)); }
internal static async Task <ServerData> CreateServer( ICompilerServerLogger logger, string pipeName = null, ICompilerServerHost compilerServerHost = null, IClientConnectionHost clientConnectionHost = null, TimeSpan?keepAlive = null) { var serverData = ServerData.Create(logger, pipeName, compilerServerHost, clientConnectionHost, keepAlive); await serverData.WaitForServerAsync(); return(serverData); }
internal static ICompilerServerHost CreateCompilerServerHost(ICompilerServerLogger logger) { // VBCSCompiler is installed in the same directory as csc.exe and vbc.exe which is also the // location of the response files. // // BaseDirectory was mistakenly marked as potentially null in 3.1 // https://github.com/dotnet/runtime/pull/32486 var clientDirectory = AppDomain.CurrentDomain.BaseDirectory !; var sdkDirectory = BuildClient.GetSystemSdkDirectory(); return(new CompilerServerHost(clientDirectory, sdkDirectory, logger)); }
internal static async Task <ServerData> CreateServer( ICompilerServerLogger logger, string pipeName = null, ICompilerServerHost compilerServerHost = null, IClientConnectionHost clientConnectionHost = null, TimeSpan?keepAlive = null ) { // The total pipe path must be < 92 characters on Unix, so trim this down to 10 chars pipeName ??= GetPipeName(); compilerServerHost ??= BuildServerController.CreateCompilerServerHost(logger); clientConnectionHost ??= BuildServerController.CreateClientConnectionHost( pipeName, logger ); keepAlive ??= TimeSpan.FromMilliseconds(-1); var listener = new TestableDiagnosticListener(); var serverListenSource = new TaskCompletionSource <bool>(); var cts = new CancellationTokenSource(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); var task = Task.Run( () => { BuildServerController.CreateAndRunServer( pipeName, compilerServerHost, clientConnectionHost, listener, keepAlive: keepAlive, cancellationToken: cts.Token ); return(listener); } ); // The contract of this function is that it will return once the server has started. Spin here until // we can verify the server has started or simply failed to start. while (BuildServerConnection.WasServerMutexOpen(mutexName) != true && !task.IsCompleted) { await Task.Yield(); } return(new ServerData(cts, pipeName, logger, task)); }
internal static BuildClient CreateBuildClient( RequestLanguage language, ICompilerServerLogger logger) { // Create a client to run the build. Infinite timeout is used to account for the // case where these tests are run under extreme load. In high load scenarios the // client will correctly drop down to a local compilation if the server doesn't respond // fast enough. CompileOnServerFunc compileOnServerFunc = (request, pipeName, cancellationToken) => BuildServerConnection.RunServerBuildRequestAsync( request, pipeName, timeoutOverride: Timeout.Infinite, tryCreateServerFunc: (_, _) => false, logger, cancellationToken); var compileFunc = GetCompileFunc(language); return(new BuildClient(language, compileFunc, compileOnServerFunc)); }
internal static int Run( IEnumerable <string> arguments, RequestLanguage language, CompileFunc compileFunc, ICompilerServerLogger logger, Guid?requestId = null ) { var sdkDir = GetSystemSdkDirectory(); if (RuntimeHostInfo.IsCoreClrRuntime) { // Register encodings for console // https://github.com/dotnet/roslyn/issues/10785 System.Text.Encoding.RegisterProvider( System.Text.CodePagesEncodingProvider.Instance ); } var client = new BuildClient(language, compileFunc, logger); var clientDir = AppContext.BaseDirectory; var workingDir = Directory.GetCurrentDirectory(); var tempDir = BuildServerConnection.GetTempPath(workingDir); var buildPaths = new BuildPaths( clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir, tempDir: tempDir ); var originalArguments = GetCommandLineArgs(arguments); return(client.RunCompilation( originalArguments, buildPaths, requestId: requestId ).ExitCode); }
/// <summary> /// Handle a response from the server, reporting messages and returning /// the appropriate exit code. /// </summary> private int HandleResponse(BuildResponse response, string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) { if (response.Type != BuildResponse.ResponseType.Completed) { ValidateBootstrapUtil.AddFailedServerConnection(response.Type, OutputAssembly?.ItemSpec); } switch (response.Type) { case BuildResponse.ResponseType.Completed: var completedResponse = (CompletedBuildResponse)response; LogMessages(completedResponse.Output, StandardOutputImportanceToUse); if (LogStandardErrorAsError) { LogErrorMultiline(completedResponse.ErrorOutput); } else { LogMessages(completedResponse.ErrorOutput, StandardErrorImportanceToUse); } return(completedResponse.ReturnCode); case BuildResponse.ResponseType.MismatchedVersion: logError("Roslyn compiler server reports different protocol version than build task."); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); case BuildResponse.ResponseType.IncorrectHash: logError("Roslyn compiler server reports different hash version than build task."); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); case BuildResponse.ResponseType.Rejected: case BuildResponse.ResponseType.AnalyzerInconsistency: logger.LogError($"Server rejected request {response.Type}"); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); default: logError($"Received an unrecognized response from the server: {response.Type}"); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } void logError(string message) { logger.LogError(message); Log.LogError(message); } }
internal static IClientConnectionHost CreateClientConnectionHost(string pipeName, ICompilerServerLogger logger) => new NamedPipeClientConnectionHost(pipeName, logger);
internal NamedPipeClientConnectionHost(string pipeName, ICompilerServerLogger logger) { PipeName = pipeName; Logger = logger; }