// Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Buffers; using System.Collections.Generic; using Nerdbank.Streams; namespace MessagePack { /// /// A thread-safe, alloc-free reusable object pool. /// #if MESSAGEPACK_INTERNAL internal #else public #endif class SequencePool { /// /// A thread-safe pool of reusable objects. /// internal static readonly SequencePool Shared = new SequencePool(); /// /// The value to use for . /// /// /// Individual users that want a different value for this can modify the setting on the rented /// or by supplying their own . /// /// /// We use 32KB so that when LZ4Codec.MaximumOutputLength is used on this length it does not require a /// buffer that would require the Large Object Heap. /// private const int MinimumSpanLength = 32 * 1024; private readonly int maxSize; private readonly Stack> pool = new Stack>(); /// /// The array pool which we share with all objects created by this instance. /// private readonly ArrayPool arrayPool; /// /// Initializes a new instance of the class. /// /// /// We use a that allows every processor to be involved in messagepack serialization concurrently, /// plus one nested serialization per processor (since LZ4 and sometimes other nested serializations may exist). /// public SequencePool() : this(Environment.ProcessorCount * 2, ArrayPool.Create(80 * 1024, 100)) { } /// /// Initializes a new instance of the class. /// /// The maximum size to allow the pool to grow. /// /// We allow 100 arrays to be shared (instead of the default 50) and reduce the max array length from the default 1MB to something more reasonable for our expected use. /// public SequencePool(int maxSize) : this(maxSize, ArrayPool.Create(80 * 1024, 100)) { } /// /// Initializes a new instance of the class. /// /// The maximum size to allow the pool to grow. /// Array pool that will be used. public SequencePool(int maxSize, ArrayPool arrayPool) { this.maxSize = maxSize; this.arrayPool = arrayPool; } /// /// Gets an instance of /// This is taken from the recycled pool if one is available; otherwise a new one is created. /// /// The rental tracker that provides access to the object as well as a means to return it. internal Rental Rent() { lock (this.pool) { if (this.pool.Count > 0) { return new Rental(this, this.pool.Pop()); } } // Configure the newly created object to share a common array pool with the other instances, // otherwise each one will have its own ArrayPool which would likely waste a lot of memory. return new Rental(this, new Sequence(this.arrayPool) { MinimumSpanLength = MinimumSpanLength }); } private void Return(Sequence value) { value.Reset(); lock (this.pool) { if (this.pool.Count < this.maxSize) { // Reset to preferred settings in case the renter changed them. value.MinimumSpanLength = MinimumSpanLength; this.pool.Push(value); } } } internal struct Rental : IDisposable { private readonly SequencePool owner; internal Rental(SequencePool owner, Sequence value) { this.owner = owner; this.Value = value; } /// /// Gets the recyclable object. /// public Sequence Value { get; } /// /// Returns the recyclable object to the pool. /// /// /// The instance is cleaned first, if a clean delegate was provided. /// It is dropped instead of being returned to the pool if the pool is already at its maximum size. /// public void Dispose() { this.owner?.Return(this.Value); } } } }