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;