Skip to content

Commit 1fde981

Browse files
ReubenBondCopilot
andauthored
Add ArcBuffer support to Orleans serialization (#10066)
Fix serialization buffer writer edge cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cd47834 commit 1fde981

6 files changed

Lines changed: 303 additions & 25 deletions

File tree

src/Orleans.Serialization.TestKit/FieldCodecTester.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -971,27 +971,28 @@ protected T RoundTripThroughCodec<T>(T original)
971971
protected object RoundTripThroughUntypedSerializer(object original, out string formattedBitStream)
972972
{
973973
object result;
974-
using (var readerSession = SessionPool.GetSession())
975-
using (var writeSession = SessionPool.GetSession())
974+
using var readerSession = SessionPool.GetSession();
975+
using var writeSession = SessionPool.GetSession();
976+
977+
using var bufferWriter = new ArcBufferWriter();
978+
var writer = Writer.Create(bufferWriter, writeSession);
979+
try
976980
{
977-
var writer = Writer.CreatePooled(writeSession);
978-
try
979-
{
980-
var serializer = ServiceProvider.GetService<Serializer<object>>();
981-
serializer.Serialize(original, ref writer);
981+
var serializer = ServiceProvider.GetService<Serializer<object>>();
982+
serializer.Serialize(original, ref writer);
982983

983-
using var analyzerSession = SessionPool.GetSession();
984-
var output = writer.Output.Slice();
985-
formattedBitStream = BitStreamFormatter.Format(output, analyzerSession);
984+
using var analyzerSession = SessionPool.GetSession();
985+
using var output = bufferWriter.ConsumeSlice(bufferWriter.Length);
986+
var analyzerReader = Reader.Create(output, analyzerSession);
987+
formattedBitStream = BitStreamFormatter.Format(ref analyzerReader);
986988

987-
var reader = Reader.Create(output, readerSession);
989+
var reader = Reader.Create(output, readerSession);
988990

989-
result = serializer.Deserialize(ref reader);
990-
}
991-
finally
992-
{
993-
writer.Dispose();
994-
}
991+
result = serializer.Deserialize(ref reader);
992+
}
993+
finally
994+
{
995+
writer.Dispose();
995996
}
996997

997998
return result;

src/Orleans.Serialization/Buffers/Adaptors/BufferSliceReaderInput.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,63 @@ internal ReadOnlySpan<byte> GetNext()
110110
[DoesNotReturn]
111111
private static void ThrowInsufficientData() => throw new InvalidOperationException("Insufficient data present in buffer.");
112112
}
113+
114+
/// <summary>
115+
/// Input type for <see cref="Reader{TInput}"/> to support <see cref="ArcBuffer"/> buffers.
116+
/// </summary>
117+
/// <remarks>
118+
/// Initializes a new instance of the <see cref="ArcBufferReaderInput"/> type.
119+
/// </remarks>
120+
/// <param name="slice">The underlying buffer.</param>
121+
public struct ArcBufferReaderInput(in ArcBuffer slice)
122+
{
123+
private readonly ArcBuffer _slice = slice;
124+
private ArcBufferPage _page = slice.First;
125+
private int _position;
126+
127+
internal readonly ArcBufferPage First => _slice.First;
128+
internal readonly int Position => _position;
129+
internal readonly int Offset => _slice.Offset;
130+
internal readonly int Length => _slice.Length;
131+
internal long PreviousBuffersSize;
132+
133+
internal readonly ArcBufferReaderInput ForkFrom(int position)
134+
{
135+
// Rely on the outer buffer being pinned.
136+
var sliced = _slice.UnsafeSlice(position, Length - position);
137+
return new ArcBufferReaderInput(in sliced);
138+
}
139+
140+
internal ReadOnlySpan<byte> GetNext()
141+
{
142+
Debug.Assert(_position <= Length);
143+
if (_page is not null)
144+
{
145+
if (_page == First)
146+
{
147+
Debug.Assert(_position == 0);
148+
var offset = Offset;
149+
var length = Math.Min(Length, _page.Length - offset);
150+
_position += length;
151+
var result = _page.AsSpan(offset, length);
152+
_page = _page.Next;
153+
return result;
154+
}
155+
156+
if (_position != Length)
157+
{
158+
var length = Math.Min(Length - _position, _page.Length);
159+
_position += length;
160+
var result = _page.AsSpan(0, length);
161+
_page = _page.Next;
162+
return result;
163+
}
164+
}
165+
166+
ThrowInsufficientData();
167+
return default;
168+
}
169+
170+
[DoesNotReturn]
171+
private static void ThrowInsufficientData() => throw new InvalidOperationException("Insufficient data present in buffer.");
172+
}

src/Orleans.Serialization/Buffers/Reader.cs

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ public static class Reader
194194
[MethodImpl(MethodImplOptions.AggressiveInlining)]
195195
public static Reader<BufferSliceReaderInput> Create(BufferSlice input, SerializerSession session) => new(new BufferSliceReaderInput(in input), session, 0);
196196

197+
/// <summary>
198+
/// Creates a reader for the provided buffer.
199+
/// </summary>
200+
/// <param name="input">The input.</param>
201+
/// <param name="session">The session.</param>
202+
/// <returns>A new <see cref="Reader{TInput}"/>.</returns>
203+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
204+
public static Reader<ArcBufferReaderInput> Create(ArcBuffer input, SerializerSession session) => new(new ArcBufferReaderInput(in input), session, 0);
205+
197206
/// <summary>
198207
/// Creates a reader for the provided input stream.
199208
/// </summary>
@@ -267,6 +276,7 @@ public ref struct Reader<TInput>
267276
private readonly static bool IsReadOnlySequenceInput = typeof(TInput) == typeof(ReadOnlySequenceInput);
268277
private readonly static bool IsReaderInput = typeof(ReaderInput).IsAssignableFrom(typeof(TInput));
269278
private readonly static bool IsBufferSliceInput = typeof(TInput) == typeof(BufferSliceReaderInput);
279+
private readonly static bool IsArcBufferInput = typeof(TInput) == typeof(ArcBufferReaderInput);
270280

271281
private ReadOnlySpan<byte> _currentSpan;
272282
private int _bufferPos;
@@ -297,6 +307,15 @@ internal Reader(TInput input, SerializerSession session, long globalOffset)
297307
_bufferSize = _currentSpan.Length;
298308
_sequenceOffset = globalOffset;
299309
}
310+
else if (IsArcBufferInput)
311+
{
312+
_input = input;
313+
ref var slice = ref Unsafe.As<TInput, ArcBufferReaderInput>(ref _input);
314+
_currentSpan = slice.GetNext();
315+
_bufferPos = 0;
316+
_bufferSize = _currentSpan.Length;
317+
_sequenceOffset = globalOffset;
318+
}
300319
else if (IsReaderInput)
301320
{
302321
_input = input;
@@ -357,6 +376,11 @@ public long Position
357376
var previousBuffersSize = Unsafe.As<TInput, BufferSliceReaderInput>(ref _input).PreviousBuffersSize;
358377
return _sequenceOffset + previousBuffersSize + _bufferPos;
359378
}
379+
else if (IsArcBufferInput)
380+
{
381+
var previousBuffersSize = Unsafe.As<TInput, ArcBufferReaderInput>(ref _input).PreviousBuffersSize;
382+
return _sequenceOffset + previousBuffersSize + _bufferPos;
383+
}
360384
else if (IsSpanInput)
361385
{
362386
return _sequenceOffset + _bufferPos;
@@ -388,6 +412,10 @@ public long Length
388412
{
389413
return Unsafe.As<TInput, BufferSliceReaderInput>(ref _input).Length;
390414
}
415+
else if (IsArcBufferInput)
416+
{
417+
return Unsafe.As<TInput, ArcBufferReaderInput>(ref _input).Length;
418+
}
391419
else if (IsSpanInput)
392420
{
393421
return _currentSpan.Length;
@@ -441,6 +469,22 @@ public void Skip(long count)
441469
}
442470
}
443471
}
472+
else if (IsArcBufferInput)
473+
{
474+
var end = Position + count;
475+
while (Position < end)
476+
{
477+
var previousBuffersSize = Unsafe.As<TInput, ArcBufferReaderInput>(ref _input).PreviousBuffersSize;
478+
if (end - previousBuffersSize <= _bufferSize)
479+
{
480+
_bufferPos = (int)(end - previousBuffersSize);
481+
}
482+
else
483+
{
484+
MoveNext();
485+
}
486+
}
487+
}
444488
else if (IsSpanInput)
445489
{
446490
_bufferPos += (int)count;
@@ -496,6 +540,17 @@ public void ForkFrom(long position, out Reader<TInput> forked)
496540
ThrowInvalidPosition(position, forked.Position);
497541
}
498542
}
543+
else if (IsArcBufferInput)
544+
{
545+
ref var input = ref Unsafe.As<TInput, ArcBufferReaderInput>(ref _input);
546+
var newInput = input.ForkFrom(checked((int)position));
547+
forked = new Reader<TInput>(Unsafe.As<ArcBufferReaderInput, TInput>(ref newInput), Session, position);
548+
549+
if (forked.Position != position)
550+
{
551+
ThrowInvalidPosition(position, forked.Position);
552+
}
553+
}
499554
else if (IsSpanInput)
500555
{
501556
forked = new Reader<TInput>(_currentSpan[(int)position..], Session, position);
@@ -541,6 +596,10 @@ public void ResumeFrom(long position)
541596
{
542597
// Nothing is required.
543598
}
599+
else if (IsArcBufferInput)
600+
{
601+
// Nothing is required.
602+
}
544603
else if (IsSpanInput)
545604
{
546605
// Nothing is required.
@@ -598,6 +657,14 @@ private void MoveNext()
598657
_bufferPos = 0;
599658
_bufferSize = _currentSpan.Length;
600659
}
660+
else if (IsArcBufferInput)
661+
{
662+
ref var slice = ref Unsafe.As<TInput, ArcBufferReaderInput>(ref _input);
663+
slice.PreviousBuffersSize += _bufferSize;
664+
_currentSpan = slice.GetNext();
665+
_bufferPos = 0;
666+
_bufferSize = _currentSpan.Length;
667+
}
601668
else if (IsSpanInput)
602669
{
603670
ThrowInsufficientData();
@@ -615,7 +682,7 @@ private void MoveNext()
615682
[MethodImpl(MethodImplOptions.AggressiveInlining)]
616683
public byte ReadByte()
617684
{
618-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
685+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
619686
{
620687
var pos = _bufferPos;
621688
if ((uint)pos < (uint)_currentSpan.Length)
@@ -657,7 +724,7 @@ private static byte ReadByteSlow(ref Reader<TInput> reader)
657724
/// <returns>The <see cref="uint"/> which was read.</returns>
658725
public uint ReadUInt32()
659726
{
660-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
727+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
661728
{
662729
const int width = 4;
663730
if (_bufferPos + width > _bufferSize)
@@ -701,7 +768,7 @@ static uint ReadSlower(ref Reader<TInput> r)
701768
/// <returns>The <see cref="ulong"/> which was read.</returns>
702769
public ulong ReadUInt64()
703770
{
704-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
771+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
705772
{
706773
const int width = 8;
707774
if (_bufferPos + width > _bufferSize)
@@ -779,7 +846,7 @@ public byte[] ReadBytes(uint count)
779846
}
780847

781848
var bytes = new byte[count];
782-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
849+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
783850
{
784851
var destination = new Span<byte>(bytes);
785852
ReadBytes(destination);
@@ -798,7 +865,7 @@ public byte[] ReadBytes(uint count)
798865
/// <param name="destination">The destination.</param>
799866
public void ReadBytes(scoped Span<byte> destination)
800867
{
801-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
868+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
802869
{
803870
if (_bufferPos + destination.Length <= _bufferSize)
804871
{
@@ -846,7 +913,7 @@ private void ReadBytesMultiSegment(scoped Span<byte> dest)
846913
[MethodImpl(MethodImplOptions.AggressiveInlining)]
847914
public bool TryReadBytes(int length, out ReadOnlySpan<byte> bytes)
848915
{
849-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
916+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
850917
{
851918
if (_bufferPos + length <= _bufferSize)
852919
{
@@ -879,7 +946,7 @@ public bool TryReadBytes(int length, out ReadOnlySpan<byte> bytes)
879946
[MethodImpl(MethodImplOptions.AggressiveInlining)]
880947
public unsafe uint ReadVarUInt32()
881948
{
882-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
949+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
883950
{
884951
var pos = _bufferPos;
885952

@@ -937,7 +1004,7 @@ private uint ReadVarUInt32Slow()
9371004
[MethodImpl(MethodImplOptions.AggressiveInlining)]
9381005
public ulong ReadVarUInt64()
9391006
{
940-
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput)
1007+
if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput || IsArcBufferInput)
9411008
{
9421009
var pos = _bufferPos;
9431010

src/Orleans.Serialization/Buffers/Writer.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ public static class Writer
3030
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3131
public static Writer<TBufferWriter> Create<TBufferWriter>(TBufferWriter destination, SerializerSession session) where TBufferWriter : IBufferWriter<byte> => new(destination, session);
3232

33+
/// <summary>
34+
/// Creates a writer which writes to the specified destination.
35+
/// </summary>
36+
/// <param name="destination">The destination.</param>
37+
/// <param name="session">The session.</param>
38+
/// <returns>A new <see cref="Writer{TBufferWriter}"/>.</returns>
39+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
40+
public static Writer<ArcBufferWriterWrapper> Create(ArcBufferWriter destination, SerializerSession session) => new(new(destination), session);
41+
3342
/// <summary>
3443
/// Creates a writer which writes to the specified destination.
3544
/// </summary>
@@ -95,6 +104,25 @@ public static class Writer
95104
public static Writer<PooledBuffer> CreatePooled(SerializerSession session) => new(new PooledBuffer(), session);
96105
}
97106

107+
/// <summary>
108+
/// Wraps an <see cref="ArcBufferWriter"/> for use as a serialization writer target.
109+
/// </summary>
110+
/// <remarks>
111+
/// Disposing a <see cref="Writer{TBufferWriter}"/> over this wrapper does not dispose the underlying <see cref="ArcBufferWriter"/>.
112+
/// </remarks>
113+
/// <param name="bufferWriter">The wrapped buffer writer.</param>
114+
public readonly struct ArcBufferWriterWrapper(ArcBufferWriter bufferWriter) : IBufferWriter<byte>
115+
{
116+
/// <inheritdoc/>
117+
public void Advance(int count) => ((IBufferWriter<byte>)bufferWriter).Advance(count);
118+
119+
/// <inheritdoc/>
120+
public Memory<byte> GetMemory(int sizeHint = 0) => bufferWriter.GetMemory(sizeHint);
121+
122+
/// <inheritdoc/>
123+
public Span<byte> GetSpan(int sizeHint = 0) => bufferWriter.GetSpan(sizeHint);
124+
}
125+
98126
/// <summary>
99127
/// Provides functionality for writing to an output stream.
100128
/// </summary>

0 commit comments

Comments
 (0)