Skip to content

Commit

Permalink
Merge pull request #5613 from ostorc/rarAsService/node
Browse files Browse the repository at this point in the history
This PR provides MSBuild necessary infrastructure to start up new RAR node and connect to it.

Implements #5555
  • Loading branch information
Ondřej Štorc authored Sep 1, 2020
2 parents d58e2b7 + d82b3f3 commit 51a1071
Show file tree
Hide file tree
Showing 64 changed files with 1,180 additions and 228 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
NU5125: Arcade uses licenseUrl when doing pack, which now causes NU5125 warning. This disables that warning until arcade can switch over.
-->

<NoWarn>$(NoWarn);NU1603;NU5105;NU5125;1701;1702</NoWarn>
<NoWarn>$(NoWarn);NU1603;NU5105;NU5125;1701;1702;VSTHRD002;VSTHRD105;VSTHRD110;VSTHRD200</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug-MONO'">
Expand Down
4 changes: 3 additions & 1 deletion eng/Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="$(MicrosoftNetCompilersToolsetVersion)" />
<PackageReference Update="Microsoft.VisualStudio.SDK.EmbedInteropTypes" Version="15.0.15" />
<PackageReference Update="Microsoft.VisualStudio.Setup.Configuration.Interop" Version="1.16.30" />
<PackageReference Update="Microsoft.Win32.Registry" Version="4.3.0" />
<PackageReference Update="Microsoft.Win32.Registry" Version="4.6.0" />
<PackageReference Update="NuGet.Build.Tasks" Version="$(NuGetBuildTasksVersion)" />
<PackageReference Update="NuGet.Frameworks" Version="$(NuGetBuildTasksVersion)" />
<PackageReference Update="PdbGit" Version="3.0.41" />
Expand Down Expand Up @@ -52,6 +52,8 @@
<PackageReference Update="xunit.assert" Version="$(XUnitVersion)" />
<PackageReference Update="xunit.console" Version="$(XUnitVersion)" />
<PackageReference Update="xunit.core" Version="$(XUnitVersion)" />
<PackageReference Update="StreamJsonRpc" Version="2.4.48" />
<PackageReference Update="Newtonsoft.Json" Version="12.*" PrivateAssets="all" />
</ItemGroup>

<ItemGroup Condition="'$(DotNetBuildFromSource)' != 'true'">
Expand Down
3 changes: 3 additions & 0 deletions eng/Signing.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project>
<ItemGroup>
<FileSignInfo Include="Nerdbank.Streams.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="MessagePack.Annotations.dll" CertificateName="3PartySHA2" />
<ItemsToSign Include="$(VisualStudioSetupOutputPath)DevDivPackages\*.nupkg" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ public ResolveAssemblyReference() { }
public string TargetFrameworkVersion { get { throw null; } set { } }
public string TargetProcessorArchitecture { get { throw null; } set { } }
public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } }
public bool UseResolveAssemblyReferenceService { get { throw null; } set { } }
public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ public ResolveAssemblyReference() { }
public string TargetFrameworkVersion { get { throw null; } set { } }
public string TargetProcessorArchitecture { get { throw null; } set { } }
public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } }
public bool UseResolveAssemblyReferenceService { get { throw null; } set { } }
public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
Expand Down
6 changes: 6 additions & 0 deletions ref/Microsoft.Build/net/Microsoft.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,12 @@ internal ProjectTaskOutputPropertyInstance() { }
public string TaskParameter { get { throw null; } }
public override Microsoft.Build.Construction.ElementLocation TaskParameterLocation { get { throw null; } }
}
public sealed partial class RarNode
{
public RarNode() { }
public Microsoft.Build.Execution.NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out System.Exception shutdownException, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken)) { shutdownException = default(System.Exception); throw null; }
public Microsoft.Build.Execution.NodeEngineShutdownReason Run(out System.Exception shutdownException) { shutdownException = default(System.Exception); throw null; }
}
public partial class RequestedProjectState
{
public RequestedProjectState() { }
Expand Down
6 changes: 6 additions & 0 deletions ref/Microsoft.Build/netstandard/Microsoft.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,12 @@ internal ProjectTaskOutputPropertyInstance() { }
public string TaskParameter { get { throw null; } }
public override Microsoft.Build.Construction.ElementLocation TaskParameterLocation { get { throw null; } }
}
public sealed partial class RarNode
{
public RarNode() { }
public Microsoft.Build.Execution.NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out System.Exception shutdownException, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken)) { shutdownException = default(System.Exception); throw null; }
public Microsoft.Build.Execution.NodeEngineShutdownReason Run(out System.Exception shutdownException) { shutdownException = default(System.Exception); throw null; }
}
public partial class RequestedProjectState
{
public RequestedProjectState() { }
Expand Down
20 changes: 20 additions & 0 deletions src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,26 @@ private void PerformSchedulingActions(IEnumerable<ScheduleResponse> responses)
}
}

internal bool CreateRarNode()
{
// If the _buildParametrs is not set, we are in OutOfProc mode, so continue
// Else check if users specified that he want to use multiple nodes, if so use RARaaS
if (_buildParameters?.MaxNodeCount == 1)
return false;

string nodeLocation = _buildParameters?.NodeExeLocation ?? BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
if (string.IsNullOrEmpty(nodeLocation))
{
// Couldn't find a path to MSBuild.exe; can't create a new node.
return false;
}

bool nodeReuse = _buildParameters?.EnableNodeReuse ?? true;
bool lowPriority = _buildParameters?.LowPriority ?? false;
string commandLineArgs = $"/nologo /nodemode:3 /nodeReuse:{nodeReuse} /low:{lowPriority}";
return NodeProviderOutOfProcBase.LaunchNode(nodeLocation, commandLineArgs) != -1;
}

/// <summary>
/// Completes a submission using the specified overall results.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ internal NodeEndpointOutOfProc(
/// </summary>
protected override Handshake GetHandshake()
{
return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, is64Bit: EnvironmentUtilities.Is64BitProcess, nodeReuse: _enableReuse, lowPriority: _lowPriority));
return new Handshake(CommunicationsUtilities.GetHandshakeOptions(
taskHost: false,
is64Bit: EnvironmentUtilities.Is64BitProcess,
nodeReuse: _enableReuse,
lowPriority: _lowPriority));
}

#region Structs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ public int AvailableNodes
/// </summary>
/// <param name="enableNodeReuse">Is reuse of build nodes allowed?</param>
/// <param name="enableLowPriority">Is the build running at low priority?</param>
internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority)
/// <param name="specialNode">/Indicates if node can not accept standard MSBuild work</param>
internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority, bool specialNode)
{
CommunicationsUtilities.Trace("MSBUILDNODEHANDSHAKESALT=\"{0}\", msbuildDirectory=\"{1}\", enableNodeReuse={2}, enableLowPriority={3}", Traits.MSBuildNodeHandshakeSalt, BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32, enableNodeReuse, enableLowPriority);
return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, is64Bit: EnvironmentUtilities.Is64BitProcess));
return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, specialNode: specialNode, is64Bit: EnvironmentUtilities.Is64BitProcess));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,16 @@ protected void ShutdownAllNodes(bool nodeReuse, NodeContextTerminateDelegate ter
int timeout = 30;

// Attempt to connect to the process with the handshake without low priority.
Stream nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, false));
Stream nodeStream = NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: false));

if (nodeStream == null)
{
// If we couldn't connect attempt to connect to the process with the handshake including low priority.
nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, true));
}
// If we couldn't connect attempt to connect to the process with the handshake including low priority.
nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: false));

// Attempt to connect to the non-worker process
// Attempt to connect to the process with the handshake without low priority.
nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: true));
// If we couldn't connect attempt to connect to the process with the handshake including low priority.
nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: true));

if (nodeStream != null)
{
Expand Down Expand Up @@ -194,7 +197,7 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
_processesToIgnore.Add(nodeLookupKey);

// Attempt to connect to each process in turn.
Stream nodeStream = TryConnectToProcess(nodeProcess.Id, 0 /* poll, don't wait for connections */, hostHandshake);
Stream nodeStream = NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, 0 /* poll, don't wait for connections */, hostHandshake);
if (nodeStream != null)
{
// Connection successful, use this node.
Expand Down Expand Up @@ -245,7 +248,7 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
// to the debugger process. Instead, use MSBUILDDEBUGONSTART=1

// Now try to connect to it.
Stream nodeStream = TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake);
Stream nodeStream = NamedPipeUtil.TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake);
if (nodeStream != null)
{
// Connection successful, use this node.
Expand Down Expand Up @@ -293,100 +296,10 @@ private string GetProcessesToIgnoreKey(Handshake hostHandshake, int nodeProcessI
return hostHandshake.ToString() + "|" + nodeProcessId.ToString(CultureInfo.InvariantCulture);
}

#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
// This code needs to be in a separate method so that we don't try (and fail) to load the Windows-only APIs when JIT-ing the code
// on non-Windows operating systems
private void ValidateRemotePipeSecurityOnWindows(NamedPipeClientStream nodeStream)
{
SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
#if FEATURE_PIPE_SECURITY
PipeSecurity remoteSecurity = nodeStream.GetAccessControl();
#else
var remoteSecurity = new PipeSecurity(nodeStream.SafePipeHandle, System.Security.AccessControl.AccessControlSections.Access |
System.Security.AccessControl.AccessControlSections.Owner | System.Security.AccessControl.AccessControlSections.Group);
#endif
IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
if (remoteOwner != identifier)
{
CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value);
throw new UnauthorizedAccessException();
}
}
#endif

/// <summary>
/// Attempts to connect to the specified process.
/// </summary>
private Stream TryConnectToProcess(int nodeProcessId, int timeout, Handshake handshake)
{
// Try and connect to the process.
string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + nodeProcessId);

NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous
#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY
| PipeOptions.CurrentUserOnly
#endif
);
CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId, pipeName, timeout);

try
{
nodeStream.Connect(timeout);

#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
if (NativeMethodsShared.IsWindows && !NativeMethodsShared.IsMono)
{
// Verify that the owner of the pipe is us. This prevents a security hole where a remote node has
// been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to
// us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can
// only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up
// remote node could set the owner to something else would also let it change owners on other objects, so
// this would be a security flaw upstream of us.
ValidateRemotePipeSecurityOnWindows(nodeStream);
}
#endif

int[] handshakeComponents = handshake.RetrieveHandshakeComponents();
for (int i = 0; i < handshakeComponents.Length; i++)
{
CommunicationsUtilities.Trace("Writing handshake part {0} to pipe {1}", i, pipeName);
nodeStream.WriteIntForHandshake(handshakeComponents[i]);
}

// This indicates that we have finished all the parts of our handshake; hopefully the endpoint has as well.
nodeStream.WriteEndOfHandshakeSignal();

CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName);

#if NETCOREAPP2_1 || MONO
nodeStream.ReadEndOfHandshakeSignal(true, timeout);
#else
nodeStream.ReadEndOfHandshakeSignal(true);
#endif
// We got a connection.
CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName);
return nodeStream;
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
// Can be:
// UnauthorizedAccessException -- Couldn't connect, might not be a node.
// IOException -- Couldn't connect, already in use.
// TimeoutException -- Couldn't connect, might not be a node.
// InvalidOperationException – Couldn’t connect, probably a different build
CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd());

// If we don't close any stream, we might hang up the child
nodeStream?.Dispose();
}

return null;
}

/// <summary>
/// Creates a new MSBuild process
/// </summary>
private int LaunchNode(string msbuildLocation, string commandLineArgs)
internal static int LaunchNode(string msbuildLocation, string commandLineArgs)
{
// Should always have been set already.
ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, nameof(msbuildLocation));
Expand Down
33 changes: 32 additions & 1 deletion src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Pipes;
#if FEATURE_APPDOMAIN
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting;
Expand All @@ -23,6 +24,7 @@
using Microsoft.Build.BackEnd.Components.Caching;
using System.Reflection;
using Microsoft.Build.Eventing;
using Microsoft.Build.Internal;

namespace Microsoft.Build.BackEnd
{
Expand All @@ -34,7 +36,7 @@ internal class TaskHost :
#if FEATURE_APPDOMAIN
MarshalByRefObject,
#endif
IBuildEngine7
IBuildEngine7, IRarBuildEngine
{
/// <summary>
/// True if the "secret" environment variable MSBUILDNOINPROCNODE is set.
Expand Down Expand Up @@ -983,5 +985,34 @@ private void VerifyActiveProxy()
{
ErrorUtilities.VerifyThrow(_activeProxy, "Attempted to use an inactive task host.");
}

/// <summary>
/// Initialize new RAR node
/// </summary>
bool IRarBuildEngine.CreateRarNode()
{
return BuildManager.DefaultBuildManager.CreateRarNode();
}

/// <summary>
/// Provides RAR node name for current configuration
/// </summary>
string IRarBuildEngine.GetRarPipeName()
{
BuildParameters parameters = _host.BuildParameters;
return CommunicationsUtilities.GetRarPipeName(parameters.EnableNodeReuse, parameters.LowPriority);
}

/// <summary>
/// Constructs <seealso cref="NamedPipeClientStream"/>
/// </summary>
NamedPipeClientStream IRarBuildEngine.GetRarClientStream(string pipeName, int timeout)
{
BuildParameters parameters = _host.BuildParameters;
Handshake handshake = NodeProviderOutOfProc.GetHandshake(enableNodeReuse: parameters.EnableNodeReuse,
enableLowPriority: parameters.LowPriority, specialNode: true);

return NamedPipeUtil.TryConnectToProcess(pipeName, timeout, handshake);
}
}
}
Loading

0 comments on commit 51a1071

Please sign in to comment.