diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs
index 2e8503bf6ab..236894208af 100644
--- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs
@@ -546,6 +546,18 @@ public void IsRunningMultipleNodes4Nodes()
Assert.True(_taskHost.IsRunningMultipleNodes); // "Expect IsRunningMultipleNodes to be true with 4 nodes"
}
+ ///
+ /// Test that Yield and Reacquire can be called without throwing exceptions.
+ /// The mock request callback should handle these calls appropriately.
+ ///
+ [Fact]
+ public void TestYieldAndReacquire()
+ {
+ // Should not throw exceptions when called
+ _taskHost.Yield();
+ _taskHost.Reacquire();
+ }
+
#if FEATURE_CODETASKFACTORY
///
/// Task logging after it's done should not crash us.
diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs
index abb36afecd2..be9a73f1380 100644
--- a/src/Build/Instance/TaskFactories/TaskHostTask.cs
+++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs
@@ -191,6 +191,8 @@ public TaskHostTask(
(this as INodePacketFactory).RegisterPacketHandler(NodePacketType.LogMessage, LogMessagePacket.FactoryForDeserialization, this);
(this as INodePacketFactory).RegisterPacketHandler(NodePacketType.TaskHostTaskComplete, TaskHostTaskComplete.FactoryForDeserialization, this);
(this as INodePacketFactory).RegisterPacketHandler(NodePacketType.NodeShutdown, NodeShutdown.FactoryForDeserialization, this);
+ (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.TaskHostYield, TaskHostYield.FactoryForDeserialization, this);
+ (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.TaskHostReacquire, TaskHostReacquire.FactoryForDeserialization, this);
_packetReceivedEvent = new AutoResetEvent(false);
_receivedPackets = new ConcurrentQueue();
@@ -508,6 +510,12 @@ private void HandlePacket(INodePacket packet, out bool taskFinished)
case NodePacketType.LogMessage:
HandleLoggedMessage(packet as LogMessagePacket);
break;
+ case NodePacketType.TaskHostYield:
+ HandleTaskHostYield(packet as TaskHostYield);
+ break;
+ case NodePacketType.TaskHostReacquire:
+ HandleTaskHostReacquire(packet as TaskHostReacquire);
+ break;
default:
ErrorUtilities.ThrowInternalErrorUnreachable();
break;
@@ -647,6 +655,28 @@ private void HandleLoggedMessage(LogMessagePacket logMessagePacket)
}
}
+ ///
+ /// Handle yield notification from the task host.
+ /// The task host is indicating it's about to perform long-running operations
+ /// and the node can do other work. We acknowledge receipt immediately.
+ ///
+ private void HandleTaskHostYield(TaskHostYield yieldPacket)
+ {
+ // Send acknowledgment back to task host
+ _taskHostProvider.SendData(_taskHostNodeId, new TaskHostYield());
+ }
+
+ ///
+ /// Handle reacquire notification from the task host.
+ /// The task host wants to regain control after previously yielding.
+ /// We acknowledge receipt immediately.
+ ///
+ private void HandleTaskHostReacquire(TaskHostReacquire reacquirePacket)
+ {
+ // Send acknowledgment back to task host
+ _taskHostProvider.SendData(_taskHostNodeId, new TaskHostReacquire());
+ }
+
///
/// Since we log that we weren't able to connect to the task host in a couple of different places,
/// extract it out into a separate method.
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index d53ecf9d743..2963b7034c7 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -100,6 +100,8 @@
+
+
diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj
index e43039e8e6c..844be4e073a 100644
--- a/src/MSBuild/MSBuild.csproj
+++ b/src/MSBuild/MSBuild.csproj
@@ -113,6 +113,8 @@
+
+
diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs
index d141991394f..00e60c70a90 100644
--- a/src/MSBuild/OutOfProcTaskHostNode.cs
+++ b/src/MSBuild/OutOfProcTaskHostNode.cs
@@ -130,6 +130,16 @@ internal class OutOfProcTaskHostNode :
///
private ManualResetEvent _taskCancelledEvent;
+ ///
+ /// The event which is set when the parent node acknowledges a yield request.
+ ///
+ private AutoResetEvent _yieldAcknowledgedEvent;
+
+ ///
+ /// The event which is set when the parent node acknowledges a reacquire request.
+ ///
+ private AutoResetEvent _reacquireAcknowledgedEvent;
+
///
/// The thread currently executing user task in the TaskRunner
///
@@ -195,6 +205,8 @@ public OutOfProcTaskHostNode()
_shutdownEvent = new ManualResetEvent(false);
_taskCompleteEvent = new AutoResetEvent(false);
_taskCancelledEvent = new ManualResetEvent(false);
+ _yieldAcknowledgedEvent = new AutoResetEvent(false);
+ _reacquireAcknowledgedEvent = new AutoResetEvent(false);
_packetFactory = new NodePacketFactory();
@@ -203,6 +215,8 @@ public OutOfProcTaskHostNode()
thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this);
thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this);
thisINodePacketFactory.RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this);
+ thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostYield, TaskHostYield.FactoryForDeserialization, this);
+ thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostReacquire, TaskHostReacquire.FactoryForDeserialization, this);
#if !CLR2COMPATIBILITY
EngineServices = new EngineServicesImpl(this);
@@ -405,21 +419,36 @@ public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames,
}
///
- /// Stub implementation of IBuildEngine3.Yield. The task host does not support yielding, so just go ahead and silently
- /// return, letting the task continue.
+ /// Implementation of IBuildEngine3.Yield. Sends a yield packet to the parent node
+ /// indicating that the task host is yielding control and the node can do other work
+ /// while the task performs long-running out-of-process operations.
///
public void Yield()
{
- return;
+ if (_nodeEndpoint?.LinkStatus == LinkStatus.Active)
+ {
+ TaskHostYield yieldPacket = new TaskHostYield();
+ _nodeEndpoint.SendData(yieldPacket);
+
+ // Wait for the parent node to acknowledge the yield
+ _yieldAcknowledgedEvent.WaitOne();
+ }
}
///
- /// Stub implementation of IBuildEngine3.Reacquire. The task host does not support yielding, so just go ahead and silently
- /// return, letting the task continue.
+ /// Implementation of IBuildEngine3.Reacquire. Sends a reacquire packet to the parent node
+ /// indicating that the task host wants to regain control after previously yielding.
///
public void Reacquire()
{
- return;
+ if (_nodeEndpoint?.LinkStatus == LinkStatus.Active)
+ {
+ TaskHostReacquire reacquirePacket = new TaskHostReacquire();
+ _nodeEndpoint.SendData(reacquirePacket);
+
+ // Wait for the parent node to acknowledge the reacquire
+ _reacquireAcknowledgedEvent.WaitOne();
+ }
}
#endregion // IBuildEngine3 Implementation
@@ -725,6 +754,14 @@ private void HandlePacket(INodePacket packet)
case NodePacketType.NodeBuildComplete:
HandleNodeBuildComplete(packet as NodeBuildComplete);
break;
+ case NodePacketType.TaskHostYield:
+ // Acknowledgment from parent node
+ _yieldAcknowledgedEvent.Set();
+ break;
+ case NodePacketType.TaskHostReacquire:
+ // Acknowledgment from parent node
+ _reacquireAcknowledgedEvent.Set();
+ break;
}
}
@@ -875,11 +912,15 @@ private NodeEngineShutdownReason HandleShutdown()
_shutdownEvent.Close();
_taskCompleteEvent.Close();
_taskCancelledEvent.Close();
+ _yieldAcknowledgedEvent.Close();
+ _reacquireAcknowledgedEvent.Close();
#else
_packetReceivedEvent.Dispose();
_shutdownEvent.Dispose();
_taskCompleteEvent.Dispose();
_taskCancelledEvent.Dispose();
+ _yieldAcknowledgedEvent.Dispose();
+ _reacquireAcknowledgedEvent.Dispose();
#endif
return _shutdownReason;
diff --git a/src/Shared/INodePacket.cs b/src/Shared/INodePacket.cs
index 4aa870d7a09..e18ba35a90e 100644
--- a/src/Shared/INodePacket.cs
+++ b/src/Shared/INodePacket.cs
@@ -208,7 +208,6 @@ internal enum NodePacketType : byte
///
ProcessReport, // 0x13
-
/// Notifies the RAR node to set a configuration for a particular build.
RarNodeEndpointConfiguration,
@@ -222,6 +221,26 @@ internal enum NodePacketType : byte
///
RarNodeExecuteResponse, // 0x15
+ ///
+ /// Message sent from the task host to the parent node when a task
+ /// calls IBuildEngine3.Yield() to indicate it is yielding control
+ /// and the node can do other work.
+ ///
+ /// Contents:
+ /// (nothing)
+ ///
+ TaskHostYield, // 0x16
+
+ ///
+ /// Message sent from the task host to the parent node when a task
+ /// calls IBuildEngine3.Reacquire() to indicate it wants to regain
+ /// control after yielding.
+ ///
+ /// Contents:
+ /// (nothing)
+ ///
+ TaskHostReacquire, // 0x17
+
// Reserve space for future core packet types (0x16-0x3B available for expansion)
// Server command packets placed at end of safe range to maintain separation from core packets
diff --git a/src/Shared/TaskHostReacquire.cs b/src/Shared/TaskHostReacquire.cs
new file mode 100644
index 00000000000..3984630bac7
--- /dev/null
+++ b/src/Shared/TaskHostReacquire.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+namespace Microsoft.Build.BackEnd
+{
+ ///
+ /// TaskHostReacquire informs the parent node that the task host
+ /// wants to reacquire control after previously yielding via Yield().
+ ///
+ internal class TaskHostReacquire : INodePacket
+ {
+ ///
+ /// Constructor
+ ///
+ public TaskHostReacquire()
+ {
+ }
+
+ ///
+ /// The type of this NodePacket
+ ///
+ public NodePacketType Type
+ {
+ get { return NodePacketType.TaskHostReacquire; }
+ }
+
+ ///
+ /// Translates the packet to/from binary form.
+ ///
+ /// The translator to use.
+ public void Translate(ITranslator translator)
+ {
+ // Do nothing -- this packet doesn't contain any parameters.
+ }
+
+ ///
+ /// Factory for deserialization.
+ ///
+ internal static INodePacket FactoryForDeserialization(ITranslator translator)
+ {
+ TaskHostReacquire taskReacquire = new TaskHostReacquire();
+ taskReacquire.Translate(translator);
+ return taskReacquire;
+ }
+ }
+}
diff --git a/src/Shared/TaskHostYield.cs b/src/Shared/TaskHostYield.cs
new file mode 100644
index 00000000000..b100ae056e3
--- /dev/null
+++ b/src/Shared/TaskHostYield.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+namespace Microsoft.Build.BackEnd
+{
+ ///
+ /// TaskHostYield informs the parent node that the task host is
+ /// yielding control and the node can do other work while the task
+ /// performs long-running out-of-process operations.
+ ///
+ internal class TaskHostYield : INodePacket
+ {
+ ///
+ /// Constructor
+ ///
+ public TaskHostYield()
+ {
+ }
+
+ ///
+ /// The type of this NodePacket
+ ///
+ public NodePacketType Type
+ {
+ get { return NodePacketType.TaskHostYield; }
+ }
+
+ ///
+ /// Translates the packet to/from binary form.
+ ///
+ /// The translator to use.
+ public void Translate(ITranslator translator)
+ {
+ // Do nothing -- this packet doesn't contain any parameters.
+ }
+
+ ///
+ /// Factory for deserialization.
+ ///
+ internal static INodePacket FactoryForDeserialization(ITranslator translator)
+ {
+ TaskHostYield taskYield = new TaskHostYield();
+ taskYield.Translate(translator);
+ return taskYield;
+ }
+ }
+}