AyCode.Core/AyCode.Core.Tests/Serialization/AsyncPipeReaderInputExtensi...

64 lines
2.9 KiB
C#

using AyCode.Core.Serializers.Binaries;
using System;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
namespace AyCode.Core.Tests.Serialization;
/// <summary>
/// Test/benchmark-only extension methods for populating <see cref="AsyncPipeReaderInput"/>
/// from <see cref="System.IO.Pipelines.PipeReader"/>-backed transports (NamedPipe, FileStream,
/// custom pipe sources).
///
/// <para><b>Why test-only:</b> in real production, the consuming application already has its own
/// reader-task that reads from the pipe and pushes bytes via <c>AsyncPipeReaderInput.Feed</c>
/// — providing this drain extension publicly would duplicate that responsibility and confuse
/// the canonical push-pattern. The extension is kept here for unit-test scaffolding and the
/// streaming benchmark; production NuGet consumers should write their own drain logic in their
/// own reader-task following the application's threading model.</para>
/// </summary>
public static class AsyncPipeReaderInputExtensions
{
/// <summary>
/// Drains a <see cref="PipeReader"/> end-to-end into the <see cref="AsyncPipeReaderInput"/>:
/// calls <see cref="AsyncPipeReaderInput.Feed"/> on each segment and
/// <see cref="AsyncPipeReaderInput.Complete"/> when the pipe completes.
///
/// <para>Typical usage (test-only): NamedPipe IPC and FileStream-via-PipeReader transports
/// schedule this on a background task while the deserialization context reads from the same
/// input on another thread.</para>
///
/// <para><see cref="AsyncPipeReaderInput.Complete"/> is invoked in a <c>finally</c> block —
/// ensures the consumer always wakes up even if the pipe read throws or the operation is
/// cancelled. Exceptions (including <see cref="OperationCanceledException"/>) propagate to
/// the caller after <c>Complete</c> runs.</para>
/// </summary>
/// <param name="input">The receive-side input to feed.</param>
/// <param name="reader">The pipe reader to drain.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <exception cref="ArgumentNullException">If <paramref name="input"/> or <paramref name="reader"/> is <c>null</c>.</exception>
public static async Task DrainFromAsync(this AsyncPipeReaderInput input, PipeReader reader, CancellationToken cancellationToken = default)
{
if (input is null) throw new ArgumentNullException(nameof(input));
if (reader is null) throw new ArgumentNullException(nameof(reader));
try
{
while (true)
{
var result = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
foreach (var segment in result.Buffer) input.Feed(segment.Span);
reader.AdvanceTo(result.Buffer.End);
if (result.IsCompleted) break;
}
}
finally
{
input.Complete();
}
}
}