2021-01-21 01:22:18 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
using Xunit;
|
|
|
|
using Xunit.Abstractions;
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
namespace MfGames.Locking.Tests
|
|
|
|
{
|
|
|
|
public class LockTests
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
public static readonly TimeSpan ProcessTime =
|
|
|
|
TimeSpan.FromMilliseconds(50);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
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(
|
2021-01-21 01:22:18 +00:00
|
|
|
Task.Run(
|
|
|
|
() => this.ReportInReadLock(
|
|
|
|
ProcessTime * 0,
|
|
|
|
ProcessTime * 3,
|
|
|
|
1)),
|
|
|
|
Task.Run(
|
|
|
|
() => this.ReportInWriteLock(
|
|
|
|
ProcessTime * 1,
|
|
|
|
ProcessTime * 1,
|
|
|
|
2)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void ReadsDoNotBlockReads()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
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)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void ThreadedDataGatheringWorks()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
Task.Run(
|
|
|
|
() => this.ReportInReadLock(ProcessTime, ProcessTime, 1)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void UpgradableBlocksUpgradable()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
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)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void UpgradableDoesNotBlockReads()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
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)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void WriteBlockRead()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
Task.Run(
|
|
|
|
() => this.ReportInWriteLock(
|
|
|
|
ProcessTime,
|
|
|
|
ProcessTime * 3,
|
|
|
|
1)),
|
|
|
|
Task.Run(
|
|
|
|
() => this.ReportInReadLock(
|
|
|
|
ProcessTime * 2,
|
|
|
|
ProcessTime,
|
|
|
|
2)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void WriteBlockReads()
|
|
|
|
{
|
|
|
|
Task.WaitAll(
|
2021-01-21 01:22:18 +00:00
|
|
|
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)));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed + ": " + sequence + " => adding sequence");
|
2018-07-17 18:13:08 +00:00
|
|
|
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)
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed + ": " + sequence + " => starting process");
|
2018-07-17 18:13:08 +00:00
|
|
|
Thread.Sleep(processTime);
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed + ": " + sequence + " => finished process");
|
2018-07-17 18:13:08 +00:00
|
|
|
this.AddEvent(sequence);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReportInReadLock(
|
|
|
|
TimeSpan settleTime,
|
|
|
|
TimeSpan processTime,
|
|
|
|
int sequence)
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => entering read lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
Thread.Sleep(settleTime);
|
|
|
|
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => locking read lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
using (new ReadLock(this.locker))
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => starting read lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Report(processTime, sequence);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReportInUpgradableLock(
|
|
|
|
TimeSpan settleTime,
|
|
|
|
TimeSpan processTime,
|
|
|
|
int sequence)
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => entering upgradable lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
Thread.Sleep(settleTime);
|
|
|
|
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => locking upgradable lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
using (new UpgradableLock(this.locker))
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => starting upgradable lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Report(processTime, sequence);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReportInWriteLock(
|
|
|
|
TimeSpan settleTime,
|
|
|
|
TimeSpan processTime,
|
|
|
|
int sequence)
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => entering write lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
Thread.Sleep(settleTime);
|
|
|
|
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => locking write lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
using (new WriteLock(this.locker))
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
this.Elapsed
|
|
|
|
+ ": "
|
|
|
|
+ sequence
|
|
|
|
+ " => starting write lock: "
|
|
|
|
+ sequence);
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
this.Report(processTime, sequence);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Verifies the sequence of events.
|
|
|
|
/// </summary>
|
|
|
|
private void Verify()
|
|
|
|
{
|
2021-01-21 01:22:18 +00:00
|
|
|
this.output.WriteLine(
|
|
|
|
"Final Sequence: " + string.Join(", ", this.events));
|
2018-07-17 18:13:08 +00:00
|
|
|
|
|
|
|
for (int i = 1; i <= this.events.Count; i++)
|
|
|
|
{
|
|
|
|
Assert.Equal(i, this.events[i - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|