This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-locking-cil/src/MfGames.Locking.Tests/LockTests.cs
2021-01-20 19:22:18 -06:00

322 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace MfGames.Locking.Tests
{
public class LockTests
{
public static readonly TimeSpan ProcessTime =
TimeSpan.FromMilliseconds(50);
private readonly List<int> events;
private readonly ReaderWriterLockSlim locker;
private readonly ITestOutputHelper output;
private readonly DateTime start;
public LockTests(ITestOutputHelper output)
{
this.output = output;
this.start = DateTime.UtcNow;
this.locker = new ReaderWriterLockSlim();
this.events = new List<int>();
}
private TimeSpan Elapsed => DateTime.UtcNow - this.start;
[Fact]
public void BasicDataGatheringWorks()
{
this.Report(ProcessTime, 1);
this.Verify();
}
[Fact]
public void ReadBlocksWrite()
{
Task.WaitAll(
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 0,
ProcessTime * 3,
1)),
Task.Run(
() => this.ReportInWriteLock(
ProcessTime * 1,
ProcessTime * 1,
2)));
this.Verify();
}
[Fact]
public void ReadsDoNotBlockReads()
{
Task.WaitAll(
Task.Run(
() => this.ReportInReadLock(
ProcessTime,
ProcessTime * 5,
3)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
2)));
this.Verify();
}
[Fact]
public void ThreadedDataGatheringWorks()
{
Task.WaitAll(
Task.Run(
() => this.ReportInReadLock(ProcessTime, ProcessTime, 1)));
this.Verify();
}
[Fact]
public void UpgradableBlocksUpgradable()
{
Task.WaitAll(
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime,
ProcessTime * 5,
1)),
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime * 3,
ProcessTime,
2)),
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime * 2,
ProcessTime * 2,
3)));
this.Verify();
}
[Fact]
public void UpgradableDoesNotBlockReads()
{
Task.WaitAll(
Task.Run(
() => this.ReportInUpgradableLock(
ProcessTime,
ProcessTime * 5,
3)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
2)));
this.Verify();
}
[Fact]
public void WriteBlockRead()
{
Task.WaitAll(
Task.Run(
() => this.ReportInWriteLock(
ProcessTime,
ProcessTime * 3,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
2)));
this.Verify();
}
[Fact]
public void WriteBlockReads()
{
Task.WaitAll(
Task.Run(
() => this.ReportInWriteLock(
ProcessTime,
ProcessTime * 5,
1)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime,
2)),
Task.Run(
() => this.ReportInReadLock(
ProcessTime * 2,
ProcessTime * 2,
3)));
this.Verify();
}
private void AddEvent(int sequence)
{
// This is a monitor lock on the slim, just to give us a second lock.
lock (this.locker)
{
this.output.WriteLine(
this.Elapsed + ": " + sequence + " => adding sequence");
this.events.Add(sequence);
}
}
/// <summary>
/// Waits a short period of time and then injects the sequence into the event list.
/// </summary>
private void Report(
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(
this.Elapsed + ": " + sequence + " => starting process");
Thread.Sleep(processTime);
this.output.WriteLine(
this.Elapsed + ": " + sequence + " => finished process");
this.AddEvent(sequence);
}
private void ReportInReadLock(
TimeSpan settleTime,
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => entering read lock: "
+ sequence);
Thread.Sleep(settleTime);
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => locking read lock: "
+ sequence);
using (new ReadLock(this.locker))
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => starting read lock: "
+ sequence);
this.Report(processTime, sequence);
}
}
private void ReportInUpgradableLock(
TimeSpan settleTime,
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => entering upgradable lock: "
+ sequence);
Thread.Sleep(settleTime);
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => locking upgradable lock: "
+ sequence);
using (new UpgradableLock(this.locker))
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => starting upgradable lock: "
+ sequence);
this.Report(processTime, sequence);
}
}
private void ReportInWriteLock(
TimeSpan settleTime,
TimeSpan processTime,
int sequence)
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => entering write lock: "
+ sequence);
Thread.Sleep(settleTime);
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => locking write lock: "
+ sequence);
using (new WriteLock(this.locker))
{
this.output.WriteLine(
this.Elapsed
+ ": "
+ sequence
+ " => starting write lock: "
+ sequence);
this.Report(processTime, sequence);
}
}
/// <summary>
/// Verifies the sequence of events.
/// </summary>
private void Verify()
{
this.output.WriteLine(
"Final Sequence: " + string.Join(", ", this.events));
for (int i = 1; i <= this.events.Count; i++)
{
Assert.Equal(i, this.events[i - 1]);
}
}
}
}