|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.UnitTests;
using Shouldly;
using Xunit;
#nullable disable
namespace Microsoft.Build.Tasks.UnitTests
{
public class ResourceManagement_Tests
{
[Fact]
public void SingleCoreRequest()
{
var messages = AssertBuildSucceededAndGetMessages(@"
{
int grantedCores = BuildEngine9.RequestCores(1337);
Log.LogMessage(""Number of cores acquired: "" + grantedCores);
BuildEngine9.ReleaseCores(grantedCores);
}", "<UseCores />");
var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray();
filteredMessages.Length.ShouldBe(1);
GetTrailingIntegerFromMessage(filteredMessages[0]).ShouldBeGreaterThan(0);
}
[Fact]
public void SingleCoreRequestWithNoRelease()
{
var messages = AssertBuildSucceededAndGetMessages(@"
{
int grantedCores = BuildEngine9.RequestCores(1337);
Log.LogMessage(""Number of cores acquired: "" + grantedCores);
// Note that we're missing a call to ReleaseCores() so we rely on cores being released after the task is finished.
}", "<UseCores /> <UseCores />");
var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray();
filteredMessages.Length.ShouldBe(2);
int grantedCores1 = GetTrailingIntegerFromMessage(filteredMessages[0]);
int grantedCores2 = GetTrailingIntegerFromMessage(filteredMessages[1]);
// Both tasks were able to get the same number of cores because cores were auto-released.
grantedCores1.ShouldBeGreaterThan(0);
grantedCores2.ShouldBe(grantedCores1);
}
[Fact]
public void SingleCoreRequestWithReacquire()
{
var messages = AssertBuildSucceededAndGetMessages(@"
{
int grantedCores1 = BuildEngine9.RequestCores(1337);
Log.LogMessage(""Number of cores acquired: "" + grantedCores1);
BuildEngine9.Yield();
// Reacquire releases all cores.
BuildEngine9.Reacquire();
int grantedCores2 = BuildEngine9.RequestCores(1337);
Log.LogMessage(""Number of cores acquired: "" + grantedCores2);
}", "<UseCores />");
var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray();
filteredMessages.Length.ShouldBe(2);
int grantedCores1 = GetTrailingIntegerFromMessage(filteredMessages[0]);
int grantedCores2 = GetTrailingIntegerFromMessage(filteredMessages[1]);
// Both tasks were able to get the same number of cores because cores were auto-released.
grantedCores1.ShouldBeGreaterThan(0);
grantedCores2.ShouldBe(grantedCores1);
}
[Fact]
public void MultipleCoreRequests()
{
// Exercise concurrent RequestCores() and ReleaseCores() calls.
AssertBuildSucceededAndGetMessages(@"
{
const int coresToAcquire = 1337;
int acquiredCores = 0;
int done = 0;
System.Threading.Thread requestThread = new System.Threading.Thread(() =>
{
for (int i = 0; i < coresToAcquire; i++)
{
BuildEngine9.RequestCores(1);
System.Threading.Interlocked.Increment(ref acquiredCores);
}
System.Threading.Thread.VolatileWrite(ref done, 1);
});
System.Threading.Thread releaseThread = new System.Threading.Thread(() =>
{
while (System.Threading.Thread.VolatileRead(ref done) == 0 || System.Threading.Thread.VolatileRead(ref acquiredCores) > 0)
{
if (System.Threading.Thread.VolatileRead(ref acquiredCores) > 0)
{
BuildEngine9.ReleaseCores(1);
System.Threading.Interlocked.Decrement(ref acquiredCores);
}
else
{
System.Threading.Thread.Yield();
}
}
});
// One thread is acquiring cores, the other is releasing them. The releasing thread is running with a lower
// priority to increase the chances of contention where all cores are allocated and RequestCores() blocks.
requestThread.Start();
releaseThread.Priority = System.Threading.ThreadPriority.BelowNormal;
releaseThread.Start();
requestThread.Join();
releaseThread.Join();
}", "<UseCores />");
}
private List<BuildMessageEventArgs> AssertBuildSucceededAndGetMessages(string taskCode, string targetContent)
{
string text = $@"
<Project>
<UsingTask
TaskName=""UseCores""
TaskFactory=""RoslynCodeTaskFactory""
AssemblyFile=""$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"" >
<Task>
<Reference Include=""{typeof(Enumerable).Assembly.Location}"" />
<Code Type=""Fragment"" Language=""cs"">
{taskCode}
</Code>
</Task>
</UsingTask>
<Target Name=""Build"">
{targetContent}
</Target>
</Project>";
using var env = TestEnvironment.Create();
var projectFile = env.CreateTestProjectWithFiles("test.proj", text);
var logger = projectFile.BuildProjectExpectSuccess();
return logger.BuildMessageEvents;
}
private int GetTrailingIntegerFromMessage(BuildMessageEventArgs msg)
{
string[] messageComponents = msg.Message.Split(' ');
int.TryParse(messageComponents.Last(), out int trailingInteger).ShouldBeTrue();
return trailingInteger;
}
}
}
|