Skip to content

Optimize WebSocket receive path on pre-netstandard2.1 targets#1462

Merged
AArnott merged 3 commits into
mainfrom
copilot/websocket-read-async-improvement
Jun 23, 2026
Merged

Optimize WebSocket receive path on pre-netstandard2.1 targets#1462
AArnott merged 3 commits into
mainfrom
copilot/websocket-read-async-improvement

Conversation

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

This change improves WebSocketMessageHandler.ReadCoreAsync for pre-netstandard2.1 TFMs by avoiding an unnecessary copy when the sequence memory can be exposed as an ArraySegment<byte>. It keeps existing behavior while reducing allocations/copy work on the hot read path used by both client and server WebSocket transports.

  • Read-path optimization (netfx / pre-2.1 branch)

    • Switched from always renting a temporary buffer and writing into the sequence, to first obtaining destination memory from contentSequenceBuilder.
    • If MemoryMarshal.TryGetArray(memory, out segment) succeeds, ReceiveAsync now reads directly into destination storage.
  • Fallback correctness when direct array access is unavailable

    • Retained pooled-array fallback for non-array-backed memory.
    • Receives into a bounded segment (new ArraySegment<byte>(rented, 0, memory.Length)), then copies only result.Count bytes into destination memory.
  • Buffer advancement alignment

    • Replaced Write(...) usage in the legacy branch with Advance(result.Count) after receive/copy, aligning mechanics with the modern branch.
Memory<byte> memory = this.contentSequenceBuilder.GetMemory(this.sizeHint);
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
{
    result = await this.WebSocket.ReceiveAsync(segment, cancellationToken).ConfigureAwait(false);
}
else
{
    byte[] rented = ArrayPool<byte>.Shared.Rent(memory.Length);
    try
    {
        result = await this.WebSocket.ReceiveAsync(
            new ArraySegment<byte>(rented, 0, memory.Length), cancellationToken).ConfigureAwait(false);
        rented.AsSpan(0, result.Count).CopyTo(memory.Span);
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(rented);
    }
}
this.contentSequenceBuilder.Advance(result.Count);

Copilot AI and others added 2 commits June 12, 2026 19:45
Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com>
Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve websocket read async performance for netfx Optimize WebSocket receive path on pre-netstandard2.1 targets Jun 12, 2026
Copilot AI requested a review from AArnott June 12, 2026 19:51
@AArnott AArnott marked this pull request as ready for review June 23, 2026 13:35
Copilot AI review requested due to automatic review settings June 23, 2026 13:35
@AArnott

AArnott commented Jun 23, 2026

Copy link
Copy Markdown
Member

/azp run

@AArnott AArnott enabled auto-merge (squash) June 23, 2026 13:35
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the WebSocket receive hot path for pre-netstandard2.1 targets in WebSocketMessageHandler.ReadCoreAsync by avoiding an intermediate pooled-buffer copy when the destination Memory<byte> can be exposed as an ArraySegment<byte>.

Changes:

  • For legacy TFMs, obtains destination storage via contentSequenceBuilder.GetMemory(sizeHint) and attempts direct ReceiveAsync into it via MemoryMarshal.TryGetArray.
  • Preserves correctness with an ArrayPool<byte> fallback when direct array access isn’t available, copying only result.Count bytes.
  • Aligns legacy path with modern path mechanics by using contentSequenceBuilder.Advance(result.Count) instead of Write(...).

@AArnott AArnott merged commit d931926 into main Jun 23, 2026
10 checks passed
@AArnott AArnott deleted the copilot/websocket-read-async-improvement branch June 23, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants