forked from enyim/EnyimMemcached
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve IDistributedCache implementation
- Loading branch information
1 parent
52e69f8
commit 5f53d02
Showing
14 changed files
with
526 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace ServiceStackRedisCacheTests | ||
{ | ||
[CollectionDefinition(nameof(DistributedCacheCollection))] | ||
public class DistributedCacheCollection : ICollectionFixture<DistributedCacheFixture> | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows.Markup; | ||
using Microsoft.Extensions.Caching.Distributed; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace ServiceStackRedisCacheTests; | ||
|
||
public class DistributedCacheFixture | ||
{ | ||
public IDistributedCache DistributedCache { get; private set; } | ||
|
||
public DistributedCacheFixture() | ||
{ | ||
using IServiceScope scope = GetServiceProvider().CreateScope(); | ||
DistributedCache = scope.ServiceProvider.GetRequiredService<IDistributedCache>(); | ||
} | ||
|
||
private IServiceProvider GetServiceProvider() | ||
{ | ||
IServiceCollection services = new ServiceCollection(); | ||
IConfiguration conf = new ConfigurationBuilder(). | ||
AddJsonFile("appsettings.json", optional: false) | ||
.Build(); | ||
services.AddSingleton(conf); | ||
services.AddLogging(); | ||
services.AddEnyimMemcached(); | ||
return services.BuildServiceProvider(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
using Enyim.Caching; | ||
using Microsoft.Extensions.Caching.Distributed; | ||
using Xunit.Priority; | ||
|
||
namespace ServiceStackRedisCacheTests; | ||
|
||
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] | ||
[Collection(nameof(DistributedCacheCollection))] | ||
public class DistributedCacheTests | ||
{ | ||
private const string _value = "Coding changes the world"; | ||
private readonly IDistributedCache _cache; | ||
|
||
public DistributedCacheTests(DistributedCacheFixture fixture) | ||
{ | ||
_cache = fixture.DistributedCache; | ||
} | ||
|
||
[Fact] | ||
public async Task Cache_with_absolute_expiration() | ||
{ | ||
var key = nameof(Cache_with_absolute_expiration) + "_" + Guid.NewGuid(); | ||
var keyAsync = nameof(Cache_with_absolute_expiration) + "_async_" + Guid.NewGuid(); | ||
|
||
var options = new DistributedCacheEntryOptions | ||
{ | ||
AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2) | ||
}; | ||
|
||
_cache.SetString(key, _value, options); | ||
await _cache.SetStringAsync(keyAsync, _value, options); | ||
|
||
Assert.Equal(_value, _cache.GetString(key)); | ||
Assert.Equal(_value, await _cache.GetStringAsync(keyAsync)); | ||
|
||
await Task.Delay(TimeSpan.FromSeconds(3)); | ||
|
||
Assert.Null(_cache.GetString(key)); | ||
Assert.Null(await _cache.GetStringAsync(keyAsync)); | ||
} | ||
|
||
[Fact] | ||
public async Task Cache_with_relative_to_now() | ||
{ | ||
var key = nameof(Cache_with_relative_to_now) + "_" + Guid.NewGuid(); | ||
var keyAsync = nameof(Cache_with_relative_to_now) + "_async_" + Guid.NewGuid(); | ||
|
||
var options = new DistributedCacheEntryOptions | ||
{ | ||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(2) | ||
}; | ||
|
||
_cache.SetString(key, _value, options); | ||
await _cache.SetStringAsync(keyAsync, _value, options); | ||
|
||
Assert.Equal(_value, _cache.GetString(key)); | ||
Assert.Equal(_value, await _cache.GetStringAsync(keyAsync)); | ||
|
||
await Task.Delay(TimeSpan.FromSeconds(3)); | ||
|
||
Assert.Null(_cache.GetString(key)); | ||
Assert.Null(await _cache.GetStringAsync(keyAsync)); | ||
} | ||
|
||
[Fact] | ||
public async Task Cache_with_sliding_expiration() | ||
{ | ||
var key = nameof(Cache_with_sliding_expiration) + "_" + Guid.NewGuid(); | ||
var keyAsync = nameof(Cache_with_sliding_expiration) + "_async_" + Guid.NewGuid(); | ||
|
||
var options = new DistributedCacheEntryOptions | ||
{ | ||
SlidingExpiration = TimeSpan.FromSeconds(3) | ||
}; | ||
|
||
_cache.SetString(key, _value, options); | ||
await _cache.SetStringAsync(keyAsync, _value, options); | ||
|
||
Assert.Equal(_value, _cache.GetString(key)); | ||
Assert.Equal(_value, await _cache.GetStringAsync(keyAsync)); | ||
|
||
await Task.Delay(2000); | ||
_cache.Refresh(key); | ||
await _cache.RefreshAsync(keyAsync); | ||
|
||
await Task.Delay(2000); | ||
Assert.Equal(_value, _cache.GetString(key)); | ||
Assert.Equal(_value, await _cache.GetStringAsync(keyAsync)); | ||
|
||
await Task.Delay(3100); | ||
Assert.Null(_cache.GetString(key)); | ||
Assert.Null(await _cache.GetStringAsync(keyAsync)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> | ||
<PackageReference Include="xunit" Version="2.4.2" /> | ||
<PackageReference Include="Xunit.Priority" Version="1.1.6" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Enyim.Caching\Enyim.Caching.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="appsettings.json"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global using Xunit; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"enyimMemcached": { | ||
"Servers": [ | ||
{ | ||
"Address": "memcached", | ||
"Port": 11211 | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
using Microsoft.Extensions.Caching.Distributed; | ||
using Microsoft.Extensions.Logging; | ||
using System.Threading.Tasks; | ||
using System.Threading; | ||
using Enyim.Caching.Memcached; | ||
using Microsoft.Extensions.Caching.Memory; | ||
using System; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Enyim.Caching | ||
{ | ||
public partial class MemcachedClient | ||
{ | ||
#region Implement IDistributedCache | ||
|
||
byte[] IDistributedCache.Get(string key) | ||
{ | ||
var value = Get<byte[]>(key); | ||
|
||
if (value != null) | ||
{ | ||
Refresh(key); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
async Task<byte[]> IDistributedCache.GetAsync(string key, CancellationToken token = default) | ||
{ | ||
var value = await GetValueAsync<byte[]>(key); | ||
|
||
if (value != null) | ||
{ | ||
await RefreshAsync(key); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
void IDistributedCache.Set(string key, byte[] value, DistributedCacheEntryOptions options) | ||
{ | ||
ulong tmp = 0; | ||
var expiration = GetExpiration(options); | ||
PerformStore(StoreMode.Set, key, value, expiration, ref tmp, out var status); | ||
|
||
if (options.SlidingExpiration.HasValue) | ||
{ | ||
var sldExp = options.SlidingExpiration.Value; | ||
Add(GetSlidingExpirationKey(key), sldExp.ToString(), sldExp); | ||
} | ||
} | ||
|
||
async Task IDistributedCache.SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) | ||
{ | ||
var expiration = GetExpiration(options); | ||
await PerformStoreAsync(StoreMode.Set, key, value, expiration); | ||
|
||
if (options.SlidingExpiration.HasValue) | ||
{ | ||
var sldExp = options.SlidingExpiration.Value; | ||
await AddAsync(GetSlidingExpirationKey(key), sldExp.ToString(), sldExp); | ||
} | ||
} | ||
|
||
public void Refresh(string key) | ||
{ | ||
var sldExpKey = GetSlidingExpirationKey(key); | ||
var sldExpStr = Get<string>(sldExpKey); | ||
if (!string.IsNullOrEmpty(sldExpStr) | ||
&& TimeSpan.TryParse(sldExpStr, out var sldExp)) | ||
{ | ||
var value = Get(key); | ||
if (value != null) | ||
{ | ||
Replace(key, value, sldExp); | ||
Replace(sldExpKey, sldExpStr, sldExp); | ||
} | ||
} | ||
} | ||
|
||
public async Task RefreshAsync(string key, CancellationToken token = default) | ||
{ | ||
var sldExpKey = GetSlidingExpirationKey(key); | ||
var sldExpStr = await GetValueAsync<string>(sldExpKey); | ||
if (!string.IsNullOrEmpty(sldExpStr) | ||
&& TimeSpan.TryParse(sldExpStr, out var sldExp)) | ||
{ | ||
var value = (await GetAsync(key)).Value; | ||
if (value != null) | ||
{ | ||
await ReplaceAsync(key, value, sldExp); | ||
await ReplaceAsync(sldExpKey, sldExpStr, sldExp); | ||
} | ||
} | ||
} | ||
|
||
void IDistributedCache.Remove(string key) | ||
{ | ||
Remove(key); | ||
Remove(GetSlidingExpirationKey(key)); | ||
} | ||
|
||
async Task IDistributedCache.RemoveAsync(string key, CancellationToken token = default) | ||
{ | ||
await RemoveAsync(key); | ||
await RemoveAsync(GetSlidingExpirationKey(key)); | ||
} | ||
|
||
private uint GetExpiration(DistributedCacheEntryOptions options) | ||
{ | ||
if (options.SlidingExpiration.HasValue) | ||
{ | ||
return GetExpiration(options.SlidingExpiration); | ||
} | ||
else if (options.AbsoluteExpirationRelativeToNow.HasValue) | ||
{ | ||
return GetExpiration(null, relativeToNow: options.AbsoluteExpirationRelativeToNow.Value); | ||
} | ||
else if (options.AbsoluteExpiration.HasValue) | ||
{ | ||
return GetExpiration(null, absoluteExpiration: options.AbsoluteExpiration.Value); | ||
} | ||
else | ||
{ | ||
throw new ArgumentException("Invalid enum value for options", nameof(options)); | ||
} | ||
} | ||
|
||
private string GetSlidingExpirationKey(string key) => $"{key}-sliding-expiration"; | ||
|
||
#endregion | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.