// 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);
}
}
}
}