From e2b96b4148f498681016d8a9821c5dab3445921f Mon Sep 17 00:00:00 2001 From: Loretta Date: Fri, 22 May 2026 23:37:44 +0200 Subject: [PATCH] Switch to net9.0; improve AcBinary diagnostics & chunk fallback - Change target framework to net9.0 in AyCode.Core.targets. - Add DEBUG-only property access diagnostics to AcBinarySerializer for better error reporting. - Update AcBinaryHubProtocol to dispose chunk state and retry normal parse on unknown bytes, improving resilience after serialization failures. - Update comments to clarify new logic and rationale. --- AyCode.Core.targets | 2 +- .../Binaries/AcBinarySerializer.cs | 26 +++++++++++++++++++ .../SignalRs/AcBinaryHubProtocol.cs | 16 +++++++----- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/AyCode.Core.targets b/AyCode.Core.targets index 4e17e0e..1e470dd 100644 --- a/AyCode.Core.targets +++ b/AyCode.Core.targets @@ -7,7 +7,7 @@ - net10.0 + net9.0 enable enable diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 109cb04..189b11f 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -1965,7 +1966,12 @@ public static partial class AcBinarySerializer // Use pre-cached wrapper, fallback to GetWrapper on miss/polymorphism // Set interning eligibility for string collection elements context.StringInternEligible = prop.IsStringCollectionProperty && prop.UseStringPropertyInterning(context.InternBit); + + #if DEBUG + var value = GetPropertyValueWithDiagnostics(obj, prop); + #else var value = prop.GetValue(obj); + #endif // SKIP marker only for null (reference types) // Empty string, empty collections, etc. are valid values and must be written! @@ -2004,6 +2010,26 @@ public static partial class AcBinarySerializer } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static object? GetPropertyValueWithDiagnostics(object source, BinaryPropertyAccessor property) + { + try + { + return property.GetValue(source); + } + catch (Exception ex) when (ex is NullReferenceException || ex is TargetInvocationException) + { + var sourceType = source.GetType().FullName ?? source.GetType().Name; + var propertyName = property.Name; + var declaredType = property.PropertyType?.FullName ?? ""; + + throw new InvalidOperationException( + $"AcBinary runtime property write failed at {sourceType}.{propertyName} (declaredType={declaredType}, accessor={property.AccessorType}). " + + "This usually indicates a null-sensitive property getter or malformed source data.", + ex); + } + } + #endregion #region Specialized Array Writers diff --git a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs index c6952d0..e0d5beb 100644 --- a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs +++ b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs @@ -964,11 +964,12 @@ public class AcBinaryHubProtocol : IHubProtocol return true; } - // Unknown byte in chunk mode — break out (shouldn't happen). - // Note: AsyncPipeReaderInput's WritePos/ReadPos are private, so the previous diagnostic - // fields are unavailable here. Enable AsyncPipeReaderInput.DiagnosticLog (DEBUG-only) - // for deeper instrumentation when investigating framing-state corruption. - _logger?.LogWarning("TryParseChunkData unknown byte {FirstByte} in chunk mode, breaking. " + + // Unknown byte in chunk mode. + // Real-world case: server-side serialization fails after CHUNK_START was sent, then SignalR + // emits a normal framed CloseMessage. The first byte here is then the little-endian payload + // length (e.g. 114), not [201]/[202]. If we keep chunk state, subsequent parses keep failing + // with the same warning. Instead, tear down chunk mode and re-parse as normal framed message. + _logger?.LogWarning("TryParseChunkData unknown byte {FirstByte} in chunk mode, falling back to normal parse. " + "binderHash={BinderHash} inputLength={InputLength} " + "state: streamedArgType={TargetType} deserTaskStatus={TaskStatus} chunkFrameBytesConsumed={ChunkFrameBytesConsumed}", firstByte, @@ -977,7 +978,10 @@ public class AcBinaryHubProtocol : IHubProtocol state.StreamedArgType.Name, state.DeserTask?.Status.ToString() ?? "null", state.ChunkFrameBytesConsumed); - break; + + state.Input.Dispose(); + _chunkStates.Remove(binder); + return TryParseMessage(ref input, binder, out message); } return false;