using Ryujinx.Common.Logging;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
namespace Ryujinx.Common.Pools
{
///
/// Represents a reusable pooled buffer of some type T that was rented from a .
/// The actual length of the array may be longer than the actual valid contents; to codify the difference, the Length
/// parameter is used (in cases where you pass a buffered pool to some function like a reader that parses data from it).
/// This pool should be disposed when you are done using it, after which it is returned to the pool to be reclaimed.
///
/// The type of data that this buffer holds.
public class PooledBuffer : IDisposable
{
private int _disposed = 0;
internal PooledBuffer(T[] data, int length)
{
_buffer = data;
Length = length;
}
#if TRACK_BUFFERPOOL_LEAKS
private string _lastRentalStackTrace = "Unknown";
#pragma warning disable CA1063 // suppress "improper" finalizer style
~PooledBuffer()
{
if (Length > 0)
{
Logger.Warning?.Print(LogClass.Application, $"Buffer pool leak detected: Buffer was rented by {_lastRentalStackTrace}");
Dispose(false);
}
}
#pragma warning restore CA1063
internal void MarkRented()
{
string stackTrace = new StackTrace().ToString();
string objectTypeName = nameof(BufferPool);
string traceLine;
int startIdx = 0;
while (startIdx < stackTrace.Length)
{
startIdx = stackTrace.IndexOf('\n', startIdx);
if (startIdx < 0)
{
break;
}
startIdx += 7; // trim the " at " prefix
int endIdx = stackTrace.IndexOf('\n', startIdx) - 1;
if (endIdx < 0)
{
endIdx = stackTrace.Length;
}
traceLine = stackTrace.Substring(startIdx, endIdx - startIdx);
if (!traceLine.Contains(objectTypeName))
{
_lastRentalStackTrace = traceLine;
return;
}
}
}
#endif // TRACK_BUFFERPOOL_LEAKS
///
/// The contiguous buffer to store data in.
///
private T[] _buffer;
///
/// The buffer's intended length. This may be smaller than what is actually available (since a larger pooled buffer may have been used
/// to satisfy the request). This can be used as a signal to indicate the intended data size this buffer is used for.
///
public int Length { get; private set; }
///
/// Returns a pointer to this buffer's data as a span.
///
public Span AsSpan => _buffer.AsSpan(0, Length);
///
/// Returns a pointer to this buffer's data as a read-only span.
///
public ReadOnlySpan AsReadOnlySpan => _buffer.AsSpan(0, Length);
[SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Disposal semantics are different here; we are not freeing memory but instead returning objects to a pool")]
public void Dispose()
{
Dispose(true);
#if TRACK_BUFFERPOOL_LEAKS
GC.SuppressFinalize(this);
#endif
}
protected virtual void Dispose(bool disposing)
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
if (disposing && Length > 0)
{
// if the buffer is zero-length, just keep it zero (this prevents us from corrupting the state of the empty singleton buffer)
ArrayPool.Shared.Return(_buffer);
_buffer = null;
Length = -1;
}
}
}
}