Skip to content

Commit

Permalink
Improve IDistributedCache implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cnblogs-dudu committed Jan 2, 2023
1 parent 52e69f8 commit 5f53d02
Show file tree
Hide file tree
Showing 14 changed files with 526 additions and 95 deletions.
13 changes: 13 additions & 0 deletions DistributedCacheTets/DistributedCacheCollection.cs
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>
{
}
}
37 changes: 37 additions & 0 deletions DistributedCacheTets/DistributedCacheFixture.cs
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();
}
}
94 changes: 94 additions & 0 deletions DistributedCacheTets/DistributedCacheTests.cs
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));
}
}
35 changes: 35 additions & 0 deletions DistributedCacheTets/DistributedCacheTests.csproj
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>
1 change: 1 addition & 0 deletions DistributedCacheTets/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
10 changes: 10 additions & 0 deletions DistributedCacheTets/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"enyimMemcached": {
"Servers": [
{
"Address": "memcached",
"Port": 11211
}
]
}
}
133 changes: 133 additions & 0 deletions Enyim.Caching/DistributedCache.cs
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
}
}
12 changes: 12 additions & 0 deletions Enyim.Caching/IMemcachedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,25 @@ namespace Enyim.Caching
public interface IMemcachedClient : IDisposable
{
bool Add(string key, object value, int cacheSeconds);
bool Add(string key, object value, uint cacheSeconds);
bool Add(string key, object value, TimeSpan timeSpan);
Task<bool> AddAsync(string key, object value, int cacheSeconds);
Task<bool> AddAsync(string key, object value, uint cacheSeconds);
Task<bool> AddAsync(string key, object value, TimeSpan timeSpan);

bool Set(string key, object value, int cacheSeconds);
bool Set(string key, object value, uint cacheSeconds);
bool Set(string key, object value, TimeSpan timeSpan);
Task<bool> SetAsync(string key, object value, int cacheSeconds);
Task<bool> SetAsync(string key, object value, uint cacheSeconds);
Task<bool> SetAsync(string key, object value, TimeSpan timeSpan);

bool Replace(string key, object value, int cacheSeconds);
bool Replace(string key, object value, uint cacheSeconds);
bool Replace(string key, object value, TimeSpan timeSpan);
Task<bool> ReplaceAsync(string key, object value, int cacheSeconds);
Task<bool> ReplaceAsync(string key, object value, uint cacheSeconds);
Task<bool> ReplaceAsync(string key, object value, TimeSpan timeSpan);

Task<IGetOperationResult> GetAsync(string key);
Task<IGetOperationResult<T>> GetAsync<T>(string key);
Expand Down
Loading

0 comments on commit 5f53d02

Please sign in to comment.