# SignalR — Known Issues & Limitations ## Protocol ### SIG-I-1: 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. ### SIG-I-2: 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. ### SIG-I-3: 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 ### SIG-I-4: 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. ### SIG-I-5: 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 ### SIG-I-6: 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` 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`). Requires deserializer API changes. ## Server-side Setup & DI ### SIG-I-7: 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` from DI and falls back to defaults otherwise. The consumer plugin does NOT use it: Current `PluginNopStartup.ConfigureServices`: ```csharp .AddAcBinaryProtocol(opts => { opts.ProtocolMode = BinaryProtocolMode.AsyncSegment; // ← HARDCODED opts.Logger = new Logger(nameof(AyCodeBinaryHubProtocol)); // ← BYPASSES ILogger DI }); ``` What's missing: - No `services.Configure(configuration.GetSection("AyCode:SignalR:Protocol"))` → `ProtocolMode`, `BufferSize`, `WaitForFlush`, `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` auto-resolution that `BuildProtocol` provides → creates a parallel logger instance (see `../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#log-i-8`). ### Fix direction See `SIGNALR_TODO.md#sig-t-5`. ### Related - `../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#log-i-8` (sibling gap — same plugin, logger setup) - `SIG-I-8` (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(new AcBinaryHubProtocol())` — the actual code uses `.AddAcBinaryProtocol(opts => {...})`. Doc needs a rewrite. ## Client-side Setup & DI ### SIG-I-8: 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(...)` 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: ```csharp var protocolOpts = sp.GetRequiredService>().Value; hubBuilder.AddAcBinaryProtocol(protocolOpts); ``` ## Dispatch ### SIG-I-9: First-call null response (observed) **Status:** Open **Affects:** `PostDataAsync` 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` awaiter's null-mapping path misroutes the parsed result, or `requestId → Task` 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#sig-t-1` ## Cross-cutting (canonical home: `AyCode.Core` repo's `docs/XCUT/`) ### XCUT-I-1: JSON-in-Binary request parameters — cross-ref Canonical entry: **`../../../AyCode.Core/docs/XCUT/XCUT_ISSUES.md#xcut-i-1`**. Summary: SignalR transport carries `SignalPostJsonDataMessage` (JSON inside a Binary envelope) for request params, while response path is pure Binary. Planned replacement is coordinated across BINARY serializer + SIGNALR transport + all consuming projects.