private static Assembly TryRedirectToRuntimesDir(AssemblyName name) { CompilerServerLogger.Log($"Loading with redirect {name.Name}"); if (s_assemblyLocation == null) { return(null); } var taskDir = Path.GetDirectoryName(s_assemblyLocation); var osId = PlatformInformation.IsWindows ? "win" : "unix"; var runtimeDir = Path.Combine(taskDir, "runtimes", osId, "lib", "netstandard1.3"); var assemblyPath = Path.Combine(runtimeDir, name.Name) + ".dll"; if (File.Exists(assemblyPath)) { CompilerServerLogger.Log($"Loading from: {assemblyPath}"); return(LoadAssemblyFromPath(assemblyPath)); } CompilerServerLogger.Log($"File not found: {assemblyPath}"); return(null); Assembly LoadAssemblyFromPath(string path) => (Assembly)typeof(Assembly).GetTypeInfo() .GetDeclaredMethod("LoadFile") ?.Invoke(null, parameters: new object[] { assemblyPath }); }
public async override Task WaitForDisconnectAsync(CancellationToken cancellationToken) { // We have to poll for disconnection by reading, PipeStream.IsConnected isn't reliable unless you // actually do a read - which will cause it to update its state. while (!cancellationToken.IsCancellationRequested && Stream.IsConnected) { await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); try { CompilerServerLogger.Log($"Before poking pipe {Identifier}."); await Stream.ReadAsync(Array.Empty <byte>(), 0, 0, cancellationToken); CompilerServerLogger.Log($"After poking pipe {Identifier}."); } catch (OperationCanceledException) { } catch (Exception e) { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration of the CompilerServerLogger.LogException(e, $"Error poking pipe {Identifier}."); } } }
public async override Task <Connection> WaitForConnectionAsync(CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This doesn't block, but could fail // in certain circumstances, such as the OS refusing to create the pipe for some reason // or the pipe was disconnected before we starting listening. // // Also, note that we're waiting on CoreFx to implement some security features for us. // https://github.com/dotnet/corefx/issues/24040 var pipeStream = new NamedPipeServerStream( PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections. PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, PipeBufferSize, // Default input buffer PipeBufferSize); // Default output buffer CompilerServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || Memory.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); return(new NamedPipeConnection(pipeStream, GetNextIdentifier())); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
/// <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 async Task <NamedPipeServerStream> CreateListenTaskCore(CancellationToken cancellationToken) { // 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. CompilerServerLogger.Log("Constructing pipe '{0}'.", _pipeName); var pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough; var pipeStream = NamedPipeUtil.CreateServer( _pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, pipeOptions, PipeBufferSize, PipeBufferSize); CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", _pipeName); CompilerServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); return(pipeStream); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
/// <summary> /// Create the compilation request to send to the server process. /// </summary> internal static BuildRequest CreateRequest(uint requestId, string rawWorkingDirectory, string[] rawEnvironmentVariables, string rawCommandLineCommands, string rawResponseFileArguments) { string workingDirectory = CurrentDirectoryToUse(rawWorkingDirectory); string libDirectory = LibDirectoryToUse(rawEnvironmentVariables); string[] arguments = GetArguments(rawCommandLineCommands, rawResponseFileArguments); CompilerServerLogger.Log("BuildRequest: working directory='{0}'", workingDirectory); CompilerServerLogger.Log("BuildRequest: lib directory='{0}'", libDirectory); var requestArgs = ImmutableArray.CreateBuilder <BuildRequest.Argument>(arguments.Length + 1); requestArgs.Add(new BuildRequest.Argument(BuildProtocolConstants.ArgumentId_CurrentDirectory, 0, workingDirectory)); if (libDirectory != null) { requestArgs.Add(new BuildRequest.Argument(BuildProtocolConstants.ArgumentId_LibEnvVariable, 0, libDirectory)); } for (int i = 0; i < arguments.Length; ++i) { CompilerServerLogger.Log("BuildRequest: argument[{0}]='{1}'", i, arguments[i]); requestArgs.Add(new BuildRequest.Argument(BuildProtocolConstants.ArgumentId_CommandLineArgument, (uint)i, arguments[i])); } return(new BuildRequest(requestId, requestArgs.ToImmutable())); }
// The IsConnected property on named pipes does not detect when the client has disconnected // if we don't attempt any new I/O after the client disconnects. We start an async I/O here // which serves to check the pipe for disconnection. private async Task MonitorPipeForDisconnectionAsync(PipeStream pipeStream, CancellationToken cancellationToken) { byte[] buffer = new byte[0]; while (!cancellationToken.IsCancellationRequested && pipeStream.IsConnected) { CompilerServerLogger.Log("Before poking pipe."); try { await pipeStream.ReadAsync(buffer, 0, 0).ConfigureAwait(continueOnCapturedContext: false); } catch (ObjectDisposedException) { // Another thread may have closed the stream already. Not a problem. CompilerServerLogger.Log("Pipe has already been closed."); return; } CompilerServerLogger.Log("After poking pipe."); // Wait a hundredth of a second before trying again await Task.Delay(10).ConfigureAwait(false); } if (!cancellationToken.IsCancellationRequested) { throw new PipeBrokenException(); } }
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 responseTask = BuildClient.TryRunServerCompilation( Language, TryGetClientDir() ?? Path.GetDirectoryName(pathToTool), CurrentDirectoryToUse(), GetArguments(commandLineCommands, responseFileCommands), _sharedCompileCts.Token, libEnvVariable: LibDirectoryToUse()); 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 virtual int RunServerCore(string pipeName, IClientConnectionHost connectionHost, IDiagnosticListener listener, TimeSpan?keepAlive, CancellationToken cancellationToken) { CompilerServerLogger.Log("Keep alive timeout is: {0} milliseconds.", keepAlive?.TotalMilliseconds ?? 0); FatalError.Handler = FailFast.OnFatalException; var dispatcher = new ServerDispatcher(connectionHost, listener); dispatcher.ListenAndDispatchConnections(keepAlive, cancellationToken); return(CommonCompiler.Succeeded); }
/// <summary> /// Get the command line arguments to pass to the compiler. /// </summary> private string[] GetArguments(string commandLineCommands, string responseFileCommands) { CompilerServerLogger.Log($"CommandLine = '{commandLineCommands}'"); CompilerServerLogger.Log($"BuildResponseFile = '{responseFileCommands}'"); var commandLineArguments = CommandLineParser.SplitCommandLineIntoArguments(commandLineCommands, removeHashComments: true); var responseFileArguments = CommandLineParser.SplitCommandLineIntoArguments(responseFileCommands, removeHashComments: true); return(commandLineArguments.Concat(responseFileArguments).ToArray()); }
/// <summary> /// Connect to the given process id and return a pipe. /// Throws on cancellation. /// </summary> /// <param name="processId">Proces id to try to connect to.</param> /// <param name="timeoutMs">Timeout to allow in connecting to process.</param> /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param> /// <param name="pipeStream"> /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure /// (including IOException). /// </param> /// <returns> /// </returns> private bool TryConnectToProcess(int processId, int timeoutMs, CancellationToken cancellationToken, out NamedPipeClientStream pipeStream) { pipeStream = null; cancellationToken.ThrowIfCancellationRequested(); try { // Machine-local named pipes are named "\\.\pipe\<pipename>". // We use the pipe name followed by the process id. // The NamedPipeClientStream class handles the "\\.\pipe\" part for us. string pipeName = BuildProtocolConstants.PipeName + processId.ToString(); CompilerServerLogger.Log("Attempt to open named pipe '{0}'", pipeName); pipeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); CompilerServerLogger.Log("Attempt to connect named pipe '{0}'", pipeName); pipeStream.Connect(timeoutMs); CompilerServerLogger.Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // Verify that we own the pipe. SecurityIdentifier currentIdentity = WindowsIdentity.GetCurrent().Owner; PipeSecurity remoteSecurity = pipeStream.GetAccessControl(); IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier)); if (remoteOwner != currentIdentity) { CompilerServerLogger.Log("Owner of named pipe is incorrect"); return(false); } return(true); } catch (IOException e) { CompilerServerLogger.LogException(e, "Opening/connecting named pipe"); return(false); } catch (TimeoutException e) { CompilerServerLogger.LogException(e, "Timeout while opening/connecting named pipe"); return(false); } }
public override void Close() { CompilerServerLogger.Log($"Pipe {LoggingIdentifier}: Closing."); try { _pipeStream.Close(); } catch (Exception e) { // The client connection failing to close isn't fatal to the server process. It is simply a client // for which we can no longer communicate and that's okay because the Close method indicates we are // done with the client already. var msg = string.Format($"Pipe {LoggingIdentifier}: Error closing pipe."); CompilerServerLogger.LogException(e, msg); } }
/// <summary> /// Get the command line arguments to pass to the compiler. /// </summary> /// <returns></returns> private static string[] GetArguments(string commandLineCommands, string responseFileCommands) { CompilerServerLogger.Log("CommandLine='{0}'", commandLineCommands); CompilerServerLogger.Log("BuildResponseFile='{0}'", responseFileCommands); string[] commandLineArguments = CommandLineSplitter.SplitCommandLine(commandLineCommands); string[] responseFileArguments = CommandLineSplitter.SplitCommandLine(responseFileCommands); int numCommandLineArguments = commandLineArguments.Length; int numResponseFileArguments = responseFileArguments.Length; string[] result = new string[numCommandLineArguments + numResponseFileArguments]; Array.Copy(commandLineArguments, result, numCommandLineArguments); Array.Copy(responseFileArguments, 0, result, numCommandLineArguments, numResponseFileArguments); return(result); }
protected override void Dispose(bool disposing) { CompilerServerLogger.Log($"Pipe {Identifier}: Closing."); try { Stream.Dispose(); } catch (Exception ex) { // The client connection failing to close isn't fatal to the server process. It is simply a client // for which we can no longer communicate and that's okay because the Close method indicates we are // done with the client already. var message = string.Format($"Pipe {Identifier}: Error closing pipe."); CompilerServerLogger.LogException(ex, message); } }
/// <summary> /// Get all process IDs on the current machine that have executable names matching /// "expectedProcessName". /// </summary> private List <int> GetAllProcessIds() { List <int> processIds = new List <int>(); // Get all the processes with the right base name. Process[] allProcesses = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(serverExecutablePath)); CompilerServerLogger.Log("Found {0} existing processes with matching base name", allProcesses.Length); foreach (Process process in allProcesses) { using (process) { try { var exeNameBuffer = new StringBuilder(MAX_PATH_SIZE); int pathSize = MAX_PATH_SIZE; string fullFileName = null; if (QueryFullProcessImageName(process.Handle, 0, // Win32 path format exeNameBuffer, ref pathSize)) { fullFileName = exeNameBuffer.ToString(); CompilerServerLogger.Log("Process file path: {0}", fullFileName); } if (fullFileName != null && string.Equals(fullFileName, serverExecutablePath, StringComparison.OrdinalIgnoreCase)) { processIds.Add(process.Id); } } // If any exception occurs (e.g., accessing the process handle // fails because the process is exiting), we should simply fail // to connect and let the loop fall through catch { } } } return(processIds); }
// Based on: https://github.com/dotnet/roslyn/blob/14aed138a01c448143b9acf0fe77a662e3dfe2f4/src/Compilers/Shared/BuildServerConnection.cs#L290 public static async Task <Client> ConnectAsync(string pipeName, TimeSpan?timeout, CancellationToken cancellationToken) { var timeoutMilliseconds = timeout == null ? Timeout.Infinite : (int)timeout.Value.TotalMilliseconds; try { // Machine-local named pipes are named "\\.\pipe\<pipename>". // We use the SHA1 of the directory the compiler exes live in as the pipe name. // The NamedPipeClientStream class handles the "\\.\pipe\" part for us. CompilerServerLogger.Log("Attempt to open named pipe '{0}'", pipeName); var stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); CompilerServerLogger.Log("Attempt to connect named pipe '{0}'", pipeName); try { await stream.ConnectAsync(timeoutMilliseconds, cancellationToken); } catch (Exception e) when(e is IOException || e is TimeoutException) { // Note: IOException can also indicate timeout. // From docs: // - TimeoutException: Could not connect to the server within the specified timeout period. // - IOException: The server is connected to another client and the time-out period has expired. CompilerServerLogger.Log($"Connecting to server timed out after {timeoutMilliseconds} ms"); return(null); } CompilerServerLogger.Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // The original code in Roslyn checks that the server pipe is owned by the same user for security. // We plan to rely on the BCL for this but it's not yet implemented: // See https://github.com/dotnet/corefx/issues/25427 return(new NamedPipeClient(stream)); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { CompilerServerLogger.LogException(e, "Exception while connecting to process"); return(null); } }
/// <summary> /// Create a new instance of the server process, returning its process ID. /// Returns 0 on failure. /// </summary> private int CreateNewServerProcess() { // As far as I can tell, there isn't a way to use the Process class to // create a process with no stdin/stdout/stderr, so we use P/Invoke. // This code was taken from MSBuild task starting code. NativeMethods.STARTUPINFO startInfo = new NativeMethods.STARTUPINFO(); startInfo.cb = Marshal.SizeOf(startInfo); startInfo.hStdError = NativeMethods.InvalidHandle; startInfo.hStdInput = NativeMethods.InvalidHandle; startInfo.hStdOutput = NativeMethods.InvalidHandle; startInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES; uint dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS | NativeMethods.CREATE_NO_WINDOW; NativeMethods.PROCESS_INFORMATION processInfo = new NativeMethods.PROCESS_INFORMATION(); CompilerServerLogger.Log("Attempting to create process '{0}'", serverExecutablePath); bool success = NativeMethods.CreateProcess( serverExecutablePath, null, // command line NativeMethods.NullPtr, // process attributes NativeMethods.NullPtr, // thread attributes false, // don't inherit handles dwCreationFlags, NativeMethods.NullPtr, // inherit environment Path.GetDirectoryName(serverExecutablePath), // current directory ref startInfo, out processInfo); if (success) { CompilerServerLogger.Log("Successfully created process with process id {0}", processInfo.dwProcessId); NativeMethods.CloseHandle(processInfo.hProcess); NativeMethods.CloseHandle(processInfo.hThread); return(processInfo.dwProcessId); } else { CompilerServerLogger.Log("Failed to create process. GetLastError={0}", Marshal.GetLastWin32Error()); return(0); } }
private Task <BuildResponse> ExecuteRequestAsync(BuildRequest buildRequest, CancellationToken cancellationToken) { Func <BuildResponse> func = () => { CompilerServerLogger.Log("Begin processing request"); // TODO: this is where we actually process the request. // Take a look at BuildProtocolUtil var response = (BuildResponse)null; CompilerServerLogger.Log("End processing request"); return(response); }; var task = new Task <BuildResponse>(func, cancellationToken, TaskCreationOptions.LongRunning); task.Start(); return(task); }
private async Task <BuildResponse> DoCompilationAsync(NamedPipeClientStream pipeStream, BuildRequest req, CancellationToken cancellationToken) { using (pipeStream) { try { // Start a monitor that cancels if the pipe closes on us var monitorCancellation = new CancellationTokenSource(); Task disconnectMonitor = MonitorPipeForDisconnectionAsync(pipeStream, monitorCancellation.Token); // Write the request. CompilerServerLogger.Log("Writing request"); await req.WriteAsync(pipeStream, cancellationToken).ConfigureAwait(false); // Read the response. CompilerServerLogger.Log("Reading response"); BuildResponse response = await BuildResponse.ReadAsync(pipeStream, cancellationToken).ConfigureAwait(false); // Stop monitoring pipe monitorCancellation.Cancel(throwOnFirstException: true); await disconnectMonitor.ConfigureAwait(false); Debug.Assert(response != null); CompilerServerLogger.Log("BuildResponse received; exit code={0}", response.ReturnCode); return(response); } catch (PipeBrokenException e) { CompilerServerLogger.LogException(e, "Server process died; pipe broken."); return(null); } catch (ObjectDisposedException e) { CompilerServerLogger.LogException(e, "Pipe stream unexpectedly disposed"); return(null); } } }
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 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); } 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); }
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); } 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); }
private void Log(string message) { CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message); }
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); }
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); }
/// <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); }