Skip to content

Hardened WebSocket streaming and incremental delivery - #608

Merged
xperiandri merged 6 commits into
devfrom
task-seq-field-websocket
Sep 19, 2026
Merged

xperiandri merged 6 commits into
devfrom
task-seq-field-websocket

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Contains only WebSocket-specific work extracted semantically from the former mixed branch.

Included

  • WebSocket subscription cleanup and lifecycle synchronization.
  • Incremental payload transport and terminal error preservation.
  • WebSocket error sanitization/deduplication and focused serialization tests.

Review scope

Please review only WebSocket transport, subscription lifecycle, and WebSocket payload/error serialization. The response-data contract is in #611, and generic streaming/middleware fixes are in #612. Those areas should not be reviewed again here.

Dependency chain: #611#612 → this PR → #609.

Verification

Relevant projects build successfully; the full unit suite passed (663 passed, 5 pre-existing skips). git diff --check is clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved WebSocket send-serialization and subscription-cleanup findings block approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR hardens GraphQL WebSocket incremental delivery, subscription lifecycle handling, nullable response contracts, and error processing.

Changes:

  • Adds incremental payload paths, completion markers, and safer error serialization.
  • Improves subscription cleanup, cancellation, and async enumeration handling.
  • Updates nullable response APIs, middleware behavior, tests, and release notes.
File summaries
File Summary
tests/FSharp.Data.GraphQL.Tests/TaskSeqFieldTests.fs Tests streaming and nullable TaskSeq behavior.
tests/FSharp.Data.GraphQL.Tests/Relay/ConnectionTests.fs Updates nullable direct-response assertions.
tests/FSharp.Data.GraphQL.Tests/MutationTests.fs Updates nullable mutation-result assertions.
tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs Covers middleware coercion errors.
tests/FSharp.Data.GraphQL.Tests/LazyEnumerationExceptionTests.fs Updates lazy enumeration and null-data assertions.
tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/ObservableExtensionsTests.fs Tests async enumeration failure and cancellation.
tests/FSharp.Data.GraphQL.Tests/ExecutorMiddlewareTests.fs Updates nullable executor-result assertions.
tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs Covers null data and execution errors.
tests/FSharp.Data.GraphQL.Tests/ErrorHelpers.fs Adds nullable direct-result helpers.
tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs Updates deferred nullable-error expectations.
tests/FSharp.Data.GraphQL.Tests/AspNetCore/SerializationTests.fs Tests WebSocket serialization and error handling.
src/FSharp.Data.GraphQL.Shared/WebSockets.fs Updates nullable incremental payload models.
src/FSharp.Data.GraphQL.Shared/TypeSystem.fs Supports nullable TaskSeq resolver handling.
src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs Adds nullable TaskSeq field overloads and documentation.
src/FSharp.Data.GraphQL.Server/ObservableExtensions.fs Hardens async enumeration lifecycle handling.
src/FSharp.Data.GraphQL.Server/IO.fs Updates nullable response contracts.
src/FSharp.Data.GraphQL.Server/Executor.fs Maps nullable execution results.
src/FSharp.Data.GraphQL.Server/Execution.fs Preserves partial data and incremental errors.
src/FSharp.Data.GraphQL.Server/ErrorMessages.fs Updates enumerable error helpers.
src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs Handles middleware coercion failures as request errors.
src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs Updates incremental delivery, sanitization, and lifecycle handling.
src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLSubscriptionsManagement.fs Synchronizes subscription registry operations.
src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs Adapts HTTP response conversion to nullable contracts.
RELEASE_NOTES.md Documents response and WebSocket changes.
Review details

Suppressed comments (3)

src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLSubscriptionsManagement.fs:46

  • If one subscription's unsubscribe action or Dispose throws, Seq.iter stops here and the remaining snapshot entries are never disposed. Because the dictionary was already cleared, those subscriptions cannot be recovered on a later cleanup, leaking active streams on disconnect; cleanup should dispose every entry independently while preserving/reporting failures.
    subscriptionsToDispose
    |> Seq.iter (fun struct (id, subscription) -> subscription |> executeOnUnsubscribeAndDispose id)

src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs:469

  • The new finally is the primary protection against leaking active subscriptions when the receive loop exits, but the changed test set has no WebSocket middleware regression test for disconnect/request cancellation or the synchronous Subscribe completion/throw races this PR fixes. Add coverage that asserts every registered subscription is disposed and its id is released on those paths.
            finally
                subscriptions
                |> GraphQLSubscriptionsManagement.removeAllSubscriptions

src/FSharp.Data.GraphQL.Shared/TypeSystem.fs:2506

  • The new ValueNone branch is not exercised by the added test, which only resolves ValueSome. This branch is the nullable contract and should be verified through execution (including a streamed or deferred request) so a regression cannot turn a missing sequence into an enumerable/type error instead of GraphQL null.
            | ValueNone -> box valueNone
  • Files reviewed: 24/24 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/ErrorMessages.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Execution.fs Outdated
Comment thread tests/FSharp.Data.GraphQL.Tests/TaskSeqFieldTests.fs Outdated
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Test Results

    9 files      9 suites   13m 22s ⏱️
  776 tests   771 ✅  5 💤 0 ❌
2 328 runs  2 313 ✅ 15 💤 0 ❌

Results for commit 075abb6.

♻️ This comment has been updated with latest results.

@xperiandri
xperiandri force-pushed the task-seq-field-websocket branch from 765ac78 to 73d00c3 Compare September 18, 2026 17:48
@xperiandri
xperiandri changed the base branch from dev to task-seq-field-streaming-fixes September 18, 2026 17:49
@xperiandri
xperiandri force-pushed the task-seq-field-streaming-fixes branch from 8fad834 to 551f6c7 Compare September 18, 2026 19:54
@xperiandri
xperiandri force-pushed the task-seq-field-websocket branch from 73d00c3 to 500d88a Compare September 18, 2026 19:54
@xperiandri
xperiandri force-pushed the task-seq-field-streaming-fixes branch 2 times, most recently from bbd883f to b57bf2d Compare September 18, 2026 23:05
@xperiandri
xperiandri force-pushed the task-seq-field-websocket branch from 500d88a to f0ad702 Compare September 18, 2026 23:26
Base automatically changed from task-seq-field-streaming-fixes to dev September 18, 2026 23:49
@xperiandri
xperiandri force-pushed the task-seq-field-websocket branch from f0ad702 to 19b5f35 Compare September 18, 2026 23:53
@xperiandri
xperiandri requested a lite review from Copilot September 18, 2026 23:55
@xperiandri
xperiandri force-pushed the task-seq-field-websocket branch from 19b5f35 to 3e3dbb6 Compare September 18, 2026 23:59
Add the WebSocket-specific transport, subscription lifecycle, and
payload/error serialization changes on top of the generic streaming
and data-contract fixes: the SubscriptionExecutionResult.Data contract
change to obj voption Skippable and related lifecycle handling in
Shared/WebSockets.fs; GraphQLSubscriptionsManagement.fs subscription
bookkeeping; the ObservableErrorHandling sanitization/deduplication
module and remaining lifecycle/serialization changes in
GraphQLWebsocketMiddleware.fs; the RELEASE_NOTES.md entry documenting
the SubscriptionExecutionResult.Data change; and the new/updated
WebSocket wire-format and error-sanitization tests in
SerializationTests.fs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs Outdated
Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
* Introduced `vtryItem` for Seq, List, and Array to provide ValueOption-based safe indexing.
* Updated tryGetPathItemIndex to use `List.vtryItem` for improved safety and consistency.
* Replaced `Seq.choose` with `Seq.vchoose` in `splitBatch`.
* Added `InternalsVisibleTo` for `FSharp.Data.GraphQL.Server.AspNetCore` in the shared project.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

WebSocket closes remain unsynchronized with sends, and one disposal failure can prevent cleanup of remaining subscriptions.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (4)

Comment thread src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLSubscriptionsManagement.fs Outdated
Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unbounded close handshakes can prevent cleanup, and sanitization currently removes useful server-side diagnostics.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Preserve original backend details in server logs

src/​FSharp.Data.GraphQL.Server.AspNetCore/​GraphQLWebsocketMiddleware.fs:419

Sanitizing before logging removes the original backend message and exception from server-side diagnostics. The client should receive the sanitized list, but the trusted server log should retain problemDetails, as the other exception paths in this middleware do.

Comment thread src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs Outdated
Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>

Copilot AI commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Copilot review overview...

Addressed in 6486c2d.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The reviewed WebSocket transport, lifecycle, serialization, and focused tests are consistent with the stated scope and contain no unresolved issues.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@xperiandri xperiandri changed the title Harden WebSocket streaming and incremental delivery Hardened WebSocket streaming and incremental delivery Sep 19, 2026
@xperiandri
xperiandri merged commit d326a30 into dev Sep 19, 2026
5 checks passed
@xperiandri
xperiandri deleted the task-seq-field-websocket branch September 19, 2026 01:45
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.

3 participants