ZK_Framework/Assets/Plugins/MessagePack/SequencePool.cs

146 lines
5.4 KiB
C#

// 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
{
/// <summary>
/// A thread-safe, alloc-free reusable object pool.
/// </summary>
#if MESSAGEPACK_INTERNAL
internal
#else
public
#endif
class SequencePool
{
/// <summary>
/// A thread-safe pool of reusable <see cref="Sequence{T}"/> objects.
/// </summary>
internal static readonly SequencePool Shared = new SequencePool();
/// <summary>
/// The value to use for <see cref="Sequence{T}.MinimumSpanLength"/>.
/// </summary>
/// <remarks>
/// Individual users that want a different value for this can modify the setting on the rented <see cref="Sequence{T}"/>
/// or by supplying their own <see cref="IBufferWriter{T}" />.
/// </remarks>
/// <devremarks>
/// 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.
/// </devremarks>
private const int MinimumSpanLength = 32 * 1024;
private readonly int maxSize;
private readonly Stack<Sequence<byte>> pool = new Stack<Sequence<byte>>();
/// <summary>
/// The array pool which we share with all <see cref="Sequence{T}"/> objects created by this <see cref="SequencePool"/> instance.
/// </summary>
private readonly ArrayPool<byte> arrayPool;
/// <summary>
/// Initializes a new instance of the <see cref="SequencePool"/> class.
/// </summary>
/// <remarks>
/// We use a <see cref="maxSize"/> 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).
/// </remarks>
public SequencePool()
: this(Environment.ProcessorCount * 2, ArrayPool<byte>.Create(80 * 1024, 100))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SequencePool"/> class.
/// </summary>
/// <param name="maxSize">The maximum size to allow the pool to grow.</param>
/// <devremarks>
/// 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.
/// </devremarks>
public SequencePool(int maxSize)
: this(maxSize, ArrayPool<byte>.Create(80 * 1024, 100))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SequencePool"/> class.
/// </summary>
/// <param name="maxSize">The maximum size to allow the pool to grow.</param>
/// <param name="arrayPool">Array pool that will be used.</param>
public SequencePool(int maxSize, ArrayPool<byte> arrayPool)
{
this.maxSize = maxSize;
this.arrayPool = arrayPool;
}
/// <summary>
/// Gets an instance of <see cref="Sequence{T}"/>
/// This is taken from the recycled pool if one is available; otherwise a new one is created.
/// </summary>
/// <returns>The rental tracker that provides access to the object as well as a means to return it.</returns>
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<byte>(this.arrayPool) { MinimumSpanLength = MinimumSpanLength });
}
private void Return(Sequence<byte> 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<byte> value)
{
this.owner = owner;
this.Value = value;
}
/// <summary>
/// Gets the recyclable object.
/// </summary>
public Sequence<byte> Value { get; }
/// <summary>
/// Returns the recyclable object to the pool.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public void Dispose()
{
this.owner?.Return(this.Value);
}
}
}
}