diff --git a/Directory.Build.props b/Directory.Build.props index 1683a4c99e3..06f7144e812 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -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);NU1603;NU5105;NU5125;1701;1702 + $(NoWarn);NU1603;NU5105;NU5125;1701;1702;VSTHRD002;VSTHRD105;VSTHRD110;VSTHRD200 diff --git a/eng/Packages.props b/eng/Packages.props index a7844e3b8a1..727964ed0b9 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -17,7 +17,7 @@ - + @@ -52,6 +52,8 @@ + + diff --git a/eng/Signing.props b/eng/Signing.props index 83946d7c6e5..13912cb79a5 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -1,5 +1,8 @@ + + + \ No newline at end of file diff --git a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs index 4edef43d503..4acd75a5f5f 100644 --- a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs +++ b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs @@ -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; } } diff --git a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs index cc3fb5233ef..af96c3bbc19 100644 --- a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs +++ b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs @@ -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; } } diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs index 8daf09c374a..870bbe7458a 100644 --- a/ref/Microsoft.Build/net/Microsoft.Build.cs +++ b/ref/Microsoft.Build/net/Microsoft.Build.cs @@ -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() { } diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs index 23825be7aba..a47f5848144 100644 --- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs +++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs @@ -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() { } diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 0e59f2e1fd6..a78e25f2103 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2018,6 +2018,26 @@ private void PerformSchedulingActions(IEnumerable 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; + } + /// /// Completes a submission using the specified overall results. /// diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs index 132cb665bb1..221e575ac3d 100644 --- a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs @@ -54,7 +54,11 @@ internal NodeEndpointOutOfProc( /// 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 diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs index 11ea72ba505..50ca0c90a01 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs @@ -63,10 +63,11 @@ public int AvailableNodes /// /// Is reuse of build nodes allowed? /// Is the build running at low priority? - internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority) + /// /Indicates if node can not accept standard MSBuild work + 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)); } /// diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index 644d47e931d..8a681683154 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -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) { @@ -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. @@ -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. @@ -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 - - /// - /// Attempts to connect to the specified process. - /// - 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; - } - /// /// Creates a new MSBuild process /// - 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)); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 3e2f83c917a..074df8881d9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -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; @@ -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 { @@ -34,7 +36,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine7 + IBuildEngine7, IRarBuildEngine { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -983,5 +985,34 @@ private void VerifyActiveProxy() { ErrorUtilities.VerifyThrow(_activeProxy, "Attempted to use an inactive task host."); } + + /// + /// Initialize new RAR node + /// + bool IRarBuildEngine.CreateRarNode() + { + return BuildManager.DefaultBuildManager.CreateRarNode(); + } + + /// + /// Provides RAR node name for current configuration + /// + string IRarBuildEngine.GetRarPipeName() + { + BuildParameters parameters = _host.BuildParameters; + return CommunicationsUtilities.GetRarPipeName(parameters.EnableNodeReuse, parameters.LowPriority); + } + + /// + /// Constructs + /// + 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); + } } } diff --git a/src/Build/BackEnd/Node/RarNode.cs b/src/Build/BackEnd/Node/RarNode.cs new file mode 100644 index 00000000000..888c24215d8 --- /dev/null +++ b/src/Build/BackEnd/Node/RarNode.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.CodeDom; +using System.Diagnostics; +using System.IO; +using System.IO.Pipes; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Execution +{ + public sealed class RarNode : INode + { + /// + /// The amount of time to wait for the client to connect to the host. + /// + private const int ClientConnectTimeout = 60000; + + /// + /// Fully qualified name of RarController, used for providing instance to + /// + private const string RarControllerName = "Microsoft.Build.Tasks.ResolveAssemblyReferences.Server.RarController, Microsoft.Build.Tasks.Core"; + + + /// + /// Timeout for node shutdown + /// + private static readonly TimeSpan NodeShutdownTimeout = TimeSpan.FromHours(1); + + public NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out Exception shutdownException, CancellationToken cancellationToken = default) + { + shutdownException = null; + using CancellationTokenSource cts = new CancellationTokenSource(); + string pipeName = CommunicationsUtilities.GetRarPipeName(nodeReuse, lowPriority); + Handshake handshake = NodeProviderOutOfProc.GetHandshake(enableNodeReuse: nodeReuse, + enableLowPriority: lowPriority, specialNode: true); + + IRarController controller = GetController(pipeName, handshake); + + Task rarTask = controller.StartAsync(cts.Token); + + Task msBuildShutdown = RunShutdownCheckAsync(handshake, cts.Token); + + // Timeout for node, limits lifetime of node to 1 hour + cts.CancelAfter(NodeShutdownTimeout); + int index; + try + { + // Wait for any of these tasks to finish: + // - rarTask can timeout (default is 15 minutes) + // - msBuildShutdown ends when it receives command to shutdown + // - node lifetime expires + index = Task.WaitAny(new Task[] { msBuildShutdown, rarTask }, cts.Token); + } + catch (OperationCanceledException e) + { + shutdownException = e; + return NodeEngineShutdownReason.Error; + } + + cts.Cancel(); + + if (index == 0) + { + // We know that the task completed, so we can get Result without waiting for it. + return msBuildShutdown.Result; + } + else + { + // RAR task can only exit with connection error or timeout + return NodeEngineShutdownReason.ConnectionFailed; + } + } + + private static IRarController GetController(string pipeName, Handshake handshake) + { + Type rarControllerType = Type.GetType(RarControllerName); + + Func streamFactory = NamedPipeUtil.CreateNamedPipeServer; + Func validateCallback = NamedPipeUtil.ValidateHandshake; + IRarController controller = Activator.CreateInstance(rarControllerType, pipeName, handshake, streamFactory, validateCallback, null) as IRarController; + + ErrorUtilities.VerifyThrow(controller != null, ResourceUtilities.GetResourceString("RarControllerReflectionError"), RarControllerName); + return controller; + } + + public NodeEngineShutdownReason Run(out Exception shutdownException) + { + return Run(false, false, out shutdownException); + } + + private async Task RunShutdownCheckAsync(Handshake handshake, CancellationToken cancellationToken = default) + { + string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + Process.GetCurrentProcess().Id); + + static async Task ReadAsync(Stream stream, byte[] buffer, int bytesToRead) + { + int totalBytesRead = 0; + while (totalBytesRead < bytesToRead) + { + int bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead - totalBytesRead); + if (bytesRead == 0) + { + return totalBytesRead; + } + totalBytesRead += bytesRead; + } + return totalBytesRead; + } + + // Most common path in this while loop in long run will be over the continue statement. + // This is happening because the MSBuild when starting new nodes is trying in some cases to reuse nodes (see nodeReuse switch). + // It is done by listing the MSBuild processes and then connecting to them and validating the handshake. + // In most cases for this loop it will fail, which will lead to hitting the continue statement. + // If we get over that, the MSBuild should send NodeBuildComplete packet, which will indicate that the engine is requesting to shutdown this node. + while (true) + { + if (cancellationToken.IsCancellationRequested) + return NodeEngineShutdownReason.BuildComplete; + + using NamedPipeServerStream serverStream = NamedPipeUtil.CreateNamedPipeServer(pipeName, maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances); + await serverStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); + + bool connected = NamedPipeUtil.ValidateHandshake(handshake, serverStream, ClientConnectTimeout); + if (!connected) + continue; + + // Header consists of: + // 1 byte - Packet type + // 4 bytes - packet length + byte[] header = new byte[5]; + int bytesRead = await ReadAsync(serverStream, header, header.Length).ConfigureAwait(false); + if (bytesRead != header.Length) + { + continue; + } + + NodePacketType packetType = (NodePacketType)Enum.ToObject(typeof(NodePacketType), header[0]); + // Packet type sent by Shutdown + if (packetType == NodePacketType.NodeBuildComplete) + { + // Body of NodeBuildComplete contains only one boolean (= 1 byte) + byte[] packetBody = new byte[sizeof(bool)]; + await serverStream.ReadAsync(packetBody, 0, packetBody.Length, cancellationToken).ConfigureAwait(false); + bool nodeReuse = Convert.ToBoolean(packetBody[0]); + + return nodeReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; + } + } + } + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 524046ab620..2f136f3ebf0 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -1,4 +1,4 @@ - + @@ -157,6 +157,7 @@ + diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index c2708c387c5..55a89a1db36 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1834,4 +1834,7 @@ Utilization: {0} Average Utilization: {1:###.0} "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Couldn't create an instance of IRarController for '{0}' type + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index ee4d13472fd..5991f465065 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -178,6 +178,11 @@ Počáteční hodnota vlastnosti: $({0})={1} Zdroj: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Projekt {0} přeskočil omezení izolace grafu v odkazovaném projektu {1}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 44b17444d81..662d0f59824 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -178,6 +178,11 @@ Anfangswert der Eigenschaft: $({0})="{1}", Quelle: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Das Projekt "{0}" hat Graphisolationseinschränkungen für das referenzierte Projekt "{1}" übersprungen. diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index 6f48da26a49..9558c861f7b 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -178,6 +178,11 @@ Property initial value: $({0})="{1}" Source: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index ff6b7df796d..09e2180e0a3 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -178,6 +178,11 @@ Valor inicial de la propiedad: $({0})="{1}" Origen: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: El proyecto "{0}" ha omitido las restricciones de aislamiento de gráficos en el proyecto "{1}" al que se hace referencia. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 531f580e15a..f578b5ce53b 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -178,6 +178,11 @@ Valeur initiale de la propriété : $({0})="{1}" Source : {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: le projet "{0}" a ignoré les contraintes d'isolement de graphe dans le projet référencé "{1}" diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 1a694c9d92c..e583fca423e 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -178,6 +178,11 @@ Valore iniziale della proprietà: $({0})="{1}". Origine: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: il progetto "{0}" ha ignorato i vincoli di isolamento del grafico nel progetto di riferimento "{1}" diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 9ac0fcd13c4..f25b31b5d56 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -178,6 +178,11 @@ プロパティの初期値: $({0})="{1}" ソース: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: プロジェクト "{0}" は、参照先のプロジェクト "{1}" で、グラフの分離制約をスキップしました diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 6b4bb9305d7..79ffea47246 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -178,6 +178,11 @@ 속성 초기 값: $({0})="{1}" 소스: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 프로젝트 "{0}"에서 참조된 프로젝트 "{1}"의 그래프 격리 제약 조건을 건너뛰었습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index afb3ebf2e36..107873c75b5 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -178,6 +178,11 @@ Wartość początkowa właściwości: $({0})=„{1}” Źródło: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: W przypadku projektu „{0}” pominięto ograniczenia izolacji grafu dla przywoływanego projektu „{1}” diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 0af53bc16a2..dcd153df703 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -178,6 +178,11 @@ Valor inicial da propriedade: $({0})="{1}" Origem: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: o projeto "{0}" ignorou as restrições de isolamento do gráfico no projeto referenciado "{1}" diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 1fd04905eb0..eeb54ff9174 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -178,6 +178,11 @@ Начальное значение свойства: $({0})="{1}" Источник: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: проект "{0}" пропустил ограничения изоляции графа в проекте "{1}", на который указывает ссылка. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index d65c72420e7..9854cc7ddfa 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -178,6 +178,11 @@ Özellik başlangıç değeri: $({0})="{1}" Kaynak: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: "{0}" projesi, başvurulan "{1}" projesindeki graf yalıtımı kısıtlamalarını atladı diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index be7e4e06f42..e2ca02b06c7 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -178,6 +178,11 @@ 属性初始值: $({0})=“{1}”,源: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 项目“{0}”已跳过所引用的项目“{1}”上的图形隔离约束 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index eb53e039bba..15c7df1bd3c 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -178,6 +178,11 @@ 屬性初始值: $({0})="{1}" 來源: {2} + + Couldn't create an instance of IRarController for '{0}' type + Couldn't create an instance of IRarController for '{0}' type + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 專案 "{0}" 已跳過參考專案 "{1}" 上的圖形隔離條件約束 diff --git a/src/Framework/IRarBuildEngine.cs b/src/Framework/IRarBuildEngine.cs new file mode 100644 index 00000000000..b5178887550 --- /dev/null +++ b/src/Framework/IRarBuildEngine.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO.Pipes; + +namespace Microsoft.Build.Framework +{ + /// + /// This interface provides necessary functionality from to RAR as a service functionality. + /// + internal interface IRarBuildEngine + { + /// + /// Inialize new RAR node + /// + internal bool CreateRarNode(); + + /// + /// Provides RAR node name for current configuration + /// + internal string GetRarPipeName(); + + /// + /// Constructs + /// + internal NamedPipeClientStream GetRarClientStream(string pipeName, int timeout); + } +} diff --git a/src/Framework/IRarController.cs b/src/Framework/IRarController.cs new file mode 100644 index 00000000000..639daafb75a --- /dev/null +++ b/src/Framework/IRarController.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipes; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Build.Framework +{ + /// + /// API for controller of ResolveAssemblyReference node + /// + internal interface IRarController + { + Task StartAsync(CancellationToken token); + } +} diff --git a/src/Framework/MSBuildEventSource.cs b/src/Framework/MSBuildEventSource.cs index 5d73b6aa153..ae45efb429e 100644 --- a/src/Framework/MSBuildEventSource.cs +++ b/src/Framework/MSBuildEventSource.cs @@ -411,6 +411,18 @@ public void PacketReadSize(int size) { WriteEvent(55, size); } + + [Event(56)] + public void ResolveAssemblyReferenceNodeConnectStart() + { + WriteEvent(56); + } + + [Event(57)] + public void ResolveAssemblyReferenceNodeConnectStop() + { + WriteEvent(57); + } #endregion } } diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 3bf42162617..74b1c3e0d35 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -141,6 +141,7 @@ + true diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index c7220881a5c..0640d2d95e2 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -2642,6 +2642,16 @@ private static void StartLocalNode(CommandLineSwitches commandLineSwitches) OutOfProcTaskHostNode node = new OutOfProcTaskHostNode(); shutdownReason = node.Run(out nodeException); } + else if (nodeModeNumber == 3) + { + var node = new RarNode(); + // If FEATURE_NODE_REUSE is OFF, just validates that the switch is OK, and always returns False + bool nodeReuse = ProcessNodeReuseSwitch(commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.NodeReuse]); + string[] lowPriorityInput = commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.LowPriority]; + bool lowPriority = lowPriorityInput.Length > 0 && string.Equals(lowPriorityInput[0], bool.TrueString, StringComparison.OrdinalIgnoreCase); + + shutdownReason = node.Run(nodeReuse, lowPriority, out nodeException, s_buildCancellationSource.Token); + } else { CommandLineSwitchException.Throw("InvalidNodeNumberValue", nodeModeNumber.ToString()); diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 615488880be..577b7b4c1d4 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -61,6 +61,7 @@ CopyOnWriteDictionary.cs + ErrorUtilities.cs diff --git a/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec b/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec index f2132af28e6..6e3cd15181e 100644 --- a/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec +++ b/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec @@ -40,6 +40,13 @@ + + + + + + + @@ -94,6 +101,13 @@ + + + + + + + diff --git a/src/Package/MSBuild.VSSetup/files.swr b/src/Package/MSBuild.VSSetup/files.swr index 44277b783c7..d813b8deb3d 100644 --- a/src/Package/MSBuild.VSSetup/files.swr +++ b/src/Package/MSBuild.VSSetup/files.swr @@ -48,6 +48,13 @@ folder InstallDir:\MSBuild\Current\Bin file source=$(X86BinPath)System.Runtime.CompilerServices.Unsafe.dll vs.file.ngenArchitecture=all file source=$(X86BinPath)System.Threading.Tasks.Dataflow.dll vs.file.ngenArchitecture=all vs.file.ngenPriority=1 file source=$(X86BinPath)System.Collections.Immutable.dll vs.file.ngenArchitecture=all vs.file.ngenPriority=1 + file source=$(X86BinPath)StreamJsonRpc.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Nerdbank.Streams.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)System.IO.Pipelines.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Microsoft.VisualStudio.Threading.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Microsoft.VisualStudio.Validation.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)MessagePack.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)MessagePack.Annotations.dll vs.file.ngenArchitecture=all file source=$(X86BinPath)Microsoft.Common.CurrentVersion.targets file source=$(X86BinPath)Microsoft.Common.CrossTargeting.targets file source=$(X86BinPath)Microsoft.Common.overridetasks @@ -197,6 +204,13 @@ folder InstallDir:\MSBuild\Current\Bin\amd64 file source=$(X86BinPath)System.Runtime.CompilerServices.Unsafe.dll vs.file.ngenArchitecture=all file source=$(X86BinPath)System.Threading.Tasks.Dataflow.dll vs.file.ngenArchitecture=all file source=$(X86BinPath)System.Collections.Immutable.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)StreamJsonRpc.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Nerdbank.Streams.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)System.IO.Pipelines.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Microsoft.VisualStudio.Threading.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)Microsoft.VisualStudio.Validation.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)MessagePack.dll vs.file.ngenArchitecture=all + file source=$(X86BinPath)MessagePack.Annotations.dll vs.file.ngenArchitecture=all file source=$(X86BinPath)Microsoft.Common.CurrentVersion.targets file source=$(X86BinPath)Microsoft.Common.CrossTargeting.targets file source=$(X86BinPath)Microsoft.Common.overridetasks diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index cd238bd5540..6ef1da20e51 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -7,12 +7,12 @@ using System.Globalization; using System.IO; using System.IO.Pipes; +using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; using Microsoft.Build.Shared; -using System.Reflection; #if !FEATURE_APM using System.Threading.Tasks; @@ -56,7 +56,12 @@ internal enum HandshakeOptions /// /// Building with administrator privileges /// - Administrator = 32 + Administrator = 32, + + /// + /// Is a special node. Special nodes cannot accept normal build requests. + /// + Special = 64 } internal readonly struct Handshake @@ -154,6 +159,22 @@ static internal int NodeConnectionTimeout get { return GetIntegerVariableOrDefault("MSBUILDNODECONNECTIONTIMEOUT", DefaultNodeConnectionTimeout); } } + private static int? _clrVersion = null; + /// + /// Provides cached value of current CLR version + /// + private static int ClrVersion + { + get + { + if (!_clrVersion.HasValue) + { + _clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major; + } + return _clrVersion.Value; + } + } + /// /// Get environment block /// @@ -459,7 +480,7 @@ internal static async Task ReadAsync(Stream stream, byte[] buffer, int byte /// /// Given the appropriate information, return the equivalent HandshakeOptions. /// - internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit = false, bool nodeReuse = false, bool lowPriority = false, IDictionary taskHostParameters = null) + internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit = false, bool nodeReuse = false, bool lowPriority = false, bool specialNode = false, IDictionary taskHostParameters = null) { HandshakeOptions context = taskHost ? HandshakeOptions.TaskHost : HandshakeOptions.None; @@ -471,7 +492,7 @@ internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit // Take the current TaskHost context if (taskHostParameters == null) { - clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major; + clrVersion = ClrVersion; is64Bit = XMakeAttributes.GetCurrentMSBuildArchitecture().Equals(XMakeAttributes.MSBuildArchitectureValues.x64); } else @@ -510,6 +531,10 @@ internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit context |= HandshakeOptions.Administrator; } #endif + if (specialNode) + { + context |= HandshakeOptions.Special; + } return context; } @@ -628,5 +653,12 @@ internal static int AvoidEndOfHandshakeSignal(int x) { return x == EndOfHandshakeSignal ? ~x : x; } + + internal static string GetRarPipeName(bool nodeReuse, bool lowPriority) + { + var context = GetHandshakeOptions(taskHost: true, nodeReuse: nodeReuse, lowPriority: lowPriority, specialNode: true); + var userName = $"{Environment.UserDomainName}.{Environment.UserName}"; + return $"MSBuild.RAR.{userName}.{(int)context}"; + } } } diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index a7f8913e7d7..6daf5d24058 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -3,11 +3,21 @@ using System; using System.IO; +using System.IO.Pipes; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Threading; +using Microsoft.Build.Internal; namespace Microsoft.Build.Shared { internal static class NamedPipeUtil { + /// + /// The size of the buffers to use for named pipes + /// + private const int PipeBufferSize = 131072; + internal static string GetPipeNameOrPath(string pipeName) { if (NativeMethodsShared.IsUnixLike) @@ -25,5 +35,242 @@ internal static string GetPipeNameOrPath(string pipeName) return pipeName; } } + +#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 static 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 + + /// + /// Attempts to connect to the specified process. + /// + internal static NamedPipeClientStream TryConnectToProcess(int nodeProcessId, int timeout, Handshake handshake) + { + string pipeName = GetPipeNameOrPath("MSBuild" + nodeProcessId); + return TryConnectToProcess(pipeName, nodeProcessId, timeout, handshake); + } + + /// + /// Attempts to connect to the specified process. + /// + internal static NamedPipeClientStream TryConnectToProcess(string pipeName, int timeout, Handshake handshake) + { + return TryConnectToProcess(pipeName, null, timeout, handshake); + } + + private static NamedPipeClientStream TryConnectToProcess(string pipeName, int? nodeProcessId, int timeout, Handshake handshake) + { + 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.HasValue ? nodeProcessId.Value.ToString() : pipeName, 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; + } + + internal static bool ValidateHandshake(Handshake handshake, NamedPipeServerStream serverStream, int clientConnectTimeout) + { + // The handshake protocol is a series of int exchanges. The host sends us a each component, and we + // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. + // Once the handshake is complete, both sides can be assured the other is ready to accept data. + + bool gotValidConnection = true; + try + { + int[] handshakeComponents = handshake.RetrieveHandshakeComponents(); + for (int i = 0; i < handshakeComponents.Length; i++) + { + int handshakePart = serverStream.ReadIntForHandshake(i == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ +#if NETCOREAPP2_1 || MONO + , clientConnectTimeout /* wait a long time for the handshake from this side */ +#endif + ); + + if (handshakePart != handshakeComponents[i]) + { + CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, handshakeComponents[i]); + serverStream.WriteIntForHandshake(i + 1); + gotValidConnection = false; + break; + } + } + + if (gotValidConnection) + { + // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. +#if NETCOREAPP2_1 || MONO + serverStream.ReadEndOfHandshakeSignal(false, clientConnectTimeout); /* wait a long time for the handshake from this side */ +#else + serverStream.ReadEndOfHandshakeSignal(false); +#endif + CommunicationsUtilities.Trace("Successfully connected to parent."); + serverStream.WriteEndOfHandshakeSignal(); + +#if FEATURE_SECURITY_PERMISSIONS + // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they + // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the + // user we were started by. + WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); + WindowsIdentity clientIdentity = null; + serverStream.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); }); + + if (clientIdentity == null || !string.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) + { + CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); + return false; + } +#endif + } + } + catch (IOException e) + { + // We will get here when: + // 1. The host (OOP main node) connects to us, it immediately checks for user privileges + // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake + // 2. The host is too old sending us bits we automatically reject in the handshake + // 3. We expected to read the EndOfHandshake signal, but we received something else + CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); + + gotValidConnection = false; + } + catch (InvalidOperationException) + { + gotValidConnection = false; + } + + if (!gotValidConnection) + { + if (serverStream.IsConnected) + { + serverStream.Disconnect(); + } + return false; + } + + return true; + } + + internal static NamedPipeServerStream CreateNamedPipeServer(string pipeName, int? inputBufferSize = null, int? outputBufferSize = null, int maxNumberOfServerInstances = 1, bool allowNewInstances = false) + { + inputBufferSize ??= PipeBufferSize; + outputBufferSize ??= PipeBufferSize; + +#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR + if (!NativeMethodsShared.IsMono) + { + SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + PipeSecurity security = new PipeSecurity(); + + // Restrict access to just this account. We set the owner specifically here, and on the + // pipe client side they will check the owner against this one - they must have identical + // SIDs or the client will reject this server. This is used to avoid attacks where a + // hacked server creates a less restricted pipe in an attempt to lure us into using it and + // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) + + PipeAccessRights rights = PipeAccessRights.ReadWrite; + if (allowNewInstances) + { + rights |= PipeAccessRights.CreateNewInstance; + } + + PipeAccessRule rule = new PipeAccessRule(identifier, rights, AccessControlType.Allow); + security.AddAccessRule(rule); + security.SetOwner(identifier); + + return new NamedPipeServerStream + ( + pipeName, + PipeDirection.InOut, + maxNumberOfServerInstances, // Only allow one connection at a time. + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + inputBufferSize.Value, // Default input buffer + outputBufferSize.Value, // Default output buffer + security, + HandleInheritability.None + ); + } +#endif + return new NamedPipeServerStream + ( + pipeName, + PipeDirection.InOut, + maxNumberOfServerInstances, // Only allow one connection at a time. + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + inputBufferSize.Value, // Default input buffer + outputBufferSize.Value // Default output buffer + ); + } } } diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index d634e02563c..9d75da4dbab 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -31,13 +31,10 @@ namespace Microsoft.Build.BackEnd internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint { #region Private Data - -#if NETCOREAPP2_1 || MONO /// /// The amount of time to wait for the client to connect to the host. /// private const int ClientConnectTimeout = 60000; -#endif // NETCOREAPP2_1 || MONO /// /// The size of the buffers to use for named pipes @@ -189,49 +186,7 @@ internal void InternalConstruct(string pipeName) _status = LinkStatus.Inactive; _asyncDataMonitor = new object(); _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); - -#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR - if (!NativeMethodsShared.IsMono) - { - SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; - PipeSecurity security = new PipeSecurity(); - - // Restrict access to just this account. We set the owner specifically here, and on the - // pipe client side they will check the owner against this one - they must have identical - // SIDs or the client will reject this server. This is used to avoid attacks where a - // hacked server creates a less restricted pipe in an attempt to lure us into using it and - // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) - PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); - security.AddAccessRule(rule); - security.SetOwner(identifier); - - _pipeServer = new NamedPipeServerStream - ( - pipeName, - PipeDirection.InOut, - 1, // Only allow one connection at a time. - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, - PipeBufferSize, // Default input buffer - PipeBufferSize, // Default output buffer - security, - HandleInheritability.None - ); - } - else -#endif - { - _pipeServer = new NamedPipeServerStream - ( - pipeName, - PipeDirection.InOut, - 1, // Only allow one connection at a time. - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, - PipeBufferSize, // Default input buffer - PipeBufferSize // Default output buffer - ); - } + _pipeServer = NamedPipeUtil.CreateNamedPipeServer(pipeName, PipeBufferSize, PipeBufferSize); } #endregion @@ -364,81 +319,12 @@ private void PacketPumpProc() localPipeServer.EndWaitForConnection(resultForConnection); #endif - // The handshake protocol is a series of int exchanges. The host sends us a each component, and we - // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. - // Once the handshake is complete, both sides can be assured the other is ready to accept data. - Handshake handshake = GetHandshake(); - try - { - int[] handshakeComponents = handshake.RetrieveHandshakeComponents(); - for (int i = 0; i < handshakeComponents.Length; i++) - { - int handshakePart = _pipeServer.ReadIntForHandshake(i == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ -#if NETCOREAPP2_1 || MONO - , ClientConnectTimeout /* wait a long time for the handshake from this side */ -#endif - ); - - if (handshakePart != handshakeComponents[i]) - { - CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, handshakeComponents[i]); - _pipeServer.WriteIntForHandshake(i + 1); - gotValidConnection = false; - break; - } - } - - if (gotValidConnection) - { - // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. -#if NETCOREAPP2_1 || MONO - _pipeServer.ReadEndOfHandshakeSignal(false, ClientConnectTimeout); /* wait a long time for the handshake from this side */ -#else - _pipeServer.ReadEndOfHandshakeSignal(false); -#endif - CommunicationsUtilities.Trace("Successfully connected to parent."); - _pipeServer.WriteEndOfHandshakeSignal(); - -#if FEATURE_SECURITY_PERMISSIONS - // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they - // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the - // user we were started by. - WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); - WindowsIdentity clientIdentity = null; - localPipeServer.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); }); - - if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) - { - CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); - gotValidConnection = false; - continue; - } -#endif - } - } - catch (IOException e) - { - // We will get here when: - // 1. The host (OOP main node) connects to us, it immediately checks for user privileges - // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake - // 2. The host is too old sending us bits we automatically reject in the handshake - // 3. We expected to read the EndOfHandshake signal, but we received something else - CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); - - gotValidConnection = false; - } - catch (InvalidOperationException) - { - gotValidConnection = false; - } + Handshake handshake = GetHandshake(); + gotValidConnection = NamedPipeUtil.ValidateHandshake(handshake, _pipeServer, ClientConnectTimeout); if (!gotValidConnection) { - if (localPipeServer.IsConnected) - { - localPipeServer.Disconnect(); - } - continue; + return; } ChangeLinkStatus(LinkStatus.Active); diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 029dc8ba6d2..0f88509dcc1 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -15,6 +15,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks.AssemblyDependency; +using Microsoft.Build.Tasks.ResolveAssemblyReferences.Client; using Microsoft.Build.Utilities; using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName; @@ -203,6 +204,11 @@ public bool IgnoreTargetFrameworkAttributeVersionMismatch } } + /// + /// Indicates if ResolveAssemblyReference task should be run in its own node or not. + /// + public bool UseResolveAssemblyReferenceService { get; set; } + /// /// Force dependencies to be walked even when a reference is marked with ExternallyResolved=true /// metadata. @@ -2973,6 +2979,37 @@ private string GetAssemblyPathInGac(AssemblyNameExtension assemblyName, SystemPr /// True if there was success. public override bool Execute() { + if (UseResolveAssemblyReferenceService && BuildEngine is IRarBuildEngine rarBuildEngine) + { + using var client = new RarClient(rarBuildEngine); + + var connected = client.Connect(); + if (!connected) + { + Log.LogMessageFromResources(MessageImportance.Low, "RarCouldntConnect"); + bool nodeCreated = false; + try + { + nodeCreated = client.CreateNode(); + } + catch (Exception e) + { + Log.LogWarningFromException(e); + } + + if (nodeCreated) + { + connected = client.Connect(5000); + } + } + + if (connected) + { + // Client is connected to the RAR node, we can execute RAR task remotely + // return client.Execute(); // TODO: Let it do something. + } + } + return Execute ( new FileExists(p => FileUtilities.FileExistsNoThrow(p)), diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index b194c847366..68ef82202f4 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -215,6 +215,12 @@ true + + true + + + true + true @@ -342,6 +348,12 @@ true + + + + + + @@ -982,6 +994,8 @@ + + @@ -1005,6 +1019,7 @@ + diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index f77486801a8..1b0aa167c3c 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -2158,6 +2158,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. IgnoreTargetFrameworkAttributeVersionMismatch ="$(ResolveAssemblyReferenceIgnoreTargetFrameworkAttributeVersionMismatch)" FindDependenciesOfExternallyResolvedReferences="$(FindDependenciesOfExternallyResolvedReferences)" ContinueOnError="$(ContinueOnError)" + UseResolveAssemblyReferenceService="$(UseResolveAssemblyReferenceService)" Condition="'@(Reference)'!='' or '@(_ResolvedProjectReferencePaths)'!='' or '@(_ExplicitReference)' != ''" > diff --git a/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs b/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs new file mode 100644 index 00000000000..f35dc0f61b4 --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO.Pipes; +using Microsoft.Build.Eventing; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract; +using StreamJsonRpc; + +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Client +{ + internal sealed class RarClient : IDisposable + { + /// + /// Default connection timeout for connection to the pipe. Timeout is in millisecond. + /// + private const int DefaultConnectionTimeout = 300; + private readonly IRarBuildEngine _rarBuildEngine; + private NamedPipeClientStream _clientStream; + + public RarClient(IRarBuildEngine rarBuildEngine) + { + _rarBuildEngine = rarBuildEngine; + } + + internal bool Connect() => Connect(DefaultConnectionTimeout); + + internal bool Connect(int timeout) + { + if (_clientStream != null) + return true; + + string pipeName = _rarBuildEngine.GetRarPipeName(); + + MSBuildEventSource.Log.ResolveAssemblyReferenceNodeConnectStart(); + NamedPipeClientStream stream = _rarBuildEngine.GetRarClientStream(pipeName, timeout); + MSBuildEventSource.Log.ResolveAssemblyReferenceNodeConnectStop(); + + if (stream == null) + return false; // We couldn't connect + + _clientStream = stream; + return true; + } + + internal bool CreateNode() + { + return _rarBuildEngine.CreateRarNode(); + } + + internal object Execute() + { + throw new NotImplementedException(); + //using IResolveAssemblyReferenceTaskHandler client = GetRpcClient(); + + // TODO: Find out if there is any possibility of awaiting it. + //return client.GetNumber(parameter).GetAwaiter().GetResult(); + } + + private IResolveAssemblyReferenceTaskHandler GetRpcClient() + { + ErrorUtilities.VerifyThrowInternalErrorUnreachable(_clientStream != null); + + IJsonRpcMessageHandler handler = RpcUtils.GetRarMessageHandler(_clientStream); + return JsonRpc.Attach(handler); + } + + public void Dispose() + { + _clientStream?.Dispose(); + } + } +} diff --git a/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs b/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs new file mode 100644 index 00000000000..0b6387394f1 --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract +{ + internal interface IResolveAssemblyReferenceTaskHandler : IDisposable + { + } +} diff --git a/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs b/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs new file mode 100644 index 00000000000..e8d2d17e995 --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +using Nerdbank.Streams; +using StreamJsonRpc; + +#nullable enable +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences +{ + internal static class RpcUtils + { + internal static IJsonRpcMessageHandler GetRarMessageHandler(Stream stream) + { + return new LengthHeaderMessageHandler(stream.UsePipe(), new MessagePackFormatter()); + } + } +} diff --git a/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs b/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs new file mode 100644 index 00000000000..2852a7d620f --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.IO.Pipes; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; +using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract; +using Microsoft.Build.Tasks.ResolveAssemblyReferences.Services; +using Microsoft.VisualStudio.Threading; + +using StreamJsonRpc; + +#nullable enable +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Server +{ + internal sealed class RarController : IRarController + { + /// + /// The amount of time to wait for the validation of connection. + /// + private const int ValidationTimeout = 60000; + + /// + /// Name of + /// + private readonly string _pipeName; + + /// + /// Handshake used for validation of incoming connections + /// + private readonly Handshake _handshake; + + + /// + /// Factory callback to NamedPipeUtils.CreateNamedPipeServer + /// 1. arg: pipe name + /// 2. arg: input buffer size + /// 3. arg: input buffer size + /// 4. arg. number of allow clients + /// 5. arg. add right to CreateNewInstance + /// + private readonly Func _namedPipeServerFactory; + + /// + /// Callback to validate the handshake. + /// 1. arg: expected handshake + /// 2. arg: named pipe over which we should validate the handshake + /// 3. arg: timeout for validation + /// + private readonly Func _validateHandshakeCallback; + + /// + /// Handler for all incoming tasks + /// + private readonly IResolveAssemblyReferenceTaskHandler _resolveAssemblyReferenceTaskHandler; + + /// + /// Timeout for incoming connections + /// + private readonly TimeSpan Timeout = TimeSpan.FromMinutes(15); + + public RarController( + string pipeName, + Handshake handshake, + Func namedPipeServerFactory, + Func validateHandshakeCallback, + TimeSpan? timeout = null) + : this(pipeName, + handshake, + namedPipeServerFactory, + validateHandshakeCallback, + timeout: timeout, + resolveAssemblyReferenceTaskHandler: new RarTaskHandler()) + { + } + + internal RarController( + string pipeName, + Handshake handshake, + Func namedPipeServerFactory, + Func validateHandshakeCallback, + IResolveAssemblyReferenceTaskHandler resolveAssemblyReferenceTaskHandler, + TimeSpan? timeout = null) + { + _pipeName = pipeName; + _handshake = handshake; + _namedPipeServerFactory = namedPipeServerFactory; + _validateHandshakeCallback = validateHandshakeCallback; + _resolveAssemblyReferenceTaskHandler = resolveAssemblyReferenceTaskHandler; + + if (timeout.HasValue) + { + Timeout = timeout.Value; + } + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + + using ServerMutex mutex = new ServerMutex(_pipeName); + if (!mutex.IsLocked) + { + return 1; + } + + using CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + CancellationToken token = cancellationTokenSource.Token; + + while (!token.IsCancellationRequested) + { + // server will dispose stream too. + NamedPipeServerStream serverStream = GetStream(_pipeName); + await serverStream.WaitForConnectionAsync(token).ConfigureAwait(false); + + if (!_validateHandshakeCallback(_handshake, serverStream, ValidationTimeout)) + continue; + + // Connected! Refresh timeout for incoming request + cancellationTokenSource.CancelAfter(Timeout); + + _ = HandleClientAsync(serverStream, token).ConfigureAwait(false); + } + + return 0; + } + + private async Task HandleClientAsync(Stream serverStream, CancellationToken cancellationToken = default) + { + using JsonRpc server = GetRpcServer(serverStream, _resolveAssemblyReferenceTaskHandler); + server.StartListening(); + + try + { + await server.Completion.WithCancellation(cancellationToken).ConfigureAwait(false); + } + catch (ConnectionLostException) + { + // Some problem with connection, let's ignore it. + // All other exceptions are issue though + } + } + + private JsonRpc GetRpcServer(Stream stream, IResolveAssemblyReferenceTaskHandler handler) + { + IJsonRpcMessageHandler serverHandler = RpcUtils.GetRarMessageHandler(stream); + JsonRpc rpc = new JsonRpc(serverHandler, handler); + return rpc; + } + + /// + /// Instantiates an endpoint to act as a client + /// + /// The name of the pipe to which we should connect. + private NamedPipeServerStream GetStream(string pipeName) + { + ErrorUtilities.VerifyThrow(_namedPipeServerFactory != null, "Stream factory is not set"); + + return _namedPipeServerFactory!(pipeName, + null, // Use default size + null, // Use default size + NamedPipeServerStream.MaxAllowedServerInstances, + true); + } + } +} diff --git a/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs b/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs new file mode 100644 index 00000000000..081904175c3 --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; + +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Server +{ + internal sealed class ServerMutex : IDisposable + { + private readonly Mutex _mutex; + public bool IsLocked { get; private set; } + public bool IsDisposed { get; private set; } + + public ServerMutex(string name) + { + _mutex = new Mutex(true, name, out bool createdNew); + IsLocked = createdNew; + } + + public bool Wait(int timeout) + { + return _mutex.WaitOne(timeout); + } + + + public void Dispose() + { + + if (IsDisposed) + return; + IsDisposed = true; + + try + { + if (IsLocked) + _mutex.ReleaseMutex(); + } + finally + { + _mutex.Dispose(); + IsLocked = false; + } + } + } +} diff --git a/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs b/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs new file mode 100644 index 00000000000..90e9ca795de --- /dev/null +++ b/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; + +using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract; + +namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Services +{ + internal class RarTaskHandler : IResolveAssemblyReferenceTaskHandler + { + public void Dispose() + { + // For RPC dispose + } + } +} diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx index cc1e2699443..b4f042b78b1 100644 --- a/src/Tasks/Resources/Strings.resx +++ b/src/Tasks/Resources/Strings.resx @@ -2931,4 +2931,7 @@ error codes, because those exceptions should ideally be caught at a higher scope and logged with a wrapper message that DOES have an error code. --> + + Couldn't connect to the RAR node, starting a new one. + diff --git a/src/Tasks/Resources/xlf/Strings.cs.xlf b/src/Tasks/Resources/xlf/Strings.cs.xlf index 9598c9d6157..07dc426327a 100644 --- a/src/Tasks/Resources/xlf/Strings.cs.xlf +++ b/src/Tasks/Resources/xlf/Strings.cs.xlf @@ -1412,6 +1412,11 @@ Vlastnost StopOnFirstFailure nebude mít žádný účinek, jsou-li splněny všechny následující podmínky: 1) systém pracuje v režimu více procesů, 2) vlastnost BuildInParallel má hodnotu True, 3) vlastnost RunEachTargetSeparately má hodnotu False. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Nelze číst řádky ze souboru {0}. {1} diff --git a/src/Tasks/Resources/xlf/Strings.de.xlf b/src/Tasks/Resources/xlf/Strings.de.xlf index 44b9fbe1ba8..dbd4ddf4d4a 100644 --- a/src/Tasks/Resources/xlf/Strings.de.xlf +++ b/src/Tasks/Resources/xlf/Strings.de.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure hat keine Auswirkungen, wenn die folgenden Bedingungen alle gelten: 1) Das System wird im Mehrprozessmodus ausgeführt. 2) Die BuildInParallel-Eigenschaft ist TRUE. 3) Die RunEachTargetSeparately-Eigenschaft ist FALSE. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Die Zeilen aus der Datei "{0}" konnten nicht gelesen werden. {1} diff --git a/src/Tasks/Resources/xlf/Strings.en.xlf b/src/Tasks/Resources/xlf/Strings.en.xlf index c9d6885f10c..3d7361d1f1a 100644 --- a/src/Tasks/Resources/xlf/Strings.en.xlf +++ b/src/Tasks/Resources/xlf/Strings.en.xlf @@ -1457,6 +1457,11 @@ StopOnFirstFailure will have no effect when the following conditions are all present: 1) The system is running in multiple process mode 2) The BuildInParallel property is true. 3) The RunEachTargetSeparately property is false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Could not read lines from file "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.es.xlf b/src/Tasks/Resources/xlf/Strings.es.xlf index e34aa053c3e..b60d06690f4 100644 --- a/src/Tasks/Resources/xlf/Strings.es.xlf +++ b/src/Tasks/Resources/xlf/Strings.es.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure no surtirá efecto si se dan todas las condiciones siguientes: 1) El sistema se ejecuta en modo de proceso múltiple. 2) La propiedad BuildInParallel es true. 3) La propiedad RunEachTargetSeparately es false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: No se pudieron leer las líneas del archivo "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.fr.xlf b/src/Tasks/Resources/xlf/Strings.fr.xlf index 9720b69bf5f..75f298b0fc9 100644 --- a/src/Tasks/Resources/xlf/Strings.fr.xlf +++ b/src/Tasks/Resources/xlf/Strings.fr.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure reste sans effet dans les conditions suivantes : 1) Le système s'exécute en mode multiprocessus. 2) La propriété BuildInParallel a la valeur true. 3) La propriété RunEachTargetSeparately a la valeur false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Impossible de lire les lignes dans le fichier "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.it.xlf b/src/Tasks/Resources/xlf/Strings.it.xlf index 7ce334457fc..3cb72b5dacf 100644 --- a/src/Tasks/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Resources/xlf/Strings.it.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure non avrà alcun effetto in presenza di tutte le condizioni seguenti: 1) il sistema è in esecuzione in modalità a più processi 2) la proprietà BuildInParallel è true. 3) la proprietà RunEachTargetSeparately è false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: non è stato possibile leggere le righe dal file "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.ja.xlf b/src/Tasks/Resources/xlf/Strings.ja.xlf index 52d2825682c..e78fb6b8d75 100644 --- a/src/Tasks/Resources/xlf/Strings.ja.xlf +++ b/src/Tasks/Resources/xlf/Strings.ja.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure は、次のすべての条件に該当する場合に無効となります。1) システムがマルチプロセッサ モードで実行されている。2) BuildInParallel プロパティが true に設定されている。3) RunEachTargetSeparately プロパティが false に設定されている。 LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: ファイル "{0}" からの行を読み取れませんでした。{1} diff --git a/src/Tasks/Resources/xlf/Strings.ko.xlf b/src/Tasks/Resources/xlf/Strings.ko.xlf index 2e70e167585..e3dfd3ff47c 100644 --- a/src/Tasks/Resources/xlf/Strings.ko.xlf +++ b/src/Tasks/Resources/xlf/Strings.ko.xlf @@ -1412,6 +1412,11 @@ 다음 조건이 모두 충족되면 StopOnFirstFailure가 효과가 없습니다. 1) 시스템이 다중 프로세스 모드에서 실행 중입니다. 2) BuildInParallel 속성이 true입니다. 3) RunEachTargetSeparately 속성이 false입니다. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: "{0}" 파일에서 줄을 읽을 수 없습니다. {1} diff --git a/src/Tasks/Resources/xlf/Strings.pl.xlf b/src/Tasks/Resources/xlf/Strings.pl.xlf index 6b53dc24fb7..deed816c0a7 100644 --- a/src/Tasks/Resources/xlf/Strings.pl.xlf +++ b/src/Tasks/Resources/xlf/Strings.pl.xlf @@ -1412,6 +1412,11 @@ Działanie funkcji StopOnFirstFailure nie przyniesie efektu, jeśli będą spełnione wszystkie następujące warunki: 1) System działa w trybie wieloprocesowym. 2) Właściwość BuildInParallel ma wartość true. 3) Właściwość RunEachTargetSeparately ma wartość false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Nie można odczytać wierszy z pliku „{0}”. {1} diff --git a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf index 0d31dafd981..5a8373c8e40 100644 --- a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure não terá efeito quando estas condições existirem: 1) O sistema estiver sendo executado no modo de processamento múltiplo 2) A propriedade BuildInParallel for true. 3) A propriedade RunEachTargetSeparately for false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Não foi possível ler linhas do arquivo "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.ru.xlf b/src/Tasks/Resources/xlf/Strings.ru.xlf index 29fe42402d0..8c9da6b1782 100644 --- a/src/Tasks/Resources/xlf/Strings.ru.xlf +++ b/src/Tasks/Resources/xlf/Strings.ru.xlf @@ -1412,6 +1412,11 @@ StopOnFirstFailure будет игнорироваться при одновременном соблюдении следующих условий: 1) система выполняется в режиме с несколькими процессами; 2) свойство BuildInParallel имеет значение true; 3) свойство RunEachTargetSeparately имеет значение false. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: Не удалось прочесть строки из файла "{0}". {1} diff --git a/src/Tasks/Resources/xlf/Strings.tr.xlf b/src/Tasks/Resources/xlf/Strings.tr.xlf index 5076d987e41..97e1112b975 100644 --- a/src/Tasks/Resources/xlf/Strings.tr.xlf +++ b/src/Tasks/Resources/xlf/Strings.tr.xlf @@ -1412,6 +1412,11 @@ Şu koşulların tümü doğru olduğunda StopOnFirstFailure etkisiz olur: 1) Sistem çoklu işlem modunda çalışıyorsa. 2) BuildInParallel özelliği true ise. 3) RunEachTargetSeparately özelliği false ise. LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: "{0}" dosyasındaki satırlar okunamadı. {1} diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf index 2797e21cd0b..cbe8fdeb67a 100644 --- a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf @@ -1412,6 +1412,11 @@ 如果满足以下所有条件,StopOnFirstFailure 将不起任何作用: 1) 系统在多进程模式下运行。2) BuildInParallel 属性为 true。3) RunEachTargetSeparately 属性为 false。 LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: 未能从文件“{0}”读取命令行。{1} diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf index 05c4f11672b..42739e5841a 100644 --- a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf @@ -1412,6 +1412,11 @@ 當下列條件全部成立時,StopOnFirstFailure 將沒有作用: 1) 系統正在多處理程序模式中執行 2) BuildInParallel 屬性為 true。3) RunEachTargetSeparately 屬性為 false。 LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure". + + Couldn't connect to the RAR node, starting a new one. + Couldn't connect to the RAR node, starting a new one. + + MSB3501: Could not read lines from file "{0}". {1} MSB3501: 無法從檔案 "{0}" 讀取行。{1}