Exemplo n.º 1
0
        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 });
        }
Exemplo n.º 2
0
            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}.");
                    }
                }
            }
Exemplo n.º 3
0
            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.");
            }
Exemplo n.º 4
0
        /// <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.");
        }
Exemplo n.º 5
0
        /// <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()));
        }
Exemplo n.º 6
0
        // 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();
            }
        }
Exemplo n.º 7
0
        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);
        }
Exemplo n.º 8
0
        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);
        }
Exemplo n.º 9
0
        /// <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());
        }
Exemplo n.º 10
0
        /// <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);
            }
        }
Exemplo n.º 11
0
 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);
     }
 }
Exemplo n.º 12
0
        /// <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);
        }
Exemplo n.º 13
0
            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);
                }
            }
Exemplo n.º 14
0
        /// <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);
        }
Exemplo n.º 15
0
        // 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);
            }
        }
Exemplo n.º 16
0
        /// <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);
            }
        }
Exemplo n.º 17
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);
            }
Exemplo n.º 18
0
        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);
                }
            }
        }
Exemplo n.º 19
0
        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.
        }
Exemplo n.º 20
0
        /// <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);
        }
Exemplo n.º 21
0
        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");
            }
        }
Exemplo n.º 22
0
        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);
        }
Exemplo n.º 23
0
        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);
        }
Exemplo n.º 24
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));
                }
            }
Exemplo n.º 25
0
        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);
        }
Exemplo n.º 26
0
 private void Log(string message)
 {
     CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message);
 }
Exemplo n.º 27
0
        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);
        }
Exemplo n.º 28
0
        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);
        }
Exemplo n.º 29
0
        /// <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);
        }