Merge branch 'release/2.1' into dev

This commit is contained in:
Ajay Bhargav Baaskaran 2018-03-28 18:26:25 -07:00
commit ad3048fcc3
5 changed files with 136 additions and 64 deletions

View File

@ -42,8 +42,8 @@
<NETStandardLibrary20PackageVersion>2.0.1</NETStandardLibrary20PackageVersion> <NETStandardLibrary20PackageVersion>2.0.1</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonPackageVersion>11.0.1</NewtonsoftJsonPackageVersion> <NewtonsoftJsonPackageVersion>11.0.1</NewtonsoftJsonPackageVersion>
<StreamJsonRpcPackageVersion>1.1.92</StreamJsonRpcPackageVersion> <StreamJsonRpcPackageVersion>1.1.92</StreamJsonRpcPackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0-preview2-26313-01</SystemDiagnosticsDiagnosticSourcePackageVersion> <SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0-preview2-26326-04</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemValueTuplePackageVersion>4.5.0-preview2-26313-01</SystemValueTuplePackageVersion> <SystemValueTuplePackageVersion>4.5.0-preview2-26326-04</SystemValueTuplePackageVersion>
<VisualStudio_NewtonsoftJsonPackageVersion>9.0.1</VisualStudio_NewtonsoftJsonPackageVersion> <VisualStudio_NewtonsoftJsonPackageVersion>9.0.1</VisualStudio_NewtonsoftJsonPackageVersion>
<VSIX_MicrosoftCodeAnalysisCommonPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCommonPackageVersion> <VSIX_MicrosoftCodeAnalysisCommonPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCommonPackageVersion>
<VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion> <VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>

View File

@ -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);

View File

@ -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)

View File

@ -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.
}
} }
} }

View File

@ -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}");