146 lines
5.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|