Skip to content

Commit 790b384

Browse files
maca88fredericDelaporte
authored andcommitted
Add Redis provider with batching support (#45)
1 parent 2657d9b commit 790b384

File tree

78 files changed

+6488
-232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+6488
-232
lines changed

AsyncGenerator.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,61 @@
2828
registerPlugin:
2929
- type: AsyncGenerator.Core.Plugins.EmptyRegionRemover
3030
assemblyName: AsyncGenerator.Core
31+
- filePath: StackExchangeRedis\NHibernate.Caches.StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.csproj
32+
targetFramework: net461
33+
concurrentRun: true
34+
applyChanges: true
35+
analyzation:
36+
methodConversion:
37+
- conversion: Ignore
38+
hasAttributeName: ObsoleteAttribute
39+
- conversion: Smart
40+
containingTypeName: AbstractRegionStrategy
41+
- conversion: Smart
42+
containingTypeName: RedisKeyLocker
43+
callForwarding: true
44+
cancellationTokens:
45+
guards: true
46+
methodParameter:
47+
- parameter: Required
48+
requiresCancellationToken:
49+
- containingType: NHibernate.Caches.StackExchangeRedis.RedisCache
50+
name: GetMany
51+
- containingType: NHibernate.Caches.StackExchangeRedis.RedisCache
52+
name: PutMany
53+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
54+
name: Get
55+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
56+
name: GetMany
57+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
58+
name: Put
59+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
60+
name: PutMany
61+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
62+
name: Remove
63+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
64+
name: Unlock
65+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
66+
name: UnlockMany
67+
- containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy
68+
name: Clear
69+
- containingType: NHibernate.Caches.StackExchangeRedis.RedisKeyLocker
70+
name: Unlock
71+
- containingType: NHibernate.Caches.StackExchangeRedis.RedisKeyLocker
72+
name: UnlockMany
73+
scanMethodBody: true
74+
searchAsyncCounterpartsInInheritedTypes: true
75+
scanForMissingAsyncMembers:
76+
- all: true
77+
transformation:
78+
configureAwaitArgument: false
79+
localFunctions: true
80+
asyncLock:
81+
type: NHibernate.Util.AsyncLock
82+
methodName: LockAsync
83+
registerPlugin:
84+
- type: AsyncGenerator.Core.Plugins.EmptyRegionRemover
85+
assemblyName: AsyncGenerator.Core
3186
- filePath: NHibernate.Caches.Common.Tests\NHibernate.Caches.Common.Tests.csproj
3287
targetFramework: net461
3388
concurrentRun: true
@@ -356,6 +411,54 @@
356411
registerPlugin:
357412
- type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
358413
assemblyName: AsyncGenerator.Core
414+
- filePath: StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.Tests\NHibernate.Caches.StackExchangeRedis.Tests.csproj
415+
targetFramework: net461
416+
concurrentRun: true
417+
applyChanges: true
418+
analyzation:
419+
methodConversion:
420+
- conversion: Ignore
421+
hasAttributeName: OneTimeSetUpAttribute
422+
- conversion: Ignore
423+
hasAttributeName: OneTimeTearDownAttribute
424+
- conversion: Ignore
425+
hasAttributeName: SetUpAttribute
426+
- conversion: Ignore
427+
hasAttributeName: TearDownAttribute
428+
- conversion: Smart
429+
hasAttributeName: TestAttribute
430+
- conversion: Smart
431+
hasAttributeName: TheoryAttribute
432+
preserveReturnType:
433+
- hasAttributeName: TestAttribute
434+
- hasAttributeName: TheoryAttribute
435+
typeConversion:
436+
- conversion: Ignore
437+
hasAttributeName: IgnoreAttribute
438+
- conversion: Partial
439+
hasAttributeName: TestFixtureAttribute
440+
- conversion: Partial
441+
anyBaseTypeRule: HasTestFixtureAttribute
442+
ignoreSearchForMethodReferences:
443+
- hasAttributeName: TheoryAttribute
444+
- hasAttributeName: TestAttribute
445+
cancellationTokens:
446+
withoutCancellationToken:
447+
- hasAttributeName: TestAttribute
448+
- hasAttributeName: TheoryAttribute
449+
exceptionHandling:
450+
catchMethodBody:
451+
- all: true
452+
result: false
453+
ignoreSearchForAsyncCounterparts:
454+
- name: Disassemble
455+
scanMethodBody: true
456+
searchAsyncCounterpartsInInheritedTypes: true
457+
scanForMissingAsyncMembers:
458+
- all: true
459+
registerPlugin:
460+
- type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
461+
assemblyName: AsyncGenerator.Core
359462
- filePath: SysCache\NHibernate.Caches.SysCache.Tests\NHibernate.Caches.SysCache.Tests.csproj
360463
targetFramework: net461
361464
concurrentRun: true

NHibernate.Caches.Common.Tests/Async/CacheFixture.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
using System;
1212
using System.Collections.Generic;
13+
using System.Linq;
1314
using System.Threading;
1415
using NHibernate.Cache;
1516
using NUnit.Framework;
@@ -61,6 +62,63 @@ public async Task TestRemoveAsync()
6162
Assert.That(item, Is.Null, "item still exists in cache after remove");
6263
}
6364

65+
[Test]
66+
public async Task TestLockUnlockAsync()
67+
{
68+
if (!SupportsLocking)
69+
Assert.Ignore("Test not supported by provider");
70+
71+
const string key = "keyTestLock";
72+
const string value = "valueLock";
73+
74+
var cache = GetDefaultCache();
75+
76+
// add the item
77+
await (cache.PutAsync(key, value, CancellationToken.None));
78+
79+
await (cache.LockAsync(key, CancellationToken.None));
80+
Assert.ThrowsAsync<CacheException>(() => cache.LockAsync(key, CancellationToken.None));
81+
82+
await (Task.Delay(cache.Timeout / Timestamper.OneMs));
83+
84+
for (var i = 0; i < 2; i++)
85+
{
86+
var lockValue = await (cache.LockAsync(key, CancellationToken.None));
87+
await (cache.UnlockAsync(key, lockValue, CancellationToken.None));
88+
}
89+
}
90+
91+
[Test]
92+
public async Task TestConcurrentLockUnlockAsync()
93+
{
94+
if (!SupportsLocking)
95+
Assert.Ignore("Test not supported by provider");
96+
97+
const string value = "value";
98+
const string key = "keyToLock";
99+
100+
var cache = GetDefaultCache();
101+
102+
await (cache.PutAsync(key, value, CancellationToken.None));
103+
Assert.That(await (cache.GetAsync(key, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key");
104+
105+
// Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads
106+
// Thread 1
107+
var lockValue = await (cache.LockAsync(key, CancellationToken.None));
108+
// Thread 2
109+
Assert.ThrowsAsync<CacheException>(() => cache.LockAsync(key, CancellationToken.None), "The key should be locked");
110+
// Thread 3
111+
Assert.ThrowsAsync<CacheException>(() => cache.LockAsync(key, CancellationToken.None), "The key should still be locked");
112+
113+
// Thread 1
114+
await (cache.UnlockAsync(key, lockValue, CancellationToken.None));
115+
116+
Assert.DoesNotThrowAsync(async () => lockValue = await (cache.LockAsync(key, CancellationToken.None)), "The key should be unlocked");
117+
await (cache.UnlockAsync(key, lockValue, CancellationToken.None));
118+
119+
await (cache.RemoveAsync(key, CancellationToken.None));
120+
}
121+
64122
[Test]
65123
public async Task TestClearAsync()
66124
{
@@ -134,6 +192,108 @@ public async Task TestRegionsAsync()
134192
Assert.That(get2, Is.EqualTo(s2), "Unexpected value in cache2");
135193
}
136194

195+
[Test]
196+
public async Task TestPutManyAsync()
197+
{
198+
var keys = new object[10];
199+
var values = new object[10];
200+
for (var i = 0; i < keys.Length; i++)
201+
{
202+
keys[i] = $"keyTestPut{i}";
203+
values[i] = $"valuePut{i}";
204+
}
205+
206+
var cache = GetDefaultCache();
207+
// Due to async version, it may already be there.
208+
foreach (var key in keys)
209+
await (cache.RemoveAsync(key, CancellationToken.None));
210+
211+
Assert.That(await (cache.GetManyAsync(keys, CancellationToken.None)), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!");
212+
213+
await (cache.PutManyAsync(keys, values, CancellationToken.None));
214+
var items = await (cache.GetManyAsync(keys, CancellationToken.None));
215+
216+
for (var i = 0; i < items.Length; i++)
217+
{
218+
var item = items[i];
219+
Assert.That(item, Is.Not.Null, "unable to retrieve cached item");
220+
Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added");
221+
}
222+
}
223+
224+
[Test]
225+
public async Task TestLockUnlockManyAsync()
226+
{
227+
if (!SupportsLocking)
228+
Assert.Ignore("Test not supported by provider");
229+
230+
var keys = new object[10];
231+
var values = new object[10];
232+
for (var i = 0; i < keys.Length; i++)
233+
{
234+
keys[i] = $"keyTestLock{i}";
235+
values[i] = $"valueLock{i}";
236+
}
237+
238+
var cache = GetDefaultCache();
239+
240+
// add the item
241+
await (cache.PutManyAsync(keys, values, CancellationToken.None));
242+
await (cache.LockManyAsync(keys, CancellationToken.None));
243+
Assert.ThrowsAsync<CacheException>(() => cache.LockManyAsync(keys, CancellationToken.None), "all items should be locked");
244+
245+
await (Task.Delay(cache.Timeout / Timestamper.OneMs));
246+
247+
for (var i = 0; i < 2; i++)
248+
{
249+
Assert.DoesNotThrowAsync(async () =>
250+
{
251+
await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys, CancellationToken.None)), CancellationToken.None));
252+
}, "the items should be unlocked");
253+
}
254+
255+
// Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys.
256+
var lockValue = await (cache.LockManyAsync(keys.Take(5).ToArray(), CancellationToken.None));
257+
258+
Assert.ThrowsAsync<CacheException>(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "the fifth key should be locked");
259+
260+
Assert.DoesNotThrowAsync(async () =>
261+
{
262+
await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys.Skip(5).ToArray(), CancellationToken.None)), CancellationToken.None));
263+
}, "the last 5 keys should not be locked.");
264+
265+
// Unlock the first 5 keys
266+
await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None));
267+
268+
Assert.DoesNotThrowAsync(async () =>
269+
{
270+
lockValue = await (cache.LockManyAsync(keys, CancellationToken.None));
271+
await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None));
272+
}, "the first 5 keys should not be locked.");
273+
}
274+
275+
[Test]
276+
public void TestNullKeyPutManyAsync()
277+
{
278+
var cache = GetDefaultCache();
279+
Assert.ThrowsAsync<ArgumentNullException>(() => cache.PutManyAsync(null, null, CancellationToken.None));
280+
}
281+
282+
[Test]
283+
public void TestNullValuePutManyAsync()
284+
{
285+
var cache = GetDefaultCache();
286+
Assert.ThrowsAsync<ArgumentNullException>(() => cache.PutManyAsync(new object[] { "keyTestNullValuePut" }, null, CancellationToken.None));
287+
}
288+
289+
[Test]
290+
public async Task TestNullKeyGetManyAsync()
291+
{
292+
var cache = GetDefaultCache();
293+
await (cache.PutAsync("keyTestNullKeyGet", "value", CancellationToken.None));
294+
Assert.ThrowsAsync<ArgumentNullException>(() => cache.GetManyAsync(null, CancellationToken.None));
295+
}
296+
137297
[Test]
138298
public async Task TestNonEqualObjectsWithEqualHashCodeAndToStringAsync()
139299
{

0 commit comments

Comments
 (0)