170 lines
5.9 KiB
C#
170 lines
5.9 KiB
C#
using System.Collections.Concurrent;
|
|
using MBN_STOCK_WEBVIEW.Playout.Runtime;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.Playout.Tests;
|
|
|
|
public sealed class StaDispatcherTests
|
|
{
|
|
[Fact]
|
|
public async Task InvokeAsync_RunsCallbacksOnOneStaThreadInFifoOrder()
|
|
{
|
|
await using var dispatcher = new StaDispatcher(capacity: 4);
|
|
using var firstStarted = new ManualResetEventSlim();
|
|
using var releaseFirst = new ManualResetEventSlim();
|
|
var calls = new ConcurrentQueue<(int Order, int ThreadId, ApartmentState Apartment)>();
|
|
|
|
var first = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
calls.Enqueue((
|
|
1,
|
|
Environment.CurrentManagedThreadId,
|
|
Thread.CurrentThread.GetApartmentState()));
|
|
firstStarted.Set();
|
|
Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5)));
|
|
return 1;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5)));
|
|
|
|
var second = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
calls.Enqueue((
|
|
2,
|
|
Environment.CurrentManagedThreadId,
|
|
Thread.CurrentThread.GetApartmentState()));
|
|
return 2;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
var third = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
calls.Enqueue((
|
|
3,
|
|
Environment.CurrentManagedThreadId,
|
|
Thread.CurrentThread.GetApartmentState()));
|
|
return 3;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
|
|
releaseFirst.Set();
|
|
var results = await Task.WhenAll(first, second, third);
|
|
Assert.Equal(new[] { 1, 2, 3 }, results);
|
|
|
|
var recorded = calls.ToArray();
|
|
Assert.Equal([1, 2, 3], recorded.Select(call => call.Order));
|
|
Assert.Single(recorded.Select(call => call.ThreadId).Distinct());
|
|
Assert.All(recorded, call => Assert.Equal(ApartmentState.STA, call.Apartment));
|
|
Assert.Equal(recorded[0].ThreadId, dispatcher.ManagedThreadId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvokeAsync_CancelledWhileQueued_DoesNotRunCallback()
|
|
{
|
|
await using var dispatcher = new StaDispatcher(capacity: 2);
|
|
using var firstStarted = new ManualResetEventSlim();
|
|
using var releaseFirst = new ManualResetEventSlim();
|
|
var cancelledCallbackRan = false;
|
|
|
|
var first = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
firstStarted.Set();
|
|
Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5)));
|
|
return 1;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5)));
|
|
|
|
using var cancellation = new CancellationTokenSource();
|
|
var cancelled = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
cancelledCallbackRan = true;
|
|
return 2;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
cancellation.Token);
|
|
cancellation.Cancel();
|
|
releaseFirst.Set();
|
|
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => cancelled);
|
|
Assert.Equal(1, await first);
|
|
Assert.False(cancelledCallbackRan);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvokeAsync_WhenBoundedQueueIsFull_CanCancelWithoutRunning()
|
|
{
|
|
await using var dispatcher = new StaDispatcher(capacity: 1);
|
|
using var firstStarted = new ManualResetEventSlim();
|
|
using var releaseFirst = new ManualResetEventSlim();
|
|
var thirdCallbackRan = false;
|
|
|
|
var first = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
firstStarted.Set();
|
|
Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5)));
|
|
return 1;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5)));
|
|
|
|
var second = dispatcher.InvokeAsync(
|
|
() => 2,
|
|
TimeSpan.FromSeconds(10),
|
|
CancellationToken.None);
|
|
using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
|
|
var third = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
thirdCallbackRan = true;
|
|
return 3;
|
|
},
|
|
TimeSpan.FromSeconds(10),
|
|
cancellation.Token);
|
|
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => third);
|
|
Assert.False(thirdCallbackRan);
|
|
releaseFirst.Set();
|
|
var results = await Task.WhenAll(first, second);
|
|
Assert.Equal(new[] { 1, 2 }, results);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvokeAsync_TimeoutQuarantinesDispatcherAndRejectsLaterWork()
|
|
{
|
|
await using var dispatcher = new StaDispatcher(capacity: 2);
|
|
using var callbackStarted = new ManualResetEventSlim();
|
|
using var releaseCallback = new ManualResetEventSlim();
|
|
|
|
var timedOut = dispatcher.InvokeAsync(
|
|
() =>
|
|
{
|
|
callbackStarted.Set();
|
|
Assert.True(releaseCallback.Wait(TimeSpan.FromSeconds(5)));
|
|
return 1;
|
|
},
|
|
TimeSpan.FromMilliseconds(100),
|
|
CancellationToken.None);
|
|
Assert.True(callbackStarted.Wait(TimeSpan.FromSeconds(5)));
|
|
|
|
await Assert.ThrowsAsync<StaOperationTimedOutException>(() => timedOut);
|
|
Assert.True(dispatcher.IsQuarantined);
|
|
await Assert.ThrowsAsync<StaDispatcherQuarantinedException>(
|
|
() => dispatcher.InvokeAsync(
|
|
() => 2,
|
|
TimeSpan.FromSeconds(1),
|
|
CancellationToken.None));
|
|
|
|
releaseCallback.Set();
|
|
}
|
|
}
|