Skip to content

Commit a85a815

Browse files
authored
Merge pull request #159 from servicetitan/BitFaster_FastConcurrentLru
Use BitFaster.Caching LRU cache for QueryCache; Refactor common method implementations of ICache
2 parents 7b23854 + f434ac9 commit a85a815

17 files changed

+525
-393
lines changed
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using NUnit.Framework;
5+
using Xtensive.Caching;
6+
using Xtensive.Conversion;
7+
using Xtensive.Core;
8+
using Xtensive.Orm.Tests;
9+
10+
#pragma warning disable IDE0058
11+
12+
namespace Xtensive.Orm.Tests.Core.Caching
13+
{
14+
[TestFixture]
15+
public class FastConcurrentLruCacheTest
16+
{
17+
private FastConcurrentLruCache<string, TestClass> globalCache;
18+
private readonly Random random = RandomManager.CreateRandom((int) DateTime.Now.Ticks);
19+
20+
private class BadTestClass :
21+
IIdentified<string>,
22+
IHasSize
23+
{
24+
object IIdentified.Identifier
25+
{
26+
get { return Identifier; }
27+
}
28+
29+
public string Identifier
30+
{
31+
get { return null; }
32+
}
33+
34+
public long Size
35+
{
36+
get { return 1; }
37+
}
38+
}
39+
40+
[Test]
41+
public void ConstructorsTest()
42+
{
43+
var cache = new FastConcurrentLruCache<string, TestClass>(
44+
1000,
45+
value => value.Text);
46+
47+
var cache1 = new FastConcurrentLruCache<string, TestClass>(
48+
1000,
49+
(value) => value.Text
50+
);
51+
52+
53+
TestClass item = new TestClass("1");
54+
cache.Add(item);
55+
cache1.Add(item);
56+
Assert.AreEqual(1, cache1.Count);
57+
58+
for (int i = 0; i < 100000; i++) {
59+
TestClass test = new TestClass("" + i);
60+
cache1.Add(test);
61+
}
62+
}
63+
64+
[Test]
65+
public void ConstructorDenyTest()
66+
{
67+
Assert.Throws<ArgumentOutOfRangeException>(() => {
68+
var cache =
69+
new FastConcurrentLruCache<string, TestClass>(
70+
-1,
71+
value => value.Text
72+
);
73+
});
74+
}
75+
76+
[Test]
77+
public void AddRemoveTest()
78+
{
79+
var cache = new FastConcurrentLruCache<string, TestClass>(
80+
100,
81+
value => value.Text);
82+
83+
TestClass item = new TestClass("1");
84+
cache.Add(item);
85+
Assert.AreEqual(1, cache.Count);
86+
item = new TestClass("2");
87+
cache.Add(item);
88+
Assert.AreEqual(2, cache.Count);
89+
Assert.AreEqual(item, cache[item.Text, false]);
90+
ICache<string, TestClass> icache = cache;
91+
Assert.AreEqual(item, icache[item.Text, false]);
92+
Assert.AreEqual(null, icache["3", false]);
93+
cache.Remove(item);
94+
Assert.AreEqual(1, cache.Count);
95+
cache.Clear();
96+
Assert.AreEqual(0, cache.Count);
97+
}
98+
99+
[Test]
100+
public void AddDenyTest1()
101+
{
102+
var cache = new FastConcurrentLruCache<string, TestClass>(
103+
100,
104+
value => value.Text);
105+
Assert.Throws<ArgumentNullException>(() => cache.Add(null));
106+
}
107+
108+
[Test]
109+
public void AddDenyTest3()
110+
{
111+
var cache =
112+
new FastConcurrentLruCache<string, BadTestClass>(
113+
100,
114+
value => value.Identifier);
115+
Assert.Throws<ArgumentNullException>(() => cache.Add(new BadTestClass()));
116+
}
117+
118+
[Test]
119+
public void RemoveDenyTest1()
120+
{
121+
var cache =
122+
new FastConcurrentLruCache<string, TestClass>(
123+
100,
124+
value => value.Text);
125+
Assert.Throws<ArgumentNullException>(() => cache.Remove(null));
126+
}
127+
128+
[Test]
129+
public void RemoveDenyTest2()
130+
{
131+
var cache =
132+
new FastConcurrentLruCache<string, TestClass>(
133+
100,
134+
value => value.Text);
135+
Assert.Throws<ArgumentNullException>(() => cache.RemoveKey(null));
136+
}
137+
138+
[Test]
139+
public void RemoveDenyTest3()
140+
{
141+
var cache =
142+
new FastConcurrentLruCache<string, BadTestClass>(
143+
100,
144+
value => value.Identifier);
145+
BadTestClass test1 = new BadTestClass();
146+
Assert.Throws<ArgumentNullException>(() => cache.Remove(test1));
147+
}
148+
149+
private static readonly bool canFinish = true;
150+
151+
[Test]
152+
public void SynchronizationTest()
153+
{
154+
globalCache =
155+
new FastConcurrentLruCache<string, TestClass>(
156+
1000,
157+
value => value.Text);
158+
159+
using (new ThreadPoolThreadsIncreaser(20, 20)) {
160+
var addThreads = new Task[10];
161+
var removeThreads = new Task[10];
162+
var cancellationTokenSource = new CancellationTokenSource();
163+
164+
for (int i = 0; i < 10; i++) {
165+
addThreads[i] = new Task(() => AddItem(cancellationTokenSource.Token), cancellationTokenSource.Token);
166+
removeThreads[i] = new Task(() => RemoveItem(cancellationTokenSource.Token), cancellationTokenSource.Token);
167+
}
168+
169+
try {
170+
for (int i = 0; i < 10; i++) {
171+
addThreads[i].Start();
172+
}
173+
Thread.Sleep(10);
174+
175+
for (int i = 0; i < 10; i++) {
176+
removeThreads[i].Start();
177+
}
178+
Thread.Sleep(200);
179+
}
180+
finally {
181+
cancellationTokenSource.Cancel();
182+
Thread.Sleep(20);
183+
}
184+
}
185+
186+
Assert.IsTrue(globalCache.Count >= 0);
187+
globalCache = null;
188+
}
189+
190+
private void AddItem(CancellationToken cancellationToken)
191+
{
192+
int count = random.Next(100000);
193+
int counter = 0;
194+
bool whileCondition = (counter++) < 10 || !cancellationToken.IsCancellationRequested;
195+
while (!cancellationToken.IsCancellationRequested) {
196+
globalCache.Add(new TestClass("item " + count));
197+
count++;
198+
}
199+
cancellationToken.ThrowIfCancellationRequested();
200+
}
201+
202+
private void RemoveItem(CancellationToken cancellationToken)
203+
{
204+
int counter = 0;
205+
while (!cancellationToken.IsCancellationRequested) {
206+
TestClass test = null;
207+
foreach (TestClass testClass in globalCache) {
208+
test = testClass;
209+
break;
210+
}
211+
if (test != null)
212+
globalCache.Remove(test);
213+
}
214+
cancellationToken.ThrowIfCancellationRequested();
215+
}
216+
217+
private class ThreadPoolThreadsIncreaser : Disposable
218+
{
219+
private int previousWorkingThreadsCount;
220+
private int previousIOThreadsCount;
221+
222+
private static Func<int> A;
223+
private static Func<int> B;
224+
225+
private void Increase(int workingThreadsCount, int ioThreadsCount)
226+
{
227+
int minWorkingThreads;
228+
int minIOTheads;
229+
ThreadPool.GetMinThreads(out minWorkingThreads, out minIOTheads);
230+
previousWorkingThreadsCount = minWorkingThreads;
231+
previousIOThreadsCount = minIOTheads;
232+
233+
ThreadPool.SetMinThreads(workingThreadsCount, ioThreadsCount);
234+
}
235+
236+
private static void Decrease(Func<int> workingThreadsCountAcccessor, Func<int> ioThreadsCountAcccessor)
237+
{
238+
ThreadPool.SetMinThreads(workingThreadsCountAcccessor(), ioThreadsCountAcccessor());
239+
}
240+
241+
private int Aa()
242+
{
243+
return previousWorkingThreadsCount;
244+
}
245+
246+
private int Bb()
247+
{
248+
return previousIOThreadsCount;
249+
}
250+
251+
252+
public ThreadPoolThreadsIncreaser(int workingThreadsCount, int ioThreadsCount)
253+
: base((disposing) => Decrease(A, B))
254+
{
255+
Increase(workingThreadsCount, ioThreadsCount);
256+
A = Aa;
257+
B = Bb;
258+
}
259+
}
260+
}
261+
}

Orm/Xtensive.Orm.Tests.Core/Caching/LruCacheTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright (C) 2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
15
using System;
26
using System.Threading;
37
using System.Threading.Tasks;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (C) 2007-2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using Xtensive.Core;
10+
11+
namespace Xtensive.Caching
12+
{
13+
public abstract class CacheBase<TKey, TItem> : ICache<TKey, TItem>
14+
{
15+
/// <inheritdoc/>
16+
public virtual Converter<TItem, TKey> KeyExtractor { [DebuggerStepThrough]get; protected set; }
17+
18+
/// <inheritdoc/>
19+
public virtual TItem this[TKey key, bool markAsHit] => TryGetItem(key, markAsHit, out var item) ? item : default;
20+
21+
/// <inheritdoc/>
22+
public abstract int Count { get; }
23+
24+
/// <inheritdoc/>
25+
public abstract TItem Add(TItem item, bool replaceIfExists);
26+
27+
/// <inheritdoc/>
28+
public virtual void Add(TItem item) => Add(item, true);
29+
30+
/// <inheritdoc/>
31+
public virtual void Clear() => throw new NotImplementedException();
32+
33+
/// <inheritdoc/>
34+
public virtual bool ContainsKey(TKey key) => throw new NotImplementedException();
35+
36+
/// <inheritdoc/>
37+
public virtual IEnumerator<TItem> GetEnumerator() => throw new NotImplementedException();
38+
39+
/// <inheritdoc/>
40+
public virtual void Remove(TItem item)
41+
{
42+
ArgumentValidator.EnsureArgumentNotNull(item, "item");
43+
RemoveKey(KeyExtractor(item));
44+
}
45+
46+
/// <inheritdoc/>
47+
public abstract void RemoveKey(TKey key);
48+
49+
/// <inheritdoc/>
50+
public abstract void RemoveKey(TKey key, bool removeCompletely);
51+
52+
/// <inheritdoc/>
53+
public abstract bool TryGetItem(TKey key, bool markAsHit, out TItem item);
54+
}
55+
}
56+

0 commit comments

Comments
 (0)