diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 209cf47..dabbfd5 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Frozen; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO.Pipelines; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; @@ -375,57 +374,6 @@ public static partial class AcBinaryDeserializer } } - /// - /// Drains a end-to-end into a fresh - /// and deserializes one message. Background Task.Run deserializes incrementally while - /// the calling thread drains the reader. For long-lived multi-message scenarios use the - /// overloads directly. - /// - public static async Task DeserializeFromPipeReaderAsync<[DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] T>(PipeReader reader, AcBinarySerializerOptions options, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - using var input = new AsyncPipeReaderInput(options.BufferWriterChunkSize * 2); - var deserTask = Task.Run(() => Deserialize(input, options), cancellationToken); - await DrainPipeReaderToInputAsync(reader, input, cancellationToken).ConfigureAwait(false); - return await deserTask.ConfigureAwait(false); - } - - /// - /// Non-generic Type-based counterpart to . - /// For runtime-typed scenarios (MVC formatters, plugin frameworks). - /// - public static async Task DeserializeFromPipeReaderAsync(PipeReader reader, [DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type targetType, AcBinarySerializerOptions options, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - using var input = new AsyncPipeReaderInput(options.BufferWriterChunkSize * 2); - var deserTask = Task.Run(() => Deserialize(input, targetType, options), cancellationToken); - await DrainPipeReaderToInputAsync(reader, input, cancellationToken).ConfigureAwait(false); - return await deserTask.ConfigureAwait(false); - } - - /// - /// Pumps a into a via repeated - /// calls; signals - /// at end-of-stream (in finally so consumer always wakes up on cancellation / exception). - /// - private static async Task DrainPipeReaderToInputAsync(PipeReader reader, AsyncPipeReaderInput input, CancellationToken cancellationToken) - { - 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(); - } - } - /// /// Internal: Deserialize with any TInput (multi-segment or other future input types). /// diff --git a/AyCode.Core/docs/BINARY/BINARY_TODO.md b/AyCode.Core/docs/BINARY/BINARY_TODO.md index c9e9a0a..d9b71fb 100644 --- a/AyCode.Core/docs/BINARY/BINARY_TODO.md +++ b/AyCode.Core/docs/BINARY/BINARY_TODO.md @@ -371,13 +371,11 @@ Added in `AcBinarySerializer.cs`: - `SerializeChunked(object?, Type, PipeWriter, opts)` → `int` - `SerializeChunkedFramed(object?, Type, PipeWriter, opts)` → `int` -Added in `AcBinaryDeserializer.cs`: -- `DeserializeFromPipeReaderAsync(PipeReader, opts, ct)` → `Task` -- `DeserializeFromPipeReaderAsync(PipeReader, Type, opts, ct)` → `Task` +`AcBinaryDeserializer.cs` already had `Deserialize(byte[], Type, opts)` / `Deserialize(ReadOnlySequence, Type, opts)` / `Deserialize(AsyncPipeReaderInput, Type, opts)` overloads — no new entries needed. -The `Deserialize(byte[], Type, opts)` / `Deserialize(ReadOnlySequence, Type, opts)` / `Deserialize(AsyncPipeReaderInput, Type, opts)` overloads already existed. +**Layering note**: `PipeReader → AsyncPipeReaderInput` drain-loop is the consumer's responsibility, not the binary serializer's. The serializer surface ends at `AsyncPipeReaderInput`; transport-specific draining (PipeReader, NamedPipe, SignalR `state.Buffer.Write`, etc.) lives in the consumer layer (e.g. `AcBinaryInputFormatter`, `AcBinaryHubProtocol.TryParseChunkData`). -Consumed by ASP.NET Core MVC formatter package (`AyCode.Services/Mvc/`) — `AcBinaryInputFormatter`, `AcBinaryOutputFormatter`, `AddAcBinaryFormatters` extension. Media type: `application/vnd.acbinary`. +Consumed by ASP.NET Core MVC formatter package (`AyCode.Services/Mvc/`) — `AcBinaryInputFormatter`, `AcBinaryOutputFormatter`, `AddAcBinaryFormatters` extension. Media type: `application/vnd.acbinary`. Drain-loop inlined in `AcBinaryInputFormatter.ReadRequestBodyAsync`. Plugin frameworks, ASP.NET ModelBinding, DI middleware, and DataContractSerializer-style "generic-API container" use-cases need to serialize an `object` whose type is known only at runtime. Current AcBinary surface forces a reflection trampoline through the generic `Serialize`: diff --git a/AyCode.Services/Mvc/AcBinaryInputFormatter.cs b/AyCode.Services/Mvc/AcBinaryInputFormatter.cs index c068bda..1daf837 100644 --- a/AyCode.Services/Mvc/AcBinaryInputFormatter.cs +++ b/AyCode.Services/Mvc/AcBinaryInputFormatter.cs @@ -7,8 +7,9 @@ namespace AyCode.Services.Mvc; /// /// ASP.NET Core MVC InputFormatter for AcBinary wire format. Reads request body via PipeReader, -/// drains into AsyncPipeReaderInput, deserializes to ModelType. Standard ProblemDetails error -/// flow on failure (ModelState.AddModelError → 400 + application/problem+json). +/// drains chunks into AsyncPipeReaderInput on the calling thread while a background task +/// deserializes incrementally, then returns the result. Standard ProblemDetails error flow on +/// failure (ModelState.AddModelError → 400 + application/problem+json). /// public class AcBinaryInputFormatter : InputFormatter { @@ -31,9 +32,28 @@ public class AcBinaryInputFormatter : InputFormatter var ct = context.HttpContext.RequestAborted; var reader = PipeReader.Create(context.HttpContext.Request.Body); + try { - var model = await AcBinaryDeserializer.DeserializeFromPipeReaderAsync(reader, context.ModelType, _options, ct).ConfigureAwait(false); + using var input = new AsyncPipeReaderInput(_options.BufferWriterChunkSize * 2); + var deserTask = Task.Run(() => AcBinaryDeserializer.Deserialize(input, context.ModelType, _options), ct); + + try + { + while (true) + { + var result = await reader.ReadAsync(ct).ConfigureAwait(false); + foreach (var segment in result.Buffer) input.Feed(segment.Span); + reader.AdvanceTo(result.Buffer.End); + if (result.IsCompleted) break; + } + } + finally + { + input.Complete(); + } + + var model = await deserTask.ConfigureAwait(false); return await InputFormatterResult.SuccessAsync(model).ConfigureAwait(false); } catch (OperationCanceledException) when (ct.IsCancellationRequested)