211 lines
5.7 KiB
C#
211 lines
5.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using KellermanSoftware.CompareNetObjects;
|
|
using Xunit;
|
|
using Xunit.Sdk;
|
|
|
|
namespace MfGames.Locking.Tests;
|
|
|
|
public class LockTests
|
|
{
|
|
private readonly ReaderWriterLockSlim locker;
|
|
|
|
private readonly List<(int, LockAction)> sequenceRecord;
|
|
|
|
private readonly object syncLock;
|
|
|
|
public LockTests()
|
|
{
|
|
this.syncLock = new object();
|
|
this.sequenceRecord = new List<(int, LockAction)>();
|
|
this.locker = new ReaderWriterLockSlim();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadBlocksWrite()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestRead(1, 5, 1)),
|
|
Task.Run(() => this.TestWrite(2, 2, 2))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(1, LockAction.AfterLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadsDoNotBlockReads()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestRead(1, 8, 1)),
|
|
Task.Run(() => this.TestRead(2, 1, 2)),
|
|
Task.Run(() => this.TestRead(4, 1, 3))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock),
|
|
(3, LockAction.BeforeLock),
|
|
(3, LockAction.InLock),
|
|
(3, LockAction.AfterLock),
|
|
(1, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpgradableBlocksUpgradable()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestUpgradable(1, 10, 1)),
|
|
Task.Run(() => this.TestUpgradable(2, 2, 2)),
|
|
Task.Run(() => this.TestUpgradable(5, 1, 3))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(3, LockAction.BeforeLock),
|
|
(1, LockAction.AfterLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock),
|
|
(3, LockAction.InLock),
|
|
(3, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpgradableDoesNotBlockReads()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestUpgradable(1, 6, 1)),
|
|
Task.Run(() => this.TestRead(2, 1, 2)),
|
|
Task.Run(() => this.TestRead(4, 1, 3))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock),
|
|
(3, LockAction.BeforeLock),
|
|
(3, LockAction.InLock),
|
|
(3, LockAction.AfterLock),
|
|
(1, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WriteBlockRead()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestWrite(1, 6, 1)),
|
|
Task.Run(() => this.TestRead(2, 1, 2))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(1, LockAction.AfterLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WriteBlockReads()
|
|
{
|
|
await Task.WhenAll(
|
|
Task.Run(() => this.TestWrite(1, 6, 1)),
|
|
Task.Run(() => this.TestRead(2, 1, 2))
|
|
);
|
|
|
|
this.VerifySequenceRecord(
|
|
(1, LockAction.BeforeLock),
|
|
(1, LockAction.InLock),
|
|
(2, LockAction.BeforeLock),
|
|
(1, LockAction.AfterLock),
|
|
(2, LockAction.InLock),
|
|
(2, LockAction.AfterLock)
|
|
);
|
|
}
|
|
|
|
private void RecordSequenceAction(int sequence, LockAction action)
|
|
{
|
|
lock (this.syncLock)
|
|
{
|
|
this.sequenceRecord.Add((sequence, action));
|
|
}
|
|
}
|
|
|
|
private void Test(
|
|
Func<ReaderWriterLockSlim, IDisposable> getLock,
|
|
int settleTocks,
|
|
int waitTocks,
|
|
int sequence
|
|
)
|
|
{
|
|
const int TocksInMilliseconds = 200;
|
|
|
|
Thread.Sleep(TocksInMilliseconds * settleTocks);
|
|
|
|
this.RecordSequenceAction(sequence, LockAction.BeforeLock);
|
|
|
|
using (getLock(this.locker))
|
|
{
|
|
Thread.Sleep(100);
|
|
|
|
this.RecordSequenceAction(sequence, LockAction.InLock);
|
|
|
|
Thread.Sleep(TocksInMilliseconds * waitTocks);
|
|
|
|
this.RecordSequenceAction(sequence, LockAction.AfterLock);
|
|
}
|
|
}
|
|
|
|
private void TestRead(int settleTocks, int waitTocks, int sequence)
|
|
{
|
|
this.Test(x => new ReadLock(x), settleTocks, waitTocks, sequence);
|
|
}
|
|
|
|
private void TestUpgradable(int settleTocks, int waitTocks, int sequence)
|
|
{
|
|
this.Test(x => new UpgradableLock(x), settleTocks, waitTocks, sequence);
|
|
}
|
|
|
|
private void TestWrite(int settleTocks, int waitTocks, int sequence)
|
|
{
|
|
this.Test(x => new WriteLock(x), settleTocks, waitTocks, sequence);
|
|
}
|
|
|
|
private void VerifySequenceRecord(params (int, LockAction BeforeLock)[] values)
|
|
{
|
|
lock (this.syncLock)
|
|
{
|
|
var compareLogic = new CompareLogic
|
|
{
|
|
Config = { IgnoreObjectTypes = true, MaxDifferences = 2, }
|
|
};
|
|
ComparisonResult result = compareLogic.Compare(values, this.sequenceRecord);
|
|
|
|
if (!result.AreEqual)
|
|
{
|
|
throw new XunitException(result.DifferencesString);
|
|
}
|
|
}
|
|
}
|
|
}
|