Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions Orm/Xtensive.Orm.Tests.Core/Caching/FastConcurrentLruCacheTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Xtensive.Caching;
using Xtensive.Conversion;
using Xtensive.Core;
using Xtensive.Orm.Tests;

#pragma warning disable IDE0058

namespace Xtensive.Orm.Tests.Core.Caching
{
[TestFixture]
public class FastConcurrentLruCacheTest
{
private FastConcurrentLruCache<string, TestClass> globalCache;
private readonly Random random = RandomManager.CreateRandom((int) DateTime.Now.Ticks);

private class BadTestClass :
IIdentified<string>,
IHasSize
{
object IIdentified.Identifier
{
get { return Identifier; }
}

public string Identifier
{
get { return null; }
}

public long Size
{
get { return 1; }
}
}

[Test]
public void ConstructorsTest()
{
var cache = new FastConcurrentLruCache<string, TestClass>(
1000,
value => value.Text);

var cache1 = new FastConcurrentLruCache<string, TestClass>(
1000,
(value) => value.Text
);


TestClass item = new TestClass("1");
cache.Add(item);
cache1.Add(item);
Assert.AreEqual(1, cache1.Count);

for (int i = 0; i < 100000; i++) {
TestClass test = new TestClass("" + i);
cache1.Add(test);
}
}

[Test]
public void ConstructorDenyTest()
{
Assert.Throws<ArgumentOutOfRangeException>(() => {
var cache =
new FastConcurrentLruCache<string, TestClass>(
-1,
value => value.Text
);
});
}

[Test]
public void AddRemoveTest()
{
var cache = new FastConcurrentLruCache<string, TestClass>(
100,
value => value.Text);

TestClass item = new TestClass("1");
cache.Add(item);
Assert.AreEqual(1, cache.Count);
item = new TestClass("2");
cache.Add(item);
Assert.AreEqual(2, cache.Count);
Assert.AreEqual(item, cache[item.Text, false]);
ICache<string, TestClass> icache = cache;
Assert.AreEqual(item, icache[item.Text, false]);
Assert.AreEqual(null, icache["3", false]);
cache.Remove(item);
Assert.AreEqual(1, cache.Count);
cache.Clear();
Assert.AreEqual(0, cache.Count);
}

[Test]
public void AddDenyTest1()
{
var cache = new FastConcurrentLruCache<string, TestClass>(
100,
value => value.Text);
Assert.Throws<ArgumentNullException>(() => cache.Add(null));
}

[Test]
public void AddDenyTest3()
{
var cache =
new FastConcurrentLruCache<string, BadTestClass>(
100,
value => value.Identifier);
Assert.Throws<ArgumentNullException>(() => cache.Add(new BadTestClass()));
}

[Test]
public void RemoveDenyTest1()
{
var cache =
new FastConcurrentLruCache<string, TestClass>(
100,
value => value.Text);
Assert.Throws<ArgumentNullException>(() => cache.Remove(null));
}

[Test]
public void RemoveDenyTest2()
{
var cache =
new FastConcurrentLruCache<string, TestClass>(
100,
value => value.Text);
Assert.Throws<ArgumentNullException>(() => cache.RemoveKey(null));
}

[Test]
public void RemoveDenyTest3()
{
var cache =
new FastConcurrentLruCache<string, BadTestClass>(
100,
value => value.Identifier);
BadTestClass test1 = new BadTestClass();
Assert.Throws<ArgumentNullException>(() => cache.Remove(test1));
}

private static readonly bool canFinish = true;

[Test]
public void SynchronizationTest()
{
globalCache =
new FastConcurrentLruCache<string, TestClass>(
1000,
value => value.Text);

using (new ThreadPoolThreadsIncreaser(20, 20)) {
var addThreads = new Task[10];
var removeThreads = new Task[10];
var cancellationTokenSource = new CancellationTokenSource();

for (int i = 0; i < 10; i++) {
addThreads[i] = new Task(() => AddItem(cancellationTokenSource.Token), cancellationTokenSource.Token);
removeThreads[i] = new Task(() => RemoveItem(cancellationTokenSource.Token), cancellationTokenSource.Token);
}

try {
for (int i = 0; i < 10; i++) {
addThreads[i].Start();
}
Thread.Sleep(10);

for (int i = 0; i < 10; i++) {
removeThreads[i].Start();
}
Thread.Sleep(200);
}
finally {
cancellationTokenSource.Cancel();
Thread.Sleep(20);
}
}

Assert.IsTrue(globalCache.Count >= 0);
globalCache = null;
}

private void AddItem(CancellationToken cancellationToken)
{
int count = random.Next(100000);
int counter = 0;
bool whileCondition = (counter++) < 10 || !cancellationToken.IsCancellationRequested;
while (!cancellationToken.IsCancellationRequested) {
globalCache.Add(new TestClass("item " + count));
count++;
}
cancellationToken.ThrowIfCancellationRequested();
}

private void RemoveItem(CancellationToken cancellationToken)
{
int counter = 0;
while (!cancellationToken.IsCancellationRequested) {
TestClass test = null;
foreach (TestClass testClass in globalCache) {
test = testClass;
break;
}
if (test != null)
globalCache.Remove(test);
}
cancellationToken.ThrowIfCancellationRequested();
}

private class ThreadPoolThreadsIncreaser : Disposable
{
private int previousWorkingThreadsCount;
private int previousIOThreadsCount;

private static Func<int> A;
private static Func<int> B;

private void Increase(int workingThreadsCount, int ioThreadsCount)
{
int minWorkingThreads;
int minIOTheads;
ThreadPool.GetMinThreads(out minWorkingThreads, out minIOTheads);
previousWorkingThreadsCount = minWorkingThreads;
previousIOThreadsCount = minIOTheads;

ThreadPool.SetMinThreads(workingThreadsCount, ioThreadsCount);
}

private static void Decrease(Func<int> workingThreadsCountAcccessor, Func<int> ioThreadsCountAcccessor)
{
ThreadPool.SetMinThreads(workingThreadsCountAcccessor(), ioThreadsCountAcccessor());
}

private int Aa()
{
return previousWorkingThreadsCount;
}

private int Bb()
{
return previousIOThreadsCount;
}


public ThreadPoolThreadsIncreaser(int workingThreadsCount, int ioThreadsCount)
: base((disposing) => Decrease(A, B))
{
Increase(workingThreadsCount, ioThreadsCount);
A = Aa;
B = Bb;
}
}
}
}
4 changes: 4 additions & 0 deletions Orm/Xtensive.Orm.Tests.Core/Caching/LruCacheTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright (C) 2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Threading;
using System.Threading.Tasks;
Expand Down
56 changes: 56 additions & 0 deletions Orm/Xtensive.Orm/Caching/CacheBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) 2007-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Xtensive.Core;

namespace Xtensive.Caching
{
public abstract class CacheBase<TKey, TItem> : ICache<TKey, TItem>
{
/// <inheritdoc/>
public virtual Converter<TItem, TKey> KeyExtractor { [DebuggerStepThrough]get; protected set; }

/// <inheritdoc/>
public virtual TItem this[TKey key, bool markAsHit] => TryGetItem(key, markAsHit, out var item) ? item : default;

/// <inheritdoc/>
public abstract int Count { get; }

/// <inheritdoc/>
public abstract TItem Add(TItem item, bool replaceIfExists);

/// <inheritdoc/>
public virtual void Add(TItem item) => Add(item, true);

/// <inheritdoc/>
public virtual void Clear() => throw new NotImplementedException();

/// <inheritdoc/>
public virtual bool ContainsKey(TKey key) => throw new NotImplementedException();

/// <inheritdoc/>
public virtual IEnumerator<TItem> GetEnumerator() => throw new NotImplementedException();

/// <inheritdoc/>
public virtual void Remove(TItem item)
{
ArgumentValidator.EnsureArgumentNotNull(item, "item");
RemoveKey(KeyExtractor(item));
}

/// <inheritdoc/>
public abstract void RemoveKey(TKey key);

/// <inheritdoc/>
public abstract void RemoveKey(TKey key, bool removeCompletely);

/// <inheritdoc/>
public abstract bool TryGetItem(TKey key, bool markAsHit, out TItem item);
}
}

Loading