Skip to content

IDisposable and Scoped values

Alex Peck edited this page Nov 25, 2023 · 8 revisions

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. 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 Lifetime A
    participant Lifetime B
    Thread A->>Lifetime A: A calls ScopedGetOrAdd, creates lifetime
    Lifetime A-->> Scope: lifetime creates scope, A holds ref
    activate Scope
    Scope-->>Object: create object
    activate Object
    Thread B->>Lifetime B: B calls ScopedGetOrAdd, creates lifetime
    Lifetime B-->> Scope: B holds ref
    Thread A->>Object: Thread A uses the object
    Lifetime A->>Thread A: A disposes lifetime
    Lifetime A--x Scope: lifetime de-refs scope
    Thread B->>Object: Thread B uses the object
    Lifetime B->>Thread B: B disposes lifetime
    Lifetime B--x Scope: B de-refs scope
    Scope--xObject: dispose object
    deactivate Object
    deactivate Scope
Loading
Clone this wiki locally