Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions BitFaster.Caching.UnitTests/SingletonCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,41 @@ namespace BitFaster.Caching.UnitTests
public class SingletonCacheTests
{
[Fact]
public void AcquireWithSameKeyUsingCustomComparerReturnsSameHandle()
public void AcquireWithSameKeyUsingCustomComparerReturnsSameLifetime()
{
var cache = new SingletonCache<string, object>(1, 3, StringComparer.OrdinalIgnoreCase);

var handle1 = cache.Acquire("foo");
var handle2 = cache.Acquire("FOO");
handle1.Value.Should().BeSameAs(handle2.Value);
handle1.Dispose();
handle2.Dispose();
var lifetime1 = cache.Acquire("foo");
var lifetime2 = cache.Acquire("FOO");
lifetime1.Value.Should().BeSameAs(lifetime2.Value);
lifetime1.Dispose();
lifetime2.Dispose();
}

[Fact]
public void AcquireWithSameKeyReturnsSameHandle()
public void AcquireWithSameKeyReturnsSameLifetime()
{
var cache = new SingletonCache<string, object>();

var handle1 = cache.Acquire("Foo");
var handle2 = cache.Acquire("Foo");
handle1.Value.Should().BeSameAs(handle2.Value);
handle1.Dispose();
handle2.Dispose();
var lifetime1 = cache.Acquire("Foo");
var lifetime2 = cache.Acquire("Foo");
lifetime1.Value.Should().BeSameAs(lifetime2.Value);
lifetime1.Dispose();
lifetime2.Dispose();
}

[Fact]
public void AcquireReleaseAcquireReturnsDifferentValue()
{
var cache = new SingletonCache<string, object>();

var handle1 = cache.Acquire("Foo");
handle1.Dispose();
var lifetime1 = cache.Acquire("Foo");
lifetime1.Dispose();

var handle2 = cache.Acquire("Foo");
handle2.Dispose();
var lifetime2 = cache.Acquire("Foo");
lifetime2.Dispose();

handle1.Value.Should().NotBeSameAs(handle2.Value);
lifetime1.Value.Should().NotBeSameAs(lifetime2.Value);
}

[Fact]
Expand All @@ -54,34 +54,34 @@ public async Task AcquireWithSameKeyOnTwoDifferentThreadsReturnsSameValue()
EventWaitHandle event1 = new EventWaitHandle(false, EventResetMode.AutoReset);
EventWaitHandle event2 = new EventWaitHandle(false, EventResetMode.AutoReset);

SingletonCache<string, object>.Handle handle1 = null;
SingletonCache<string, object>.Handle handle2 = null;
Lifetime<object> lifetime1 = null;
Lifetime<object> lifetime2 = null;

Task task1 = Task.Run(() =>
{
event1.WaitOne();
handle1 = cache.Acquire("Foo");
lifetime1 = cache.Acquire("Foo");
event2.Set();

event1.WaitOne();
handle1.Dispose();
lifetime1.Dispose();
event2.Set();
});

Task task2 = Task.Run(() =>
{
event1.Set();
event2.WaitOne();
handle2 = cache.Acquire("Foo");
lifetime2 = cache.Acquire("Foo");

event1.Set();
event2.WaitOne();
handle2.Dispose();
lifetime2.Dispose();
});

await Task.WhenAll(task1, task2);

handle1.Value.Should().BeSameAs(handle2.Value);
lifetime1.Value.Should().BeSameAs(lifetime2.Value);
}

[Fact]
Expand All @@ -99,9 +99,9 @@ public async Task AcquireWithSameKeyOnManyDifferentThreadsReturnsSameValue()
{
for (int i = 0; i < 100000; i++)
{
using (var handle = cache.Acquire("Foo"))
using (var lifetime = cache.Acquire("Foo"))
{
lock (handle.Value)
lock (lifetime.Value)
{
int result = Interlocked.Increment(ref count);
result.Should().Be(1);
Expand All @@ -120,7 +120,7 @@ public void WhenValueIsDisposableItIsDisposedWhenReleased()
{
var cache = new SingletonCache<string, DisposeTest>();

using (var handle = cache.Acquire("Foo"))
using (var lifetime = cache.Acquire("Foo"))
{
DisposeTest.WasDisposed.Should().BeFalse();
}
Expand Down
29 changes: 29 additions & 0 deletions BitFaster.Caching/Lifetime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace BitFaster.Caching
{
public class Lifetime<T> : IDisposable
{
private readonly Action onDisposeAction;
private bool isDisposed;

public Lifetime(T value, Action onDisposeAction)
{
this.Value = value;
this.onDisposeAction = onDisposeAction;
}

public T Value { get; }

public void Dispose()
{
if (!this.isDisposed)
{
this.onDisposeAction();
this.isDisposed = true;
}
}
}
}
27 changes: 2 additions & 25 deletions BitFaster.Caching/Scoped.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public Scoped(T value)
this.refCount = new ReferenceCount<T>(value);
}

public Lifetime CreateLifetime()
public Lifetime<T> CreateLifetime()
{
if (this.isDisposed)
{
Expand All @@ -38,7 +38,7 @@ public Lifetime CreateLifetime()
if (oldRefCount == Interlocked.CompareExchange(ref this.refCount, newRefCount, oldRefCount))
{
// When Lease is disposed, it calls DecrementReferenceCount
return new Lifetime(oldRefCount.Value, this.DecrementReferenceCount);
return new Lifetime<T>(oldRefCount.Value, this.DecrementReferenceCount);
}
}
}
Expand Down Expand Up @@ -70,28 +70,5 @@ public void Dispose()
this.isDisposed = true;
}
}

public class Lifetime : IDisposable
{
private readonly Action onDisposeAction;
private bool isDisposed;

public Lifetime(T value, Action onDisposeAction)
{
this.Value = value;
this.onDisposeAction = onDisposeAction;
}

public T Value { get; }

public void Dispose()
{
if (!this.isDisposed)
{
this.onDisposeAction();
this.isDisposed = true;
}
}
}
}
}
36 changes: 2 additions & 34 deletions BitFaster.Caching/SingletonCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ public SingletonCache(int concurrencyLevel, int capacity, IEqualityComparer<TKey
this.cache = new ConcurrentDictionary<TKey, ReferenceCount<TValue>>(concurrencyLevel, capacity, comparer);
}

public Handle Acquire(TKey key, Func<TKey, TValue> valueFactory)
public Lifetime<TValue> Acquire(TKey key, Func<TKey, TValue> valueFactory)
{
var refCount = this.cache.AddOrUpdate(key,
(_) => new ReferenceCount<TValue>(valueFactory(_)),
(_, existingRefCount) => existingRefCount.IncrementCopy());

return new Handle(key, refCount.Value, this);
return new Lifetime<TValue>(refCount.Value, () => this.Release(key));
}

private void Release(TKey key)
Expand All @@ -46,7 +46,6 @@ private void Release(TKey key)
{
if (newRefCount.Count == 0)
{
// This will remove from dictionary only if key and the value with ReferenceCount (== 0) matches (under a lock)
if (((IDictionary<TKey, ReferenceCount<TValue>>)this.cache).Remove(new KeyValuePair<TKey, ReferenceCount<TValue>>(key, newRefCount)))
{
if (newRefCount.Value is IDisposable d)
Expand All @@ -59,36 +58,5 @@ private void Release(TKey key)
}
}
}

public sealed class Handle : IDisposable
{
private TKey key;
private TValue value;
private SingletonCache<TKey, TValue> cache;

public Handle(TKey key, TValue value, SingletonCache<TKey, TValue> cache)
{
this.key = key;
this.value = value;
this.cache = cache;
}

public TValue Value
{
get
{
return this.value;
}
}

public void Dispose()
{
if (this.cache != null)
{
this.cache.Release(this.key);
this.cache = null;
}
}
}
}
}
2 changes: 1 addition & 1 deletion BitFaster.Caching/SingletonCacheExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace BitFaster.Caching
{
public static class SingletonCacheExtensions
{
public static SingletonCache<TKey, TValue>.Handle Acquire<TKey, TValue>(this SingletonCache<TKey, TValue> cache, TKey key)
public static Lifetime<TValue> Acquire<TKey, TValue>(this SingletonCache<TKey, TValue> cache, TKey key)
where TValue : new()
{
return cache.Acquire(key, _ => new TValue());
Expand Down