blob: fb8d2e720dedd1dfd8d899ed3b6856b81477bcb4 [file] [log] [blame]
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
using Grpc.Core.Utils;
using System;
using System.Threading;
using System.Buffers;
namespace Grpc.Core.Internal
{
internal class ReusableSliceBuffer
{
public const int MaxCachedSegments = 1024; // ~4MB payload for 4K slices
readonly SliceSegment[] cachedSegments = new SliceSegment[MaxCachedSegments];
int populatedSegmentCount = 0;
public ReadOnlySequence<byte> PopulateFrom(IBufferReader bufferReader)
{
long offset = 0;
int index = 0;
SliceSegment prevSegment = null;
while (bufferReader.TryGetNextSlice(out Slice slice))
{
// Initialize cached segment if still null or just allocate a new segment if we already reached MaxCachedSegments
var current = index < cachedSegments.Length ? cachedSegments[index] : new SliceSegment();
if (current == null)
{
current = cachedSegments[index] = new SliceSegment();
}
current.Reset(slice, offset);
prevSegment?.SetNext(current);
index ++;
offset += slice.Length;
prevSegment = current;
}
populatedSegmentCount = index;
// Not necessary for ending the ReadOnlySequence, but for making sure we
// don't keep more than MaxCachedSegments alive.
prevSegment?.SetNext(null);
if (index == 0)
{
return ReadOnlySequence<byte>.Empty;
}
var firstSegment = cachedSegments[0];
var lastSegment = prevSegment;
return new ReadOnlySequence<byte>(firstSegment, 0, lastSegment, lastSegment.Memory.Length);
}
public void Invalidate()
{
if (populatedSegmentCount == 0)
{
return;
}
var segment = cachedSegments[0];
while (segment != null)
{
segment.Reset(new Slice(IntPtr.Zero, 0), 0);
segment.SetNext(null);
segment = (SliceSegment) segment.Next;
}
populatedSegmentCount = 0;
}
// Represents a segment in ReadOnlySequence
// Segment is backed by Slice and the instances are reusable.
private class SliceSegment : ReadOnlySequenceSegment<byte>
{
readonly SliceMemoryManager pointerMemoryManager = new SliceMemoryManager();
public void Reset(Slice slice, long runningIndex)
{
pointerMemoryManager.Reset(slice);
Memory = pointerMemoryManager.Memory; // maybe not always necessary
RunningIndex = runningIndex;
}
public void SetNext(ReadOnlySequenceSegment<byte> next)
{
Next = next;
}
}
// Allow creating instances of Memory<byte> from Slice.
// Represents a chunk of native memory, but doesn't manage its lifetime.
// Instances of this class are reuseable - they can be reset to point to a different memory chunk.
// That is important to make the instances cacheable (rather then creating new instances
// the old ones will be reused to reduce GC pressure).
private class SliceMemoryManager : MemoryManager<byte>
{
private Slice slice;
public void Reset(Slice slice)
{
this.slice = slice;
}
public void Reset()
{
Reset(new Slice(IntPtr.Zero, 0));
}
public override Span<byte> GetSpan()
{
return slice.ToSpanUnsafe();
}
public override MemoryHandle Pin(int elementIndex = 0)
{
throw new NotSupportedException();
}
public override void Unpin()
{
}
protected override void Dispose(bool disposing)
{
// NOP
}
}
}
}
#endif