Skip to content

Commit

Permalink
Fix async/sync interopability bug (#13)
Browse files Browse the repository at this point in the history
Fixes #12
  • Loading branch information
alastairtree authored Dec 23, 2016
1 parent 8b5e965 commit e9b3b52
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace LazyCache.UnitTests
{
[TestFixture]
public class ServiceCacheTests
public class CachingServiceTests
{
[TearDown]
public void TearDown()
Expand Down Expand Up @@ -220,6 +220,43 @@ public void GetNullKeyThrowsException()
act.ShouldThrow<ArgumentNullException>();
}

[Test]
public async Task GetOrAddFollowinGetOrAddAsyncReturnsTheFirstObjectAndUnwrapsTheFirstTask()
{
Func<Task<ComplexTestObject>> fetchAsync = () => Task.FromResult(testObject);
Func<ComplexTestObject> fetchSync = () => new ComplexTestObject();

var actualAsync = await sut.GetOrAddAsync(TestKey, fetchAsync);
var actualSync = sut.GetOrAdd(TestKey, fetchSync);

Assert.IsNotNull(actualAsync);
Assert.That(actualAsync, Is.EqualTo(testObject));

Assert.IsNotNull(actualSync);
Assert.That(actualSync, Is.EqualTo(testObject));

Assert.AreEqual(actualAsync, actualSync);
}

[Test]
public async Task GetOrAddAsyncFollowinGetOrAddReturnsTheFirstObjectAndIgnoresTheSecondTask()
{
Func<Task<ComplexTestObject>> fetchAsync = () => Task.FromResult(new ComplexTestObject());
Func<ComplexTestObject> fetchSync = () => testObject;

var actualSync = sut.GetOrAdd(TestKey, fetchSync);
var actualAsync = await sut.GetOrAddAsync(TestKey, fetchAsync);


Assert.IsNotNull(actualSync);
Assert.That(actualSync, Is.EqualTo(testObject));

Assert.IsNotNull(actualAsync);
Assert.That(actualAsync, Is.EqualTo(testObject));

Assert.AreEqual(actualAsync, actualSync);
}

[Test]
public void GetOrAddAndThenGetObjectReturnsCorrectType()
{
Expand Down
2 changes: 1 addition & 1 deletion LazyCache.UnitTests/LazyCache.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<ItemGroup>
<Compile Include="AsyncHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceCacheTests.cs" />
<Compile Include="CachingServiceTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LazyCache\LazyCache.csproj">
Expand Down
100 changes: 49 additions & 51 deletions LazyCache/CachingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public CachingService(ObjectCache cache)
}

/// <summary>
/// Seconds to cache objects for by default
/// Seconds to cache objects for by default
/// </summary>
public int DefaultCacheDuration { get; set; }

Expand Down Expand Up @@ -59,18 +59,6 @@ public T Get<T>(string key)
return UnwrapLazy<T>(item);
}

private static T UnwrapLazy<T>(object item)
{
var lazy = item as Lazy<T>;
if (lazy != null)
return lazy.Value;

if (item is T)
return (T) item;

return default(T);
}


public async Task<T> GetAsync<T>(string key)
{
Expand All @@ -81,26 +69,6 @@ public async Task<T> GetAsync<T>(string key)
return await UnwrapAsyncLazys<T>(item);
}

private static async Task<T> UnwrapAsyncLazys<T>(object item)
{
var asyncLazy = item as AsyncLazy<T>;
if (asyncLazy != null)
return await asyncLazy.Value;

var task = item as Task<T>;
if (task != null)
return await task;

var lazy = item as Lazy<T>;
if (lazy != null)
return lazy.Value;

if (item is T)
return (T) item;

return default(T);
}


public T GetOrAdd<T>(string key, Func<T> addItemFactory)
{
Expand All @@ -119,9 +87,7 @@ public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> addItemFactory,
var existingCacheItem = ObjectCache.AddOrGetExisting(key, newLazyCacheItem, policy);

if (existingCacheItem != null)
{
return await UnwrapAsyncLazys<T>(existingCacheItem);
}

try
{
Expand Down Expand Up @@ -161,9 +127,7 @@ public T GetOrAdd<T>(string key, Func<T> addItemFactory, CacheItemPolicy policy)
var existingCacheItem = ObjectCache.AddOrGetExisting(key, newLazyCacheItem, policy);

if (existingCacheItem != null)
{
return UnwrapLazy<T>(existingCacheItem);
}

try
{
Expand Down Expand Up @@ -200,39 +164,73 @@ public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> addItemFactory,
return await GetOrAddAsync(key, addItemFactory, new CacheItemPolicy {SlidingExpiration = slidingExpiration});
}

private static T UnwrapLazy<T>(object item)
{
var lazy = item as Lazy<T>;
if (lazy != null)
return lazy.Value;

if (item is T)
return (T) item;

var asyncLazy = item as AsyncLazy<T>;
if (asyncLazy != null)
return asyncLazy.Value.Result;

var task = item as Task<T>;
if (task != null)
return task.Result;

return default(T);
}

private static async Task<T> UnwrapAsyncLazys<T>(object item)
{
var asyncLazy = item as AsyncLazy<T>;
if (asyncLazy != null)
return await asyncLazy.Value;

var task = item as Task<T>;
if (task != null)
return await task;

var lazy = item as Lazy<T>;
if (lazy != null)
return lazy.Value;

if (item is T)
return (T) item;

return default(T);
}

private static void EnsureRemovedCallbackDoesNotReturnTheLazy<T>(CacheItemPolicy policy)
{
if ((policy != null) && (policy.RemovedCallback != null))
if (policy?.RemovedCallback != null)
{
var originallCallback = policy.RemovedCallback;
policy.RemovedCallback = args =>
{
//unwrap the cache item in a callback given one is specified
if ((args != null) && (args.CacheItem != null))
{
var item = args.CacheItem.Value as Lazy<T>;
if (item != null)
args.CacheItem.Value = item.IsValueCreated ? item.Value : default(T);
}
var item = args?.CacheItem?.Value as Lazy<T>;
if (item != null)
args.CacheItem.Value = item.IsValueCreated ? item.Value : default(T);
originallCallback(args);
};
}
}

private static void EnsureRemovedCallbackDoesNotReturnTheAsyncLazy<T>(CacheItemPolicy policy)
{
if ((policy != null) && (policy.RemovedCallback != null))
if (policy?.RemovedCallback != null)
{
var originallCallback = policy.RemovedCallback;
policy.RemovedCallback = args =>
{
//unwrap the cache item in a callback given one is specified
if ((args != null) && (args.CacheItem != null))
{
var item = args.CacheItem.Value as AsyncLazy<T>;
if (item != null)
args.CacheItem.Value = item.IsValueCreated ? item.Value : Task.FromResult(default(T));
}
var item = args?.CacheItem?.Value as AsyncLazy<T>;
if (item != null)
args.CacheItem.Value = item.IsValueCreated ? item.Value : Task.FromResult(default(T));
originallCallback(args);
};
}
Expand Down

0 comments on commit e9b3b52

Please sign in to comment.