Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/MSBuild.Coordinator.UnitTests/CoordinatorServer_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Coordinator;
using Microsoft.Build.UnitTests;
using Shouldly;
using Xunit;

Expand Down Expand Up @@ -417,6 +418,56 @@ public async Task Heartbeat_DoesNotCauseError()
await serverTask;
}

[Fact]
public async Task Dispose_DefersSharedStateDisposalUntilInFlightHeartbeatCompletes()
{
using TestEnvironment env = TestEnvironment.Create(testOutput);
using ManualResetEventSlim heartbeatStarted = new();
using ManualResetEventSlim continueHeartbeat = new();
CoordinatorServer server = CreateServer(
DefaultSettings with
{
TotalNodeBudget = 16,
HeartbeatIntervalMs = CoordinatorSettings.MaxHeartbeatIntervalMs,
});
CoordinatorServer.TestAccessor accessor = server.GetTestAccessor();
accessor.SetHeartbeatCallbackStarted(
() =>
{
heartbeatStarted.Set();
continueHeartbeat.Wait();
});

Task serverTask = server.RunAsync(_cts.Token);

try
{
accessor.TriggerHeartbeatCheck();
heartbeatStarted.Wait(TimeSpan.FromSeconds(5)).ShouldBeTrue();

Task disposeTask = Task.Run(
() =>
{
Parallel.Invoke(server.Dispose, server.Dispose, server.Dispose);
});
(await Task.WhenAny(disposeTask, Task.Delay(5000))).ShouldBe(disposeTask);
await disposeTask;
accessor.IsDisposing.ShouldBeTrue();

continueHeartbeat.Set();

(await Task.WhenAny(serverTask, Task.Delay(5000))).ShouldBe(serverTask);
await serverTask;

server.Dispose();
}
finally
{
continueHeartbeat.Set();
server.Dispose();
}
}

[Fact]
public async Task ClientDisconnect_ReleasesGrant()
{
Expand Down
25 changes: 25 additions & 0 deletions src/MSBuild.Coordinator/CoordinatorServer.TestAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Build.Coordinator;

internal sealed partial class CoordinatorServer
{
internal TestAccessor GetTestAccessor() => new(this);

internal readonly struct TestAccessor(CoordinatorServer server)
{
public bool IsDisposing => Volatile.Read(ref server._disposeState) == Disposing;

public void SetHeartbeatCallbackStarted(Action callback)
=> server._heartbeatCallbackStartedForTests = callback;

public void TriggerHeartbeatCheck()
{
lock (server._lifecycleLock)
{
server._heartbeatMonitor!.Change(dueTime: 0, period: Timeout.Infinite);
}
}
}
}
Loading
Loading