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.
This commit is contained in:
Loretta 2026-05-22 23:37:44 +02:00
parent c2a22e5215
commit e2b96b4148
3 changed files with 37 additions and 7 deletions

View File

@ -7,7 +7,7 @@
<!--<GitBranch>$([System.IO.File]::ReadAlltext('$(MsBuildThisFileDirectory)\.git\HEAD').Replace('ref: refs/heads/', '').Trim())</GitBranch>
<_ProjectName>$(GitBranch)</_ProjectName>-->
<TargetFramework Condition="'$(TargetFramework)' == ''">net10.0</TargetFramework>
<TargetFramework Condition="'$(TargetFramework)' == ''">net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

View File

@ -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 ?? "<unknown>";
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

View File

@ -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;