Handle Mutex creation exceptions
This commit is contained in:
parent
bc72470c21
commit
03938dfd95
|
|
@ -34,37 +34,51 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
protected override Task<int> ExecuteCoreAsync()
|
||||
{
|
||||
// Make sure there's only one server with the same identity at a time.
|
||||
using (var mutex = new Mutex(initiallyOwned: true, name: MutexName.GetServerMutexName(Pipe.Value()), createdNew: out var holdsMutex))
|
||||
var serverMutexName = MutexName.GetServerMutexName(Pipe.Value());
|
||||
Mutex serverMutex = null;
|
||||
var holdsMutex = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!holdsMutex)
|
||||
serverMutex = new Mutex(initiallyOwned: true, name: serverMutexName, createdNew: out holdsMutex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 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
|
||||
|
||||
Error.Write($"Server mutex creation failed. {ex.Message}");
|
||||
|
||||
return Task.FromResult(-1);
|
||||
}
|
||||
|
||||
if (!holdsMutex)
|
||||
{
|
||||
// Another server is running, just exit.
|
||||
Error.Write("Another server already running...");
|
||||
return Task.FromResult(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TimeSpan? keepAlive = null;
|
||||
if (KeepAlive.HasValue() && int.TryParse(KeepAlive.Value(), out var result))
|
||||
{
|
||||
// Another server is running, just exit.
|
||||
Error.Write("Another server already running...");
|
||||
return Task.FromResult(1);
|
||||
// Keep alive times are specified in seconds
|
||||
keepAlive = TimeSpan.FromSeconds(result);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TimeSpan? keepAlive = null;
|
||||
if (KeepAlive.HasValue())
|
||||
{
|
||||
var value = KeepAlive.Value();
|
||||
if (int.TryParse(value, out var result))
|
||||
{
|
||||
// Keep alive times are specified in seconds
|
||||
keepAlive = TimeSpan.FromSeconds(result);
|
||||
}
|
||||
}
|
||||
var host = ConnectionHost.Create(Pipe.Value());
|
||||
|
||||
var host = ConnectionHost.Create(Pipe.Value());
|
||||
|
||||
var compilerHost = CompilerHost.Create();
|
||||
ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mutex.ReleaseMutex();
|
||||
}
|
||||
var compilerHost = CompilerHost.Create();
|
||||
ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive);
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverMutex.ReleaseMutex();
|
||||
serverMutex.Dispose();
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
|
|
|
|||
|
|
@ -23,13 +23,21 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
|
||||
public static bool WasServerMutexOpen(string mutexName)
|
||||
{
|
||||
var open = Mutex.TryOpenExisting(mutexName, out var mutex);
|
||||
if (open)
|
||||
Mutex mutex = null;
|
||||
var open = false;
|
||||
try
|
||||
{
|
||||
mutex.Dispose();
|
||||
return true;
|
||||
open = Mutex.TryOpenExisting(mutexName, out mutex);
|
||||
}
|
||||
return false;
|
||||
catch
|
||||
{
|
||||
// In the case an exception occured trying to open the Mutex then
|
||||
// the assumption is that it's not open.
|
||||
}
|
||||
|
||||
mutex?.Dispose();
|
||||
|
||||
return open;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -129,44 +137,63 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
|
||||
var clientMutexName = MutexName.GetClientMutexName(pipeName);
|
||||
Task<Client> pipeTask = null;
|
||||
using (var clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out var holdsMutex))
|
||||
|
||||
Mutex clientMutex = null;
|
||||
var holdsMutex = false;
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!holdsMutex)
|
||||
{
|
||||
try
|
||||
{
|
||||
holdsMutex = clientMutex.WaitOne(timeoutNewProcess);
|
||||
|
||||
if (!holdsMutex)
|
||||
{
|
||||
return new RejectedServerResponse();
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
holdsMutex = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an already running server
|
||||
var serverMutexName = MutexName.GetServerMutexName(pipeName);
|
||||
var wasServerRunning = WasServerMutexOpen(serverMutexName);
|
||||
var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;
|
||||
|
||||
if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, debug))
|
||||
{
|
||||
pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken);
|
||||
}
|
||||
clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out holdsMutex);
|
||||
}
|
||||
finally
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (holdsMutex)
|
||||
// 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
|
||||
|
||||
ServerLogger.LogException(ex, "Client mutex creation failed.");
|
||||
|
||||
return new RejectedServerResponse();
|
||||
}
|
||||
|
||||
if (!holdsMutex)
|
||||
{
|
||||
try
|
||||
{
|
||||
clientMutex.ReleaseMutex();
|
||||
holdsMutex = clientMutex.WaitOne(timeoutNewProcess);
|
||||
|
||||
if (!holdsMutex)
|
||||
{
|
||||
return new RejectedServerResponse();
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
holdsMutex = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an already running server
|
||||
var serverMutexName = MutexName.GetServerMutexName(pipeName);
|
||||
var wasServerRunning = WasServerMutexOpen(serverMutexName);
|
||||
var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;
|
||||
|
||||
if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, debug))
|
||||
{
|
||||
pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (holdsMutex)
|
||||
{
|
||||
clientMutex?.ReleaseMutex();
|
||||
}
|
||||
|
||||
clientMutex?.Dispose();
|
||||
}
|
||||
|
||||
if (pipeTask != null)
|
||||
|
|
|
|||
|
|
@ -103,5 +103,35 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.Views.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("SimpleMvc")]
|
||||
public async Task Build_ServerConnectionMutexCreationFails_FallsBackToInProcessRzc()
|
||||
{
|
||||
// Use a pipe name longer that 260 characters to make the Mutex constructor throw.
|
||||
var pipeName = new string('P', 261);
|
||||
var result = await DotnetMSBuild(
|
||||
"Build",
|
||||
"/p:_RazorForceBuildServer=true",
|
||||
buildServerPipeName: pipeName);
|
||||
|
||||
// We expect this to fail because we don't allow it to fallback to in process execution.
|
||||
Assert.BuildFailed(result);
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.Views.dll");
|
||||
|
||||
// Try to build again without forcing it to run on build server.
|
||||
result = await DotnetMSBuild(
|
||||
"Build",
|
||||
"/p:_RazorForceBuildServer=false",
|
||||
buildServerPipeName: pipeName);
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
|
||||
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.Views.dll");
|
||||
|
||||
// Note: We don't need to handle server clean up here because it will fail before
|
||||
// it reaches server creation part.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
bool suppressRestore = false,
|
||||
bool suppressTimeout = false,
|
||||
bool suppressBuildServer = false,
|
||||
string buildServerPipeName = null,
|
||||
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet)
|
||||
{
|
||||
var timeout = suppressTimeout ? (TimeSpan?)Timeout.InfiniteTimeSpan : null;
|
||||
|
|
@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
if (!suppressBuildServer)
|
||||
{
|
||||
buildArgumentList.Add($"/p:_RazorBuildServerPipeName={BuildServer.PipeName}");
|
||||
buildArgumentList.Add($"/p:_RazorBuildServerPipeName={buildServerPipeName ?? BuildServer.PipeName}");
|
||||
}
|
||||
|
||||
buildArgumentList.Add($"/t:{target} /p:Configuration={Configuration} {args}");
|
||||
|
|
|
|||
Loading…
Reference in New Issue