Skip to content

ObjectPool<T> #49680

@benaadams

Description

@benaadams

Background and Motivation

Follow up to #23700 (comment)

Expose the bounded ConcurrentQueueSegment<T> as an ObjectPool<T> type as using ConcurrentQueue<T> as a bounded object pool is too hard as the counting mechanism becomes separate from the queue so its at best eventually consistent; either adding too many[1] (which is then removed), or pooling too few[2] due to the race between counting and enqueuing/dequeuing; depending if the decrement is before1 .TryDequeue (when it has to be a Interlocked.CompareExchange spin) or after2 (which is a less expensive Interlocked.Decrement)

Additionally ConcurrentQueue<T> has already paid the cost for strict bounding; which then a second phase of "atomic"ish bounding has to be shimmed onto; so there is additional cost in memory and indirections from the extra in ConcurrentQueue<T> plus the inexactness of the counting; which is unsatisfactory, since all the work has already been done for the scenario its just inaccessable.

Proposed API

namespace System.Collections.Concurrent
{
    /// <summary>
    /// Provides a multi-producer, multi-consumer thread-safe bounded pool.  When the pool is full,
    /// enqueues fail and return false.  When the pool is empty, dequeues fail and return null.
    /// </summary>
    [DebuggerDisplay("Capacity = {Capacity}")]
    public sealed class ObjectPool<T>
    {
        /// <summary>Creates the pool.</summary>
        /// <param name="boundedLength">
        /// The maximum number of elements the pool can contain.  Must be a power of 2.
        /// </param>
        public ObjectPool(int boundedLength);

        /// <summary>Gets the number of elements this pool can store.</summary>
        public int Capacity;

        /// <summary>
        /// Tries to dequeue an element from the pool. If successful, the item will set
        /// from the pool and true will be returned; otherwise, the item will be null, and false
        /// will be returned.
        /// </summary>
        public bool TryRent([MaybeNullWhen(false)] out T item);

        /// <summary>
        /// Attempts to enqueue the item.  If successful, the item will be stored
        /// in the pool and true will be returned; otherwise, the item won't be stored, and false
        /// will be returned.
        /// </summary>
        public bool TryReturn(T item);
    }
}

Usage Examples

static readonly ObjectPool<MyObject> _pool = new (32);

public MyObject Rent() => _pool.TryRent(out var value) ? value : new ();

public void Return(MyObject value) 
{
    value.Reset();
    _pool.TryRetrun(value);
}

Alternative Designs

Always enqueue, but overwrite first (oldest) in queue; however that introduces extra contention...

Risks

People might want collection interfaces and enumeration; but they are fungible objects, so why?

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.CollectionswishlistIssue we would like to prioritize, but we can't commit we will get to it yet

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions