Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make cache queries parallel #6468

Merged
merged 9 commits into from
Jun 1, 2021
76 changes: 69 additions & 7 deletions src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,19 @@ public enum ErrorKind
public class InstanceMockCache : ProjectCachePluginBase
{
private readonly GraphCacheResponse? _testData;
public ConcurrentQueue<BuildRequestData> Requests { get; } = new ConcurrentQueue<BuildRequestData>();
private readonly TimeSpan? _projectQuerySleepTime;
public ConcurrentQueue<BuildRequestData> Requests { get; } = new();

public bool BeginBuildCalled { get; set; }
public bool EndBuildCalled { get; set; }

public InstanceMockCache(GraphCacheResponse? testData = null)
private int _nextId;
public ConcurrentQueue<int> QueryStartStops = new();

public InstanceMockCache(GraphCacheResponse? testData = null, TimeSpan? projectQuerySleepTime = null)
{
_testData = testData;
_projectQuerySleepTime = projectQuerySleepTime;
}

public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logger, CancellationToken cancellationToken)
Expand All @@ -228,18 +233,27 @@ public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logg
return Task.CompletedTask;
}

public override Task<CacheResult> GetCacheResultAsync(
public override async Task<CacheResult> GetCacheResultAsync(
BuildRequestData buildRequest,
PluginLoggerBase logger,
CancellationToken cancellationToken)
{
var queryId = Interlocked.Increment(ref _nextId);

Requests.Enqueue(buildRequest);
QueryStartStops.Enqueue(queryId);

logger.LogMessage($"MockCache: GetCacheResultAsync for {buildRequest.ProjectFullPath}", MessageImportance.High);

return
Task.FromResult(
_testData?.GetExpectedCacheResultForProjectNumber(GetProjectNumber(buildRequest.ProjectFullPath))
?? CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss));
if (_projectQuerySleepTime is not null)
{
await Task.Delay(_projectQuerySleepTime.Value);
}

QueryStartStops.Enqueue(queryId);

return _testData?.GetExpectedCacheResultForProjectNumber(GetProjectNumber(buildRequest.ProjectFullPath))
?? CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss);
}

public override Task EndBuildAsync(PluginLoggerBase logger, CancellationToken cancellationToken)
Expand Down Expand Up @@ -1149,6 +1163,54 @@ public void EndBuildShouldGetCalledOnceWhenItThrowsExceptionsFromGraphBuilds()
StringShouldContainSubstring(logger.FullLog, $"{nameof(AssemblyMockCache)}: EndBuildAsync", expectedOccurrences: 1);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, true)]
public void CacheShouldBeQueriedInParallelDuringGraphBuilds(bool useSynchronousLogging, bool disableInprocNode)
{
var referenceNumbers = Enumerable.Range(2, NativeMethodsShared.GetLogicalCoreCount() * 2).ToArray();

var testData = new GraphCacheResponse(
new Dictionary<int, int[]>
{
{1, referenceNumbers}
},
referenceNumbers.ToDictionary(k => k, k => GraphCacheResponse.SuccessfulProxyTargetResult())
);

var graph = testData.CreateGraph(_env);
var cache = new InstanceMockCache(testData, TimeSpan.FromMilliseconds(50));

using var buildSession = new Helpers.BuildManagerSession(_env, new BuildParameters()
{
MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
cache,
entryPoints: null,
graph),
UseSynchronousLogging = useSynchronousLogging,
DisableInProcNode = disableInprocNode
});

var graphResult = buildSession.BuildGraph(graph);

graphResult.OverallResult.ShouldBe(BuildResultCode.Success);
cache.QueryStartStops.Count.ShouldBe(graph.ProjectNodes.Count * 2);

// Iterate through the ordered list of cache query starts and stops and verify they are out of order.
cdmihai marked this conversation as resolved.
Show resolved Hide resolved
// Out of order means the cache was called in parallel. In order means it was called sequentially.
var cacheCallsAreSerialized = true;
foreach (var i in Enumerable.Range(0, cache.QueryStartStops.Count).Where(i => i % 2 == 0))
{
if (cache.QueryStartStops.ElementAt(i) != cache.QueryStartStops.ElementAt(i + 1))
{
cacheCallsAreSerialized = false;
}
}

cacheCallsAreSerialized.ShouldBeFalse(string.Join(" ", cache.QueryStartStops));
}

private static void StringShouldContainSubstring(string aString, string substring, int expectedOccurrences)
{
aString.ShouldContain(substring);
Expand Down