public ulong Reserved; //always 0 public static bool IsMemoryAvailable() { 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"; } CompilerServerLogger.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. }
/// <summary> /// Tries to connect to existing servers on the system. /// </summary> /// <returns> /// A <see cref="NamedPipeClientStream"/> on success, null on failure. /// </returns> private bool TryExistingProcesses(CancellationToken cancellationToken, out NamedPipeClientStream pipeStream) { CompilerServerLogger.Log("Trying existing processes."); pipeStream = null; foreach (int processId in GetAllProcessIds()) { cancellationToken.ThrowIfCancellationRequested(); if (TryConnectToProcess(processId, TimeOutMsExistingProcess, cancellationToken, out pipeStream)) { CompilerServerLogger.Log("Found existing process"); return(true); } } return(false); }
public static int Main(string[] args) { NameValueCollection appSettings; try { #if NET472 appSettings = System.Configuration.ConfigurationManager.AppSettings; #else // Do not use AppSettings on non-desktop platforms appSettings = new NameValueCollection(); #endif } catch (Exception ex) { // It is possible for AppSettings to throw when the application or machine configuration // is corrupted. This should not prevent the server from starting, but instead just revert // to the default configuration. appSettings = new NameValueCollection(); CompilerServerLogger.LogException(ex, "Error loading application settings"); } try { var controller = new DesktopBuildServerController(appSettings); return(controller.Run(args)); } catch (FileNotFoundException e) { // Assume the exception was the result of a missing compiler assembly. LogException(e); } catch (TypeInitializationException e) when(e.InnerException is FileNotFoundException) { // Assume the exception was the result of a missing compiler assembly. LogException((FileNotFoundException)e.InnerException); } return(CommonCompiler.Failed); }
public static bool Check(string baseDirectory, IEnumerable <CommandLineAnalyzerReference> analyzerReferences, IAnalyzerAssemblyLoader loader, IEnumerable <string> ignorableReferenceNames = null) { if (ignorableReferenceNames == null) { ignorableReferenceNames = s_defaultIgnorableReferenceNames; } try { CompilerServerLogger.Log("Begin Analyzer Consistency Check"); return(CheckCore(baseDirectory, analyzerReferences, loader, ignorableReferenceNames)); } catch (Exception e) { CompilerServerLogger.LogException(e, "Analyzer Consistency Check"); return(false); } finally { CompilerServerLogger.Log("End Analyzer Consistency Check"); } }
public async Task <BuildResponse> GetResponseAsync(BuildRequest req, CancellationToken cancellationToken) { NamedPipeClientStream pipeStream; if (TryAutoConnectToServer(cancellationToken, out pipeStream)) { // We have a good connection BuildResponse response = await DoCompilationAsync(pipeStream, req, cancellationToken).ConfigureAwait(false); if (response != null) { return(response); } else { CompilerServerLogger.Log("Compilation failed, constructing new compiler server"); // The compilation failed. There are a couple possible reasons for this, // including that we are using a 32-bit compiler server and we are out of // memory. This is the last attempt -- we will create a new server manually // and try to compile. There is no mutex because anyone else using // this server is accidental only. int newProcessId = CreateNewServerProcess(); if (newProcessId != 0 && TryConnectToProcess(newProcessId, TimeOutMsNewProcess, cancellationToken, out pipeStream)) { return(await DoCompilationAsync(pipeStream, req, cancellationToken).ConfigureAwait(false)); } } } return(null); }
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return(0); } try { using var logger = new CompilerServerLogger(); string workingDir = CurrentDirectoryToUse(); string?tempDir = BuildServerConnection.GetTempPath(workingDir); if (!UseSharedCompilation || HasToolBeenOverridden || !BuildServerConnection.IsCompilerServerSupported) { LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool by design '{pathToTool}'"); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } _sharedCompileCts = new CancellationTokenSource(); logger.Log($"CommandLine = '{commandLineCommands}'"); logger.Log($"BuildResponseFile = '{responseFileCommands}'"); var clientDir = Path.GetDirectoryName(PathToManagedTool); if (clientDir is null || tempDir is null) { LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool because we could not find client directory '{PathToManagedTool}'"); return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } var buildPaths = new BuildPathsAlt( clientDir: clientDir, workingDir: workingDir, // MSBuild doesn't need the .NET SDK directory sdkDir: null, tempDir: tempDir); // Note: using ToolArguments here (the property) since // commandLineCommands (the parameter) may have been mucked with // (to support using the dotnet cli) var responseTask = BuildServerConnection.RunServerCompilationAsync( Language, RoslynString.IsNullOrEmpty(SharedCompilationId) ? null : SharedCompilationId, GetArguments(ToolArguments, responseFileCommands).ToList(), buildPaths, keepAlive: null, libEnvVariable: LibDirectoryToUse(), logger: logger, cancellationToken: _sharedCompileCts.Token); responseTask.Wait(_sharedCompileCts.Token); ExitCode = HandleResponse(responseTask.Result, pathToTool, responseFileCommands, commandLineCommands, logger); } catch (OperationCanceledException) { ExitCode = 0; } catch (Exception e) { var util = new TaskLoggingHelper(this); util.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); util.LogErrorFromException(e, showStackTrace: true, showDetail: true, file: null); ExitCode = -1; } finally { _sharedCompileCts?.Dispose(); _sharedCompileCts = null; } return(ExitCode); }
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return(0); } if (!UseSharedCompilation || !string.IsNullOrEmpty(ToolPath)) { return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } using (_sharedCompileCts = new CancellationTokenSource()) { try { CompilerServerLogger.Log($"CommandLine = '{commandLineCommands}'"); CompilerServerLogger.Log($"BuildResponseFile = '{responseFileCommands}'"); var buildPaths = new BuildPaths( clientDir: TryGetClientDir() ?? Path.GetDirectoryName(pathToTool), // MSBuild doesn't need the .NET SDK directory sdkDir: null, workingDir: CurrentDirectoryToUse()); var responseTask = BuildClientShim.RunServerCompilation( Language, GetArguments(commandLineCommands, responseFileCommands).ToList(), buildPaths, keepAlive: null, libEnvVariable: LibDirectoryToUse(), cancellationToken: _sharedCompileCts.Token); responseTask.Wait(_sharedCompileCts.Token); var response = responseTask.Result; if (response != null) { ExitCode = HandleResponse(response, pathToTool, responseFileCommands, commandLineCommands); } else { ExitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } } catch (OperationCanceledException) { ExitCode = 0; } catch (Exception e) { Log.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); LogErrorOutput(e.ToString()); ExitCode = -1; } } return(ExitCode); }
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return(0); } try { string workingDir = CurrentDirectoryToUse(); string?tempDir = BuildServerConnection.GetTempPath(workingDir); if (!UseSharedCompilation || HasToolBeenOverridden || !BuildServerConnection.IsCompilerServerSupported) { return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } using var logger = new CompilerServerLogger(); using (_sharedCompileCts = new CancellationTokenSource()) { logger.Log($"CommandLine = '{commandLineCommands}'"); logger.Log($"BuildResponseFile = '{responseFileCommands}'"); var clientDir = Path.GetDirectoryName(PathToManagedTool); if (clientDir is null || tempDir is null) { return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } // Note: we can't change the "tool path" printed to the console when we run // the Csc/Vbc task since MSBuild logs it for us before we get here. Instead, // we'll just print our own message that contains the real client location Log.LogMessage(ErrorString.UsingSharedCompilation, clientDir); var buildPaths = new BuildPathsAlt( clientDir: clientDir, workingDir: workingDir, // MSBuild doesn't need the .NET SDK directory sdkDir: null, tempDir: tempDir); // Note: using ToolArguments here (the property) since // commandLineCommands (the parameter) may have been mucked with // (to support using the dotnet cli) var responseTask = BuildServerConnection.RunServerCompilationAsync( Language, RoslynString.IsNullOrEmpty(SharedCompilationId) ? null : SharedCompilationId, GetArguments(ToolArguments, responseFileCommands).ToList(), buildPaths, keepAlive: null, libEnvVariable: LibDirectoryToUse(), logger: logger, cancellationToken: _sharedCompileCts.Token); responseTask.Wait(_sharedCompileCts.Token); var response = responseTask.Result; if (response != null) { ExitCode = HandleResponse(response, pathToTool, responseFileCommands, commandLineCommands, logger); } else { logger.LogError($"Server compilation failed, falling back to {pathToTool}"); Log.LogMessage(ErrorString.SharedCompilationFallback, pathToTool); ExitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } } } catch (OperationCanceledException) { ExitCode = 0; } catch (Exception e) { var util = new TaskLoggingHelper(this); util.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); util.LogErrorFromException(e, showStackTrace: true, showDetail: true, file: null); ExitCode = -1; } return(ExitCode); }
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { using var logger = new CompilerServerLogger($"MSBuild {Process.GetCurrentProcess().Id}"); return(ExecuteTool(pathToTool, responseFileCommands, commandLineCommands, logger)); }
/// <summary> /// Connect to a running compiler server or automatically start a new one. /// Throws on cancellation. /// </summary> /// <param name="cancellationToken">Cancellation token for request.</param> /// <param name="pipeStream"> /// A <see cref="NamedPipeClientStream"/> connected to a compiler server process /// or null on failure. /// </param> /// <returns> /// </returns> private bool TryAutoConnectToServer(CancellationToken cancellationToken, out NamedPipeClientStream pipeStream) { pipeStream = null; cancellationToken.ThrowIfCancellationRequested(); CompilerServerLogger.Log("Creating mutex."); // We should hold the mutex when starting a new process bool haveMutex; var singleServerMutex = new Mutex(initiallyOwned: true, name: serverExecutablePath.Replace('\\', '/'), createdNew: out haveMutex); try { if (!haveMutex) { CompilerServerLogger.Log("Waiting for mutex."); try { haveMutex = singleServerMutex.WaitOne(TimeOutMsNewProcess); } catch (AbandonedMutexException) { // Someone abandoned the mutex, but we still own it // Log and continue CompilerServerLogger.Log("Acquired mutex, but mutex was previously abandoned."); haveMutex = true; } } if (haveMutex) { CompilerServerLogger.Log("Acquired mutex"); // First try to connect to an existing process if (TryExistingProcesses(cancellationToken, out pipeStream)) { // Release the mutex and get out return(true); } CompilerServerLogger.Log("Starting new process"); // No luck, start our own process. int newProcessId = CreateNewServerProcess(); if (newProcessId != 0 && TryConnectToProcess(newProcessId, TimeOutMsNewProcess, cancellationToken, out pipeStream)) { CompilerServerLogger.Log("Connected to new process"); // Release the mutex and get out return(true); } } } finally { if (haveMutex) { CompilerServerLogger.Log("Releasing mutex"); singleServerMutex.ReleaseMutex(); } } return(false); }
private static bool CheckCore(string baseDirectory, IEnumerable <CommandLineAnalyzerReference> analyzerReferences, IAnalyzerAssemblyLoader loader, IEnumerable <string> ignorableReferenceNames) { var resolvedPaths = new List <string>(); foreach (var analyzerReference in analyzerReferences) { string resolvedPath = FileUtilities.ResolveRelativePath(analyzerReference.FilePath, basePath: null, baseDirectory: baseDirectory, searchPaths: SpecializedCollections.EmptyEnumerable <string>(), fileExists: File.Exists); if (resolvedPath != null) { resolvedPath = FileUtilities.TryNormalizeAbsolutePath(resolvedPath); if (resolvedPath != null) { resolvedPaths.Add(resolvedPath); } } // Don't worry about paths we can't resolve. The compiler will report an error for that later. } // First, check that the set of references is complete, modulo items in the safe list. foreach (var resolvedPath in resolvedPaths) { var missingDependencies = AssemblyUtilities.IdentifyMissingDependencies(resolvedPath, resolvedPaths); foreach (var missingDependency in missingDependencies) { if (!ignorableReferenceNames.Any(name => missingDependency.Name.StartsWith(name))) { CompilerServerLogger.Log($"Analyzer assembly {resolvedPath} depends on '{missingDependency}' but it was not found."); return(false); } } } // Register analyzers and their dependencies upfront, // so that assembly references can be resolved: foreach (var resolvedPath in resolvedPaths) { loader.AddDependencyLocation(resolvedPath); } // Load all analyzer assemblies: var loadedAssemblies = new List <Assembly>(); foreach (var resolvedPath in resolvedPaths) { loadedAssemblies.Add(loader.LoadFromPath(resolvedPath)); } // Third, check that the MVIDs of the files on disk match the MVIDs of the loaded assemblies. for (int i = 0; i < resolvedPaths.Count; i++) { var resolvedPath = resolvedPaths[i]; var loadedAssembly = loadedAssemblies[i]; var resolvedPathMvid = AssemblyUtilities.ReadMvid(resolvedPath); var loadedAssemblyMvid = loadedAssembly.ManifestModule.ModuleVersionId; if (resolvedPathMvid != loadedAssemblyMvid) { CompilerServerLogger.Log($"Analyzer assembly {resolvedPath} has MVID '{resolvedPathMvid}' but loaded assembly '{loadedAssembly.FullName}' has MVID '{loadedAssemblyMvid}'."); return(false); } } return(true); }
internal void LogError(string message) { CompilerServerLogger.LogError(message); Log.LogError(message); }
private static void LogException(FileNotFoundException e) { CompilerServerLogger.LogException(e, "File not found"); }
private void LogException(Exception e, string message) { CompilerServerLogger.LogException(e, string.Format("Client {0}: {1}", _loggingIdentifier, message)); }
private void Log(string message) { CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message); }
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)); } }
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return(0); } if (!UseSharedCompilation || !string.IsNullOrEmpty(ToolPath) || !BuildServerConnection.IsCompilerServerSupported) { return(base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)); } using (_sharedCompileCts = new CancellationTokenSource()) { try { CompilerServerLogger.Log($"CommandLine = '{commandLineCommands}'"); CompilerServerLogger.Log($"BuildResponseFile = '{responseFileCommands}'"); var clientDir = Path.GetDirectoryName(pathToTool); // Note: we can't change the "tool path" printed to the console when we run // the Csc/Vbc task since MSBuild logs it for us before we get here. Instead, // we'll just print our own message that contains the real client location Log.LogMessage(ErrorString.UsingSharedCompilation, clientDir); var workingDir = CurrentDirectoryToUse(); var buildPaths = new BuildPathsAlt( clientDir: clientDir, // MSBuild doesn't need the .NET SDK directory sdkDir: null, workingDir: workingDir, tempDir: BuildServerConnection.GetTempPath(workingDir)); var responseTask = BuildServerConnection.RunServerCompilation( Language, GetArguments(commandLineCommands, responseFileCommands).ToList(), buildPaths, keepAlive: null, libEnvVariable: LibDirectoryToUse(), cancellationToken: _sharedCompileCts.Token); responseTask.Wait(_sharedCompileCts.Token); var response = responseTask.Result; if (response != null) { ExitCode = HandleResponse(response, pathToTool, responseFileCommands, commandLineCommands); } else { Log.LogMessage(ErrorString.SharedCompilationFallback, pathToTool); ExitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } } catch (OperationCanceledException) { ExitCode = 0; } catch (Exception e) { Log.LogErrorWithCodeFromResources("Compiler_UnexpectedException"); LogErrorOutput(e.ToString()); ExitCode = -1; } } return(ExitCode); }
private const int TimeOutMsNewProcess = 20000; // Spend up to 20s connecting to a new process, to allow time for it to start. public BuildClient() { CompilerServerLogger.Initialize("TSK"); // Mark log file entries as from MSBuild Task. this.serverExecutablePath = GetExpectedServerExecutablePath(); }