-
Notifications
You must be signed in to change notification settings - Fork 31
IDisposable and Scoped values
Object pooling is a popular way to reduce memory allocations, using IDisposable
wrappers to return objects to the pool. All cache classes in BitFaster.Caching own the lifetime of cached values, and will automatically dispose values when they are evicted. To safely store IDisposable
pooled objects within the cache, use IScopedCache
.
To avoid races using objects after they have been disposed by the cache, use IScopedCache
which wraps values in Scoped<T>
. The call to ScopedGetOrAdd
creates a Lifetime
that guarantees the scoped object will not be disposed until the lifetime is disposed. Scoped cache is thread safe, and guarantees correct disposal for concurrent lifetimes.
var lru = new ConcurrentLruBuilder<int, SomeDisposable>()
.WithCapacity(128)
.AsScopedCache()
.Build();
var valueFactory = new SomeDisposableValueFactory();
using (var lifetime = lru.ScopedGetOrAdd(1, valueFactory.Create))
{
// lifetime.Value is guaranteed to be alive until the lifetime is disposed
}
class SomeDisposableValueFactory
{
public Scoped<SomeDisposable>> Create(int key)
{
return new Scoped<SomeDisposable>(new SomeDisposable(key));
}
}
In the following sequence of operations, Threads A and B both lookup an IDisposable object from the cache and hold a lifetime. Thread C then deletes the object from the cache while it is in use. Each thread's lifetime instance ensures that the object is alive until all threads have disposed their lifetime.
sequenceDiagram
autonumber
participant Thread A
participant Thread B
participant Thread C
participant Cache
participant Scope
participant Lifetime Cache
participant Lifetime A
participant Lifetime B
Thread A->>Cache: A calls ScopedGetOrAdd
Cache->>Scope: create scope
activate Scope
Scope-->>Object: create object
activate Object
Cache-->Lifetime Cache: cache holds lifetime
activate Lifetime Cache
Cache-->Lifetime A: creates A's lifetime
activate Lifetime A
Lifetime A-->> Thread A: A holds lifetime
Thread B->>Cache: B calls ScopedGetOrAdd
Cache-->Lifetime B: creates B's lifetime
activate Lifetime B
Lifetime B-->> Thread B: B holds lifetime
Thread C->>Cache: C calls TryRemove
Cache--xLifetime Cache: Cache removes the object and disposes lifetime
deactivate Lifetime Cache
Thread A->>Object: Thread A uses the object
Lifetime A->>Thread A: A disposes lifetime
Lifetime A--x Scope: lifetime de-refs scope
deactivate Lifetime A
Thread B->>Object: Thread B uses the object
Lifetime B->>Thread B: B disposes lifetime
Lifetime B--x Scope: B de-refs scope
deactivate Lifetime B
Scope--xObject: dispose object
deactivate Object
deactivate Scope