Skip to content

Stream frames bypass the bounded queue under WebAssembly #110

Description

@Platonenkov

Under WebAssembly, stream frames bypass the bounded queue entirely, so nothing limits the backlog and nothing counts what is lost.

What happens

Connection.EnqueueStreamMessage (Xrpl/Client/connection.cs):

private void EnqueueStreamMessage(byte[] frame)
{
    if (OperatingSystem.IsBrowser())
    {
        _ = ProcessStreamMessageFireAndForgetAsync(frame);   // straight to a handler, no queue
    }
    else
    {
        channel.Writer.TryWrite(frame);
    }
}

Consequences in a browser:

Why it was not simply changed

The comment on StartMessageProcessor says the channel exists for this environment:

Uses Channel<T> for true async support in WebAssembly single-threaded environment.

So the bypass reads more like a leftover than a decision — but that cannot be settled by reading. WASM is single-threaded, Task.Run there is cooperative rather than parallel, and whether the background reader gets scheduled the way the non-browser path assumes needs checking in a real browser. Nothing in this repository can run that check: there are no WASM tests, only the Blazor-WebAssembly demo project.

Changing stream dispatch blind, inside a PR about observability, was the wrong trade — so #105 documented the limitation on DroppedStreamMessages, on StreamMessageQueueCapacity, on the interface member and in CHANGES.md, and left the behaviour alone.

What this issue is for

Decide whether browser frames should go through the same bounded queue, and verify it where it actually runs:

  1. Establish, in Tests/TestsClients/Blazor-WebAssembly, whether the channel reader is scheduled reliably under WASM — that is the question the bypass exists to dodge, and everything else follows from the answer.
  2. If yes: delete the browser branch, so capacity, eviction counting and single-reader ordering apply everywhere, and the two paths stop diverging.
  3. If no: keep the bypass but bound it — a semaphore or an explicit in-flight cap — and count what it discards, so the counter means the same thing on both platforms.

Either way the goal is that StreamMessageQueueCapacity and DroppedStreamMessages describe reality on every target, rather than on all but one.

Found by review of #109.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions