Skip to content

Commit

Permalink
Merge pull request #157 from safetree/feature/echofool/fix_get-async_…
Browse files Browse the repository at this point in the history
…return_with_cas

fix GetAsync's return IGetOperation with correct Cas value
  • Loading branch information
cnblogs-dudu authored Jan 27, 2021
2 parents 439f4a1 + 488549f commit 2b27236
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 9 deletions.
74 changes: 69 additions & 5 deletions Enyim.Caching.Tests/MemcachedClientCasTests.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using Enyim.Caching.Memcached;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Enyim.Caching.Tests
{

public class MemcachedClientCasTests : MemcachedClientTestsBase
public class MemcachedClientCasTests : MemcachedClientTestsBase
{

[Fact]
Expand All @@ -35,7 +34,72 @@ public void When_Storing_Item_With_Invalid_Cas_Result_Is_Not_Successful()
StoreAssertFail(casResult);
}

}

[Fact]
public async Task When_Storing_Item_With_Valid_Cas_Result_Is_Successful_Async()
{
var key = GetUniqueKey("cas");
var value = GetRandomString();
var comment = new Comment
{
Author = key,
Text = value
};

var storeResult = Store(StoreMode.Add, key, comment);
StoreAssertPass(storeResult);

var casResult1 = await _client.GetAsync(key);
GetAssertPass(casResult1, comment);

var casResult2 = await _client.GetAsync<Comment>(key);
GetAssertPass(casResult2, comment);
}

/// <summary>
/// comment
/// because <see cref="IMemcachedClient"/> use <see cref="Newtonsoft"/> as default serialization tool,
/// so <see cref="IMemcachedClient.GetAsync(string)"/> will return <see cref="IGetOperation"/> with <see cref="JObject"/> as <see cref="IGetOperation.Result"/>'s type.
/// </summary>
public class Comment : IEquatable<Comment>, IEquatable<JObject>
{
public string Author { get; set; }

public string Text { get; set; }

public bool Equals(Comment other)
{
return other != null && other.Author == Author && other.Text == Text;
}

public bool Equals(JObject other)
{
return Equals(other?.ToObject<Comment>());
}

public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj is Comment comment)
{
return Equals(comment);
}
if (obj is JObject jObject)
{
return Equals(jObject);
}
return false;
}

public override int GetHashCode()
{
return HashCode.Combine(Author, Text);
}
}
}
}

#region [ License information ]
Expand Down
8 changes: 6 additions & 2 deletions Enyim.Caching.Tests/MemcachedClientMutateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ public async Task When_Touch_Item_Result_Is_Successful()
{
var key = GetUniqueKey("touch");
await _client.AddAsync(key, "value", 1);
Assert.True((await _client.GetAsync<string>(key)).Success);
var operationResult = await _client.GetAsync<string>(key);
Assert.True(operationResult.Success);
Assert.NotEqual(0UL, operationResult.Cas);
var result = await _client.TouchAsync(key, TimeSpan.FromSeconds(60));
await Task.Delay(1010);
Assert.True(result.Success, "Success was false");
Assert.True((result.StatusCode ?? 0) == 0, "StatusCode was not null or 0");
Assert.True((await _client.GetAsync<string>(key)).Success);
operationResult = await _client.GetAsync<string>(key);
Assert.True(operationResult.Success);
Assert.NotEqual(0UL, operationResult.Cas);
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions Enyim.Caching.Tests/MemcachedClientTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ protected void GetAssertPass(IGetOperationResult result, object expectedValue)
Assert.Equal(expectedValue, result.Value);
}

protected void GetAssertPass<T>(IGetOperationResult<T> result, T expectedValue)
{
Assert.True(result.Success, "Success was false");
Assert.True(result.Cas > 0, "Cas value was 0");
Assert.True((result.StatusCode ?? 0) == 0, "StatusCode was neither 0 nor null");
Assert.Equal(expectedValue, result.Value);
}

protected void GetAssertFail(IGetOperationResult result)
{
Assert.False(result.Success, "Success was true");
Expand Down
1 change: 1 addition & 0 deletions Enyim.Caching/IMemcachedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface IMemcachedClient : IDisposable
bool Replace(string key, object value, int cacheSeconds);
Task<bool> ReplaceAsync(string key, object value, int cacheSeconds);

Task<IGetOperationResult> GetAsync(string key);
Task<IGetOperationResult<T>> GetAsync<T>(string key);
Task<T> GetValueAsync<T>(string key);
Task<T> GetValueOrCreateAsync<T>(string key, int cacheSeconds, Func<Task<T>> generator);
Expand Down
57 changes: 57 additions & 0 deletions Enyim.Caching/MemcachedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,25 @@ public IGetOperationResult<T> PerformGet<T>(string key)
}
}

private bool CreateGetCommand(string key, out IGetOperationResult result, out IMemcachedNode node, out IGetOperation command)
{
result = new DefaultGetOperationResultFactory().Create();
var hashedKey = this.keyTransformer.Transform(key);

node = this.pool.Locate(hashedKey);
if (node == null)
{
var errorMessage = $"Unable to locate node with \"{key}\" key";
_logger.LogError(errorMessage);
result.Fail(errorMessage);
command = null;
return false;
}

command = this.pool.OperationFactory.Get(hashedKey);
return true;
}

private bool CreateGetCommand<T>(string key, out IGetOperationResult<T> result, out IMemcachedNode node, out IGetOperation command)
{
result = new DefaultGetOperationResultFactory<T>().Create();
Expand All @@ -179,11 +198,28 @@ private bool CreateGetCommand<T>(string key, out IGetOperationResult<T> result,
return true;
}

private IGetOperationResult BuildGetCommandResult(IGetOperationResult result, IGetOperation command, IOperationResult commandResult)
{
if (commandResult.Success)
{
result.Value = transcoder.Deserialize(command.Result);
result.Cas = command.CasValue;
result.Pass();
}
else
{
commandResult.Combine(result);
}

return result;
}

private IGetOperationResult<T> BuildGetCommandResult<T>(IGetOperationResult<T> result, IGetOperation command, IOperationResult commandResult)
{
if (commandResult.Success)
{
result.Value = transcoder.Deserialize<T>(command.Result);
result.Cas = command.CasValue;
result.Pass();
}
else
Expand All @@ -194,6 +230,27 @@ private IGetOperationResult<T> BuildGetCommandResult<T>(IGetOperationResult<T> r
return result;
}

public async Task<IGetOperationResult> GetAsync(string key)
{
if (!CreateGetCommand(key, out var result, out var node, out var command))
{
_logger.LogInformation($"Failed to CreateGetCommand for '{key}' key");
return result;
}

try
{
var commandResult = await node.ExecuteAsync(command);
return BuildGetCommandResult(result, command, commandResult);
}
catch (Exception ex)
{
_logger.LogError(0, ex, $"{nameof(GetAsync)}(\"{key}\")");
result.Fail(ex.Message, ex);
return result;
}
}

public async Task<IGetOperationResult<T>> GetAsync<T>(string key)
{
if (!CreateGetCommand<T>(key, out var result, out var node, out var command))
Expand Down
5 changes: 5 additions & 0 deletions Enyim.Caching/MemcachedClientT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ public IDictionary<string, T1> Get<T1>(IEnumerable<string> keys)
return _memcachedClient.Get<T1>(keys);
}

public Task<IGetOperationResult> GetAsync(string key)
{
return _memcachedClient.GetAsync(key);
}

public Task<IGetOperationResult<T1>> GetAsync<T1>(string key)
{
return _memcachedClient.GetAsync<T1>(key);
Expand Down
9 changes: 7 additions & 2 deletions Enyim.Caching/NullMemcachedClient.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Enyim.Caching.Memcached;
using Enyim.Caching.Memcached.Results;
Expand Down Expand Up @@ -104,6 +102,13 @@ public T Get<T>(string key)
return default(T);
}

public Task<IGetOperationResult> GetAsync(string key)
{
var result = new DefaultGetOperationResultFactory().Create();
result.Success = false;
return Task.FromResult(result);
}

public async Task<IGetOperationResult<T>> GetAsync<T>(string key)
{
var result = new DefaultGetOperationResultFactory<T>().Create();
Expand Down

0 comments on commit 2b27236

Please sign in to comment.