Skip to content

Commit

Permalink
Improve resilience to COM RCW cleanup issues in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed Oct 29, 2020
1 parent 28b4508 commit 2238c78
Showing 1 changed file with 14 additions and 25 deletions.
39 changes: 14 additions & 25 deletions src/EditorFeatures/TestUtilities/Threading/StaTaskScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable disable

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;

Expand Down Expand Up @@ -45,8 +46,7 @@ static StaTaskScheduler()
// to DomainUnload is a reasonable place to do it.
AppDomain.CurrentDomain.DomainUnload += (sender, e) =>
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.GetTotalMemory(forceFullCollection: true);
};
}

Expand All @@ -55,36 +55,24 @@ public StaTaskScheduler()
{
using (var threadStartedEvent = new ManualResetEventSlim(initialState: false))
{
DispatcherSynchronizationContext synchronizationContext = null;
Dispatcher staDispatcher = null;
StaThread = new Thread(() =>
{
var oldContext = SynchronizationContext.Current;
try
{
// All WPF Tests need a DispatcherSynchronizationContext and we dont want to block pending keyboard
// or mouse input from the user. So use background priority which is a single level below user input.
synchronizationContext = new DispatcherSynchronizationContext();

// xUnit creates its own synchronization context and wraps any existing context so that messages are
// still pumped as necessary. So we are safe setting it here, where we are not safe setting it in test.
SynchronizationContext.SetSynchronizationContext(synchronizationContext);

threadStartedEvent.Set();

Dispatcher.Run();
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
staDispatcher = Dispatcher.CurrentDispatcher;
threadStartedEvent.Set();
Dispatcher.Run();
});
StaThread.Name = $"{nameof(StaTaskScheduler)} thread";
StaThread.IsBackground = true;
StaThread.SetApartmentState(ApartmentState.STA);
StaThread.Start();

threadStartedEvent.Wait();
DispatcherSynchronizationContext = synchronizationContext;
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
DispatcherSynchronizationContext = (DispatcherSynchronizationContext)staDispatcher.Invoke(() => SynchronizationContext.Current);
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs

AppDomain.CurrentDomain.DomainUnload += (_, _) => Dispose();
}

// Work around the WeakEventTable Shutdown race conditions
Expand All @@ -104,8 +92,9 @@ public void Dispose()
{
if (StaThread.IsAlive)
{
DispatcherSynchronizationContext.Post(_ => Dispatcher.ExitAllFrames(), null);
StaThread.Join();
// The message pump is not active during AppDomain.Unload callbacks, so our only option is to abort the
// thread directly. 😢
StaThread.Abort();
}
}
}
Expand Down

0 comments on commit 2238c78

Please sign in to comment.