// This code uses a Mutex.WaitOne / ReleaseMutex pairing. Both of these calls must occur on the same thread // or an exception will be thrown. This code lives in a separate non-async function to help ensure this // invariant doesn't get invalidated in the future by an `await` being inserted. static Task <NamedPipeClientStream> tryConnectToServer( string pipeName, BuildPathsAlt buildPaths, int?timeoutOverride, CreateServerFunc createServerFunc, CancellationToken cancellationToken) { var originalThreadId = Thread.CurrentThread.ManagedThreadId; var clientDir = buildPaths.ClientDirectory; var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess; var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess; Task <NamedPipeClientStream> pipeTask = null; IServerMutex clientMutex = null; try { var holdsMutex = false; try { var clientMutexName = GetClientMutexName(pipeName); clientMutex = OpenOrCreateMutex(clientMutexName, out holdsMutex); } catch { // The Mutex constructor can throw in certain cases. One specific example is docker containers // where the /tmp directory is restricted. In those cases there is no reliable way to execute // the server and we need to fall back to the command line. // // Example: https://github.com/dotnet/roslyn/issues/24124 return(null); } if (!holdsMutex) { try { holdsMutex = clientMutex.TryLock(timeoutNewProcess); if (!holdsMutex) { return(null); } } catch (AbandonedMutexException) { holdsMutex = true; } } // Check for an already running server var serverMutexName = GetServerMutexName(pipeName); bool wasServerRunning = WasServerMutexOpen(serverMutexName); var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess; if (wasServerRunning || createServerFunc(clientDir, pipeName)) { pipeTask = TryConnectToServerAsync(pipeName, timeout, cancellationToken); } return(pipeTask); } finally { try { clientMutex?.Dispose(); } catch (ApplicationException e) { var releaseThreadId = Thread.CurrentThread.ManagedThreadId; var message = $"ReleaseMutex failed. WaitOne Id: {originalThreadId} Release Id: {releaseThreadId}"; throw new Exception(message, e); } } }
internal static async Task <BuildResponse> RunServerCompilationCore( RequestLanguage language, List <string> arguments, BuildPathsAlt buildPaths, string pipeName, string keepAlive, string libEnvVariable, int?timeoutOverride, CreateServerFunc createServerFunc, CancellationToken cancellationToken) { if (pipeName == null) { return(new RejectedBuildResponse()); } if (buildPaths.TempDirectory == null) { return(new RejectedBuildResponse()); } // early check for the build hash. If we can't find it something is wrong; no point even trying to go to the server if (string.IsNullOrWhiteSpace(BuildProtocolConstants.GetCommitHash())) { return(new IncorrectHashBuildResponse()); } var clientDir = buildPaths.ClientDirectory; var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess; var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess; Task <NamedPipeClientStream> pipeTask = null; IServerMutex clientMutex = null; try { var holdsMutex = false; try { var clientMutexName = GetClientMutexName(pipeName); clientMutex = OpenOrCreateMutex(clientMutexName, out holdsMutex); } catch { // The Mutex constructor can throw in certain cases. One specific example is docker containers // where the /tmp directory is restricted. In those cases there is no reliable way to execute // the server and we need to fall back to the command line. // // Example: https://github.com/dotnet/roslyn/issues/24124 return(new RejectedBuildResponse()); } if (!holdsMutex) { try { holdsMutex = clientMutex.TryLock(timeoutNewProcess); if (!holdsMutex) { return(new RejectedBuildResponse()); } } catch (AbandonedMutexException) { holdsMutex = true; } } // Check for an already running server var serverMutexName = GetServerMutexName(pipeName); bool wasServerRunning = WasServerMutexOpen(serverMutexName); var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess; if (wasServerRunning || createServerFunc(clientDir, pipeName)) { pipeTask = TryConnectToServerAsync(pipeName, timeout, cancellationToken); } } finally { clientMutex?.Dispose(); } if (pipeTask != null) { var pipe = await pipeTask.ConfigureAwait(false); if (pipe != null) { var request = BuildRequest.Create(language, buildPaths.WorkingDirectory, buildPaths.TempDirectory, BuildProtocolConstants.GetCommitHash(), arguments, keepAlive, libEnvVariable); return(await TryCompile(pipe, request, cancellationToken).ConfigureAwait(false)); } } return(new RejectedBuildResponse()); }