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()
|
protected override Task<int> ExecuteCoreAsync()
|
||||||
{
|
{
|
||||||
// Make sure there's only one server with the same identity at a time.
|
// 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.
|
// Keep alive times are specified in seconds
|
||||||
Error.Write("Another server already running...");
|
keepAlive = TimeSpan.FromSeconds(result);
|
||||||
return Task.FromResult(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
var host = ConnectionHost.Create(Pipe.Value());
|
||||||
{
|
|
||||||
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 compilerHost = CompilerHost.Create();
|
||||||
|
ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive);
|
||||||
var compilerHost = CompilerHost.Create();
|
}
|
||||||
ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive);
|
finally
|
||||||
}
|
{
|
||||||
finally
|
serverMutex.ReleaseMutex();
|
||||||
{
|
serverMutex.Dispose();
|
||||||
mutex.ReleaseMutex();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,21 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
|
|
||||||
public static bool WasServerMutexOpen(string mutexName)
|
public static bool WasServerMutexOpen(string mutexName)
|
||||||
{
|
{
|
||||||
var open = Mutex.TryOpenExisting(mutexName, out var mutex);
|
Mutex mutex = null;
|
||||||
if (open)
|
var open = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
mutex.Dispose();
|
open = Mutex.TryOpenExisting(mutexName, out mutex);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
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>
|
/// <summary>
|
||||||
|
|
@ -129,44 +137,63 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
|
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
|
||||||
var clientMutexName = MutexName.GetClientMutexName(pipeName);
|
var clientMutexName = MutexName.GetClientMutexName(pipeName);
|
||||||
Task<Client> pipeTask = null;
|
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
|
try
|
||||||
{
|
{
|
||||||
if (!holdsMutex)
|
clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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)
|
if (pipeTask != null)
|
||||||
|
|
|
||||||
|
|
@ -103,5 +103,35 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
|
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
|
||||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.Views.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 suppressRestore = false,
|
||||||
bool suppressTimeout = false,
|
bool suppressTimeout = false,
|
||||||
bool suppressBuildServer = false,
|
bool suppressBuildServer = false,
|
||||||
|
string buildServerPipeName = null,
|
||||||
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet)
|
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet)
|
||||||
{
|
{
|
||||||
var timeout = suppressTimeout ? (TimeSpan?)Timeout.InfiniteTimeSpan : null;
|
var timeout = suppressTimeout ? (TimeSpan?)Timeout.InfiniteTimeSpan : null;
|
||||||
|
|
@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
|
|
||||||
if (!suppressBuildServer)
|
if (!suppressBuildServer)
|
||||||
{
|
{
|
||||||
buildArgumentList.Add($"/p:_RazorBuildServerPipeName={BuildServer.PipeName}");
|
buildArgumentList.Add($"/p:_RazorBuildServerPipeName={buildServerPipeName ?? BuildServer.PipeName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
buildArgumentList.Add($"/t:{target} /p:Configuration={Configuration} {args}");
|
buildArgumentList.Add($"/t:{target} /p:Configuration={Configuration} {args}");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue