提交 b26e16d4 编写于 作者: J Jason Malinowski

Don't accidentally share a named semaphore between test processes

WpfSharedTestData had two static members: Instance and
TestSerializationGateName. TestSerializationGateName is the name of a
system-wide Semaphore that was used to execute WpfFacts one at time.
The intent is this was a GUID so it wouldn't actually be shared.
Unfortunately, the Instance member is initialized first; and the
non-static Semaphore field is initialized with the still uninitialized
TestSerializationGateName. This means our Semaphore would always be
named with a GUID of all-zeros, causing us to share the semaphore
between all running xUnit processes. This was terribly bad for
two reasons:

1. It kills test performance. We run tests in parallel in separate
   processes to ensure isolation, but this sharing of the semaphore
   meant that all of those processes are only running one test at a
   time, defeating all running of tests in parallel.
2. If one test process crashes, the Semaphore is never freed, meaning
   all your other test processes will deadlock and never complete.

It's unclear why this code is using a Semaphore, but since we have
no need to share the Semaphore between processes, we can use a
SemaphoreSlim. This also has the added benefit of properly supporting
a WaitAsync that isn't implemented by launching off a Thread just to
wait for the system semaphore.
上级 6c5980fa
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Roslyn.Utilities
{
internal static class SemaphoreExtensions
{
public static SemaphoreDisposer DisposableWait(this Semaphore semaphore, CancellationToken cancellationToken)
{
if (cancellationToken.CanBeCanceled)
{
int signalledIndex = WaitHandle.WaitAny(new[] { semaphore, cancellationToken.WaitHandle });
if (signalledIndex != 0)
{
cancellationToken.ThrowIfCancellationRequested();
throw ExceptionUtilities.Unreachable;
}
}
else
{
semaphore.WaitOne();
}
return new SemaphoreDisposer(semaphore);
}
public static Task<SemaphoreDisposer> DisposableWaitAsync(this Semaphore semaphore, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(
() => DisposableWait(semaphore, cancellationToken),
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
internal struct SemaphoreDisposer : IDisposable
{
private readonly Semaphore _semaphore;
public SemaphoreDisposer(Semaphore semaphore)
{
_semaphore = semaphore;
}
public void Dispose()
{
_semaphore.Release();
}
}
}
}
......@@ -13,21 +13,13 @@ public sealed class WpfTestSharedData
{
internal static readonly WpfTestSharedData Instance = new WpfTestSharedData();
/// <summary>
/// The name of a <see cref="Semaphore"/> used to ensure that only a single
/// <see cref="WpfFactAttribute"/>-attributed test runs at once. This requirement must be made because,
/// currently, <see cref="WpfTestCase"/>'s logic sets various static state before a method runs. If two tests
/// run interleaved on the same scheduler (i.e. if one yields with an await) then all bets are off.
/// </summary>
internal static readonly Guid TestSerializationGateName = Guid.NewGuid();
/// <summary>
/// Holds the last 10 test cases executed: more recent test cases will occur later in the
/// list. Useful for debugging deadlocks that occur because state leak between runs.
/// </summary>
private readonly List<string> _recentTestCases = new List<string>();
public Semaphore TestSerializationGate = new Semaphore(1, 1, TestSerializationGateName.ToString("N"));
public readonly SemaphoreSlim TestSerializationGate = new SemaphoreSlim(1, 1);
private WpfTestSharedData()
{
......
......@@ -140,9 +140,6 @@
<Compile Include="..\..\..\Compilers\Core\Portable\InternalUtilities\ReferenceEqualityComparer.cs">
<Link>InternalUtilities\ReferenceEqualityComparer.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\InternalUtilities\SemaphoreExtensions.cs">
<Link>InternalUtilities\SemaphoreExtensions.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\InternalUtilities\SemaphoreSlimExtensions.cs">
<Link>InternalUtilities\SemaphoreSlimExtensions.cs</Link>
</Compile>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册