Skip to content

Commit

Permalink
Hide the internal API more, and move the tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AArnott committed Jul 20, 2022
1 parent 2645b2e commit f5b8f42
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 103 deletions.
23 changes: 23 additions & 0 deletions src/Microsoft.VisualStudio.Threading/JoinableTaskInternals.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.VisualStudio.Threading;

using System.ComponentModel;

#pragma warning disable RS0016 // Add public types and members to the declared API
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

/// <summary>
/// A helper class for integration with Visual Studio.
/// APIs in this file are intended for Microsoft internal use only
/// and are subject to change without notice.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class JoinableTaskInternals
{
public static bool IsMainThreadBlockedByAnyJoinableTask(JoinableTaskContext? joinableTaskContext)
{
return joinableTaskContext?.IsMainThreadBlockedByAnyJoinableTask == true;
}
}
18 changes: 0 additions & 18 deletions src/Microsoft.VisualStudio.Threading/VSThreadHelper.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -45,85 +45,6 @@ await Task.Run(delegate
});
}

[Fact]
public void IsMainThreadBlockedByAnyJoinableTask_True()
{
Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
AsyncManualResetEvent mainThreadBlockerEvent = new AsyncManualResetEvent(false);
AsyncManualResetEvent backgroundThreadMonitorEvent = new AsyncManualResetEvent(false);

// Start task to monitor IsMainThreadBlockedByAnyJoinableTask
Task monitorTask = Task.Run(async () =>
{
await mainThreadBlockerEvent.WaitAsync(this.TimeoutToken);

while (!VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context))
{
// Give the main thread time to enter a blocking state, if the test hasn't already timed out.
await Task.Delay(50, this.TimeoutToken);
}

backgroundThreadMonitorEvent.Set();
});

JoinableTask? joinable = this.Factory.RunAsync(async delegate
{
Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
await this.Factory.SwitchToMainThreadAsync(this.TimeoutToken);

this.Factory.Run(async () =>
{
await TaskScheduler.Default.SwitchTo(alwaysYield: true);
mainThreadBlockerEvent.Set();
await backgroundThreadMonitorEvent.WaitAsync(this.TimeoutToken);
});
});

joinable.Join();
monitorTask.WaitWithoutInlining(throwOriginalException: true);

Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
}

[Fact]
public void IsMainThreadBlockedByAnyJoinableTask_False()
{
Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
ManualResetEventSlim backgroundThreadBlockerEvent = new();

JoinableTask? joinable = this.Factory.RunAsync(async delegate
{
Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
await TaskScheduler.Default.SwitchTo(alwaysYield: true);

this.Factory.Run(async () =>
{
backgroundThreadBlockerEvent.Set();

// Set a delay sufficient for the other thread to have noticed if IsMainThreadBlockedByAnyJoinableTask is true
// while we're suspended.
await Task.Delay(AsyncDelay);
});
});

backgroundThreadBlockerEvent.Wait(UnexpectedTimeout);

do
{
// Give the background thread time to enter a blocking state, if the test hasn't already timed out.
this.TimeoutToken.ThrowIfCancellationRequested();

// IsMainThreadBlockedByAnyJoinableTask should be false when a background thread is blocked.
Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
Thread.Sleep(10);
}
while (!joinable.IsCompleted);

joinable.Join();

Assert.False(VSThreadHelper.IsMainThreadBlockedByAnyJoinableTask(this.Context));
}

[Fact]
public void ReportHangOnRun()
{
Expand Down Expand Up @@ -489,8 +410,8 @@ public void GetHangReportProducesDgmlWithNamedJoinableCollections()
this.Logger.WriteLine(report.Content);
var dgml = XDocument.Parse(report.Content);
IEnumerable<string>? collectionLabels = from node in dgml.Root!.Element(XName.Get("Nodes", DgmlNamespace))!.Elements()
where node.Attribute(XName.Get("Category"))?.Value == "Collection"
select node.Attribute(XName.Get("Label"))?.Value;
where node.Attribute(XName.Get("Category"))?.Value == "Collection"
select node.Attribute(XName.Get("Label"))?.Value;
Assert.Contains(collectionLabels, label => label == jtcName);
return Task.CompletedTask;
});
Expand All @@ -512,8 +433,8 @@ public void GetHangReportProducesDgmlWithMethodNameRequestingMainThread()
this.Logger.WriteLine(report.Content);
var dgml = XDocument.Parse(report.Content);
IEnumerable<string>? collectionLabels = from node in dgml.Root!.Element(XName.Get("Nodes", DgmlNamespace))!.Elements()
where node.Attribute(XName.Get("Category"))?.Value == "Task"
select node.Attribute(XName.Get("Label"))?.Value;
where node.Attribute(XName.Get("Category"))?.Value == "Task"
select node.Attribute(XName.Get("Label"))?.Value;
Assert.Contains(collectionLabels, label => label.Contains(nameof(this.GetHangReportProducesDgmlWithMethodNameRequestingMainThread)));
}

Expand All @@ -535,8 +456,8 @@ public void GetHangReportProducesDgmlWithMethodNameYieldingOnMainThread()
this.Logger.WriteLine(report.Content);
var dgml = XDocument.Parse(report.Content);
IEnumerable<string>? collectionLabels = from node in dgml.Root!.Element(XName.Get("Nodes", DgmlNamespace))!.Elements()
where node.Attribute(XName.Get("Category"))?.Value == "Task"
select node.Attribute(XName.Get("Label"))?.Value;
where node.Attribute(XName.Get("Category"))?.Value == "Task"
select node.Attribute(XName.Get("Label"))?.Value;
Assert.Contains(collectionLabels, label => label.Contains(nameof(this.YieldingMethodAsync)));
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Xunit;
using Xunit.Abstractions;

public class JoinableTaskInternalsTests : JoinableTaskTestBase
{
public JoinableTaskInternalsTests(ITestOutputHelper logger)
: base(logger)
{
}

[Fact]
public void IsMainThreadBlockedByAnyJoinableTask_True()
{
Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
AsyncManualResetEvent mainThreadBlockerEvent = new AsyncManualResetEvent(false);
AsyncManualResetEvent backgroundThreadMonitorEvent = new AsyncManualResetEvent(false);

// Start task to monitor IsMainThreadBlockedByAnyJoinableTask
Task monitorTask = Task.Run(async () =>
{
await mainThreadBlockerEvent.WaitAsync(this.TimeoutToken);

while (!JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context))
{
// Give the main thread time to enter a blocking state, if the test hasn't already timed out.
await Task.Delay(50, this.TimeoutToken);
}

backgroundThreadMonitorEvent.Set();
});

JoinableTask? joinable = this.asyncPump.RunAsync(async delegate
{
Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
await this.asyncPump.SwitchToMainThreadAsync(this.TimeoutToken);

this.asyncPump.Run(async () =>
{
await TaskScheduler.Default.SwitchTo(alwaysYield: true);
mainThreadBlockerEvent.Set();
await backgroundThreadMonitorEvent.WaitAsync(this.TimeoutToken);
});
});

joinable.Join();
monitorTask.WaitWithoutInlining(throwOriginalException: true);

Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
}

[Fact]
public void IsMainThreadBlockedByAnyJoinableTask_False()
{
Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
ManualResetEventSlim backgroundThreadBlockerEvent = new();

JoinableTask? joinable = this.asyncPump.RunAsync(async delegate
{
Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
await TaskScheduler.Default.SwitchTo(alwaysYield: true);

this.asyncPump.Run(async () =>
{
backgroundThreadBlockerEvent.Set();

// Set a delay sufficient for the other thread to have noticed if IsMainThreadBlockedByAnyJoinableTask is true
// while we're suspended.
await Task.Delay(AsyncDelay);
});
});

backgroundThreadBlockerEvent.Wait(UnexpectedTimeout);

do
{
// Give the background thread time to enter a blocking state, if the test hasn't already timed out.
this.TimeoutToken.ThrowIfCancellationRequested();

// IsMainThreadBlockedByAnyJoinableTask should be false when a background thread is blocked.
Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
Thread.Sleep(10);
}
while (!joinable.IsCompleted);

joinable.Join();

Assert.False(JoinableTaskInternals.IsMainThreadBlockedByAnyJoinableTask(this.context));
}
}

0 comments on commit f5b8f42

Please sign in to comment.