AyCode.Core/AyCode.Services/docs/SIGNALR/SIGNALR_ISSUES.md

8.0 KiB

SignalR — Known Issues & Limitations

Protocol

ACCORE-SIG-I-R4W7: Server-side IsRawBytesData pre-serialize

Status: Open Affects: AcWebSignalRHubBase.SendMessageToClient

The server forwards the client's IsRawBytesData flag in the response SignalParams. This causes the protocol to return raw byte[] instead of deserializing. The original design pre-serialized on the server side, but with the zero-copy typed deserialization path (SignalDataType), this is redundant.

Plan: Remove IsRawBytesData forwarding from server response path. The client should use SignalDataType for typed deserialization and explicit byte[] type for raw data.

ACCORE-SIG-I-L5K3: Parameter serialization is per-parameter

Status: Open Affects: SignalParams.SetParameterValues / GetParameterValues

Each parameter is individually serialized via ToBinary() / BinaryTo(Type) — N context pool acquire/release cycles. For many small primitives (int, bool, string) the per-call overhead may exceed a single bulk serialization.

Possible optimization: Batch fast-path — single serialization context for all parameters. Benchmark first.

ACCORE-SIG-I-H8D6: Parameter serialization is AcBinary only

Status: Open Affects: SignalParams.SetParameterValues / GetParameterValues

Uses ToBinary() / BinaryTo() exclusively. JSON parameter support would require dispatching on DataSerializerType + AcJsonSerializer reference. Low priority — binary is the primary transport.

Transport

ACCORE-SIG-I-P1J4: BufferWriterChunkSize defaults to 64KB for SignalR

Status: Closed (2026-04-25, retroactive — was Status: DONE pre-LLMP-DEC-44 vocabulary normalization) Affects: AcBinaryHubProtocol constructor, write path

The framework's BufferWriterChunkSize default was 64KB, suboptimal for SignalR chunked responses (misaligned with Kestrel slab size, higher latency-to-first-byte).

Resolution

  • What: Set BufferWriterChunkSize = 4096 explicitly in AcBinaryHubProtocol constructor.
  • Where: AcBinaryHubProtocol constructor (override applied at construction time).
  • Why: Aligned with Kestrel's slab allocation size; reduces latency-to-first-byte for chunked SignalR responses.
  • Scope: Override is SignalR-specific. Non-SignalR paths keep the 64KB default — this is intentional and stable behaviour now.
  • Date: Code change predates this status update; the entry was already Status: DONE before the LLMP-DEC-44 vocabulary normalization. The current update only formalizes it as Closed per the new 3-value convention.

ACCORE-SIG-I-N3V8: WebSocket buffer sizes are hardcoded

Status: Open Affects: AcSignalRClientBase connection setup

Transport max message size (30MB) and application buffer (30MB) are hardcoded. Sufficient for current payloads but not configurable per-deployment.

DataSource

ACCORE-SIG-I-T7S2: GetAll returns raw byte[] for populate/merge

Status: Open Affects: AcSignalRDataSource.LoadDataSourceAsync

The GetAll path uses IsRawBytesData = true to receive raw byte[] from the protocol, then deserializes into the existing list via PopulateMerge. This avoids allocating a temporary List<T> for merge. The extra copy (pipe → byte[]) is the trade-off.

Possible optimization: Direct typed deserialization with merge support in the deserializer (PopulateMerge from ReadOnlySequence<byte>). Requires deserializer API changes.

Server-side Setup & DI

ACCORE-SIG-I-B5G9: Server-side NopCommerce plugin AcBinaryHubProtocolOptions not bound from appsettings

Status: Open · Severity: Minor (works, but hardcoded + bypasses DI logger) · Area: Consumer adoption gap in Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs

The framework overload chain is complete on the server side — AcSignalRProtocolExtensions.BuildProtocol already resolves IOptions<AcBinaryHubProtocolOptions> from DI and falls back to defaults otherwise. The consumer plugin does NOT use it:

Current PluginNopStartup.ConfigureServices:

.AddAcBinaryProtocol(opts =>
{
    opts.ProtocolMode = BinaryProtocolMode.AsyncSegment;      // ← HARDCODED
    opts.Logger = new Logger(nameof(AyCodeBinaryHubProtocol)); // ← BYPASSES ILogger<T> DI
});

What's missing:

  • No services.Configure<AcBinaryHubProtocolOptions>(configuration.GetSection("AyCode:SignalR:Protocol"))ProtocolMode, BufferSize, FlushPolicy, FlushTimeout are all hardcoded / default.
  • The appsettings.json has no AyCode:SignalR (or equivalent) section at all — so per-deploy tuning (e.g. increasing FlushTimeout for a satellite link, switching ProtocolMode for diagnostics) requires a code change + redeploy.
  • Manual new Logger(...) sidesteps the DI ILogger<AcBinaryHubProtocol> auto-resolution that BuildProtocol provides → creates a parallel logger instance (see ../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#accore-log-i-m4c9).

Fix direction

See SIGNALR_TODO.md#accore-sig-t-m5l6.

  • ../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#accore-log-i-m4c9 (sibling gap — same plugin, logger setup)
  • ACCORE-SIG-I-K6F1 (client-side equivalent — HubConnectionBuilder.Services inner DI isolation forces a different workaround)
  • Plugin doc drift: Nop.Plugin.Misc.AIPlugin/docs/SIGNALR/README.md:22 documents services.AddSingleton<IHubProtocol>(new AcBinaryHubProtocol()) — the actual code uses .AddAcBinaryProtocol(opts => {...}). Doc needs a rewrite.

Client-side Setup & DI

ACCORE-SIG-I-K6F1: HubConnectionBuilder inner DI isolation

Status: Open Affects: Consumer client setup in Program.cs (MAUI, WASM, ASP.NET Core server prerender)

HubConnectionBuilder.Services is a separate IServiceCollection from the outer host DI. services.Configure<AcBinaryHubProtocolOptions>(...) registered in the outer container does NOT flow into HubConnectionBuilder.Services. Calling hubBuilder.AddAcBinaryProtocol() with no args silently falls back to default options.

Known workaround: Use the overload AddAcBinaryProtocol(IHubConnectionBuilder, AcBinaryHubProtocolOptions, Action<...>? = null) that accepts pre-resolved options explicitly. Canonical consumer pattern:

var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
hubBuilder.AddAcBinaryProtocol(protocolOpts);

Dispatch

ACCORE-SIG-I-X4M7: First-call null response (observed)

Status: Open Affects: PostDataAsync<T> awaiter / OnReceiveMessage → pending-request correlation

Observed symptom: first GetProductDtos_80-style call returns null despite server serializing and sending a valid ~80KB chunked response. Second call (client-side retry) works normally.

Log timeline:

  • Server: Serialize end (chunked) dataBytes=80571 chunkCount=20
  • Client: Deserialize end (chunked) — successful
  • Client: OnReceiveMessage ... requestId=5
  • Client: ~410ms later — Client received null response. ... requestId=5
  • Client: auto-retry (requestId=6) → full 338 items

Hypothesis (unverified): PostDataAsync<T> awaiter's null-mapping path misroutes the parsed result, or requestId → Task<T> correlation has a race on the first response of a fresh connection. Client auto-retry hides the user-visible impact.

Related TODO: SIGNALR_TODO.md#accore-sig-t-j2p5

Cross-cutting (canonical home: AyCode.Core repo's docs/XCUT/)

ACCORE-XCUT-I-X8Q1: JSON-in-Binary request parameters — cross-ref

Status: Closed (2026-04-26, see canonical entry).

Canonical entry: ../../../AyCode.Core/docs/XCUT/XCUT_ISSUES.md#accore-xcut-i-x8q1. Summary: SignalR transport previously carried SignalPostJsonDataMessage<T> (JSON inside a Binary envelope) for request params, while response path was pure Binary. Replacement landed in commits cdd54d3 + 3b70070: SignalParams (length-prefixed binary pack/unpack) replaces the JSON envelope, coordinated across BINARY serializer + SIGNALR transport + consumer projects. Breaking change for older clients.