Example #1
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.
        }
Example #2
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);
        }
Example #3
0
        public static int Main(string[] args)
        {
            NameValueCollection appSettings;

            try
            {
#if NET472
                appSettings = System.Configuration.ConfigurationManager.AppSettings;
#else
                // Do not use AppSettings on non-desktop platforms
                appSettings = new NameValueCollection();
#endif
            }
            catch (Exception ex)
            {
                // It is possible for AppSettings to throw when the application or machine configuration
                // is corrupted.  This should not prevent the server from starting, but instead just revert
                // to the default configuration.
                appSettings = new NameValueCollection();
                CompilerServerLogger.LogException(ex, "Error loading application settings");
            }

            try
            {
                var controller = new DesktopBuildServerController(appSettings);
                return(controller.Run(args));
            }
            catch (FileNotFoundException e)
            {
                // Assume the exception was the result of a missing compiler assembly.
                LogException(e);
            }
            catch (TypeInitializationException e) when(e.InnerException is FileNotFoundException)
            {
                // Assume the exception was the result of a missing compiler assembly.
                LogException((FileNotFoundException)e.InnerException);
            }
            return(CommonCompiler.Failed);
        }
        public static bool Check(string baseDirectory, IEnumerable <CommandLineAnalyzerReference> analyzerReferences, IAnalyzerAssemblyLoader loader, IEnumerable <string> ignorableReferenceNames = null)
        {
            if (ignorableReferenceNames == null)
            {
                ignorableReferenceNames = s_defaultIgnorableReferenceNames;
            }

            try
            {
                CompilerServerLogger.Log("Begin Analyzer Consistency Check");
                return(CheckCore(baseDirectory, analyzerReferences, loader, ignorableReferenceNames));
            }
            catch (Exception e)
            {
                CompilerServerLogger.LogException(e, "Analyzer Consistency Check");
                return(false);
            }
            finally
            {
                CompilerServerLogger.Log("End Analyzer Consistency Check");
            }
        }
Example #5
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);
        }
Example #6
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);
        }
Example #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 buildPaths = new BuildPaths(
                        clientDir: TryGetClientDir() ?? Path.GetDirectoryName(pathToTool),
                        // MSBuild doesn't need the .NET SDK directory
                        sdkDir: null,
                        workingDir: CurrentDirectoryToUse());

                    var responseTask = BuildClientShim.RunServerCompilation(
                        Language,
                        GetArguments(commandLineCommands, responseFileCommands).ToList(),
                        buildPaths,
                        keepAlive: null,
                        libEnvVariable: LibDirectoryToUse(),
                        cancellationToken: _sharedCompileCts.Token);

                    responseTask.Wait(_sharedCompileCts.Token);

                    var response = responseTask.Result;
                    if (response != null)
                    {
                        ExitCode = HandleResponse(response, pathToTool, responseFileCommands, commandLineCommands);
                    }
                    else
                    {
                        ExitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
                    }
                }
                catch (OperationCanceledException)
                {
                    ExitCode = 0;
                }
                catch (Exception e)
                {
                    Log.LogErrorWithCodeFromResources("Compiler_UnexpectedException");
                    LogErrorOutput(e.ToString());
                    ExitCode = -1;
                }
            }
            return(ExitCode);
        }
Example #8
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);
        }
Example #9
0
 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
 {
     using var logger = new CompilerServerLogger($"MSBuild {Process.GetCurrentProcess().Id}");
     return(ExecuteTool(pathToTool, responseFileCommands, commandLineCommands, logger));
 }
Example #10
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);
        }
        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);
        }
Example #12
0
 internal void LogError(string message)
 {
     CompilerServerLogger.LogError(message);
     Log.LogError(message);
 }
Example #13
0
 private static void LogException(FileNotFoundException e)
 {
     CompilerServerLogger.LogException(e, "File not found");
 }
Example #14
0
 private void LogException(Exception e, string message)
 {
     CompilerServerLogger.LogException(e, string.Format("Client {0}: {1}", _loggingIdentifier, message));
 }
Example #15
0
 private void Log(string message)
 {
     CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message);
 }
Example #16
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));
                }
            }
Example #17
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);
        }
Example #18
0
        private const int TimeOutMsNewProcess      = 20000; // Spend up to 20s connecting to a new process, to allow time for it to start.

        public BuildClient()
        {
            CompilerServerLogger.Initialize("TSK");   // Mark log file entries as from MSBuild Task.
            this.serverExecutablePath = GetExpectedServerExecutablePath();
        }