Skip to content

fix(create): a create completing on a disposed hub no longer throws out of its Subscribe callback (exit 2 after a clean summary) - #5691

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/create-completion-after-dispose
Sep 25, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/create-completion-after-dispose

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Refs #5690

What

HandleCreateNodeRequest's completion arm calls ActivatePendingControlPlane after the response. The caller, once answered, may dispose the hub handling the create; GetMeshNodeStream resolves the workspace eagerly from that hub's scope and threw ObjectDisposedException synchronously out of the Subscribe callback onto the pool thread that completed the chain → unhandled → xUnit exit 2 after a clean summary (run 36104469492, MeshWeaver.Compiler.Pipeline.Test, 1102 passed, [FATAL ERROR] stack ending in ActivatePendingControlPlane). In a portal the same path would terminate the process.

Fix

The three builders this chain calls from inside Subscribe callbacks and that resolve hub services eagerly — ActivatePendingControlPlane's stream, RunPostCreationHandlersObs, CompensateFailedCreate — are wrapped in Observable.Defer, so the throw becomes an OnError their existing arms report (the activation's warning; the post-handler/rollback arms, whose Respond is a no-op once the owner-disposing NACK has answered). No bound raised, nothing swallowed.

Verification

The other exit-2 sighting (MeshWeaver.Messaging.Hub.Test, PR #5674 run 36098377607) is a different unhandled exception — a test subscriber with no error arm on a released owned connection — already fixed by #5681; details in #5690.

Doc: Doc/Architecture/RequestViaStreamUpdate (activation section).

Pairs-with: none — no public surface removed.

🤖 Generated with Claude Code

…ut of its Subscribe callback

After the create answers, ActivatePendingControlPlane (#3153) called
hub.GetMeshNodeStream, which resolves the workspace EAGERLY from the handling
hub's scope. A caller that disposes that hub once answered made it throw
ObjectDisposedException synchronously out of the completion callback, onto the
pool thread that completed the chain: an unhandled exception, and a test host
exiting 2 after a clean summary (core run 36104469492). The activation, the
post-creation chain and its rollback are now built inside Observable.Defer, so
the throw is an OnError their existing arms report.

ACreateCompletingOnADisposedHubTest pins it deterministically: red before
(ObjectDisposedException out of LetThrough), green after.

Refs #5690

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 25, 2026 09:20

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

Bulk rollback can still eagerly resolve services after hub disposal, and test coverage does not verify all deferred paths.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

This PR defers service resolution during create completion to prevent disposed-hub exceptions from escaping reactive callbacks.

Changes:

  • Defers activation, post-creation, and rollback operations.
  • Adds a disposed-hub regression test.
  • Documents the deferred activation pattern.
File Summary
test/​MeshWeaver.Graph.Test/​ACreateCompletingOnADisposedHubTest.cs Adds a regression test for completion after hub disposal.
src/​MeshWeaver.Mesh.Contract/​MeshExtensions.cs Defers vulnerable observable construction.
src/​MeshWeaver.Documentation/​Data/​Architecture/​RequestViaStreamUpdate.md Documents the disposal-safe pattern.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

"Post-creation handler chain errored at {Path} — rolling the create back (#638)",
resultNode.Path);
CompensateFailedCreate(hub, resultNode, mode, logger)
Observable.Defer(() => CompensateFailedCreate(hub, resultNode, mode, logger))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — fixed in the next commit. The deferral now lives INSIDE both helpers: CompensateFailedCreate wraps its service resolution in Observable.Defer with its existing Catch outside it, so an ObjectDisposedException at either call site (single-create error arm, bulk Concat at ~2387) becomes the Undetermined outcome and the bulk rollback continues with the next ghost; RunPostCreationHandlersObs is now Observable.Defer(() => BuildPostCreationHandlers(...)). The call-site Defers were removed. Release -warnaserror clean; ACreateCompletingOnADisposedHubTest and ControlPlaneRunsWhenTheRequestArrivesByCreateTest green.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  1 files  ±0    1 suites  ±0   3m 12s ⏱️ +10s
345 tests ±0  345 ✅ ±0  0 💤 ±0  0 ❌ ±0 
349 runs  ±0  349 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

443 tests   - 13   443 ✅  - 13   1m 6s ⏱️ +6s
  2 suites  -  1     0 💤 ± 0 
  2 files    -  1     0 ❌ ± 0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

550 tests   - 1 123   550 ✅  - 1 123   1m 6s ⏱️ - 2m 14s
  1 suites  -     1     0 💤 ±    0 
  1 files    -     1     0 ❌ ±    0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

♻️ This comment has been updated with latest results.

…nHandlersObs so the bulk rollback's Concat is covered too

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    2 files   -   1      2 suites   - 1   6m 4s ⏱️ ±0s
2 635 tests +621  2 635 ✅ +621  0 💤 ±0  0 ❌ ±0 
2 636 runs  +621  2 636 ✅ +621  0 💤 ±0  0 ❌ ±0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 646 tests  +895   1 646 ✅ +1 087   9m 9s ⏱️ + 2m 4s
    2 suites  -   1       0 💤  -   192 
    2 files    -   1       0 ❌ ±    0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    4 files   -   1      4 suites   - 1   14m 52s ⏱️ -12s
3 491 tests  - 662  3 491 ✅  - 660  0 💤  - 2  0 ❌ ±0 
3 495 runs   - 662  3 495 ✅  - 660  0 💤  - 2  0 ❌ ±0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   12 files   -   5     12 suites   - 5   35m 32s ⏱️ -5s
9 110 tests  - 282  9 110 ✅  - 88  0 💤  - 194  0 ❌ ±0 
9 119 runs   - 282  9 119 ✅  - 88  0 💤  - 194  0 ❌ ±0 

Results for commit e998188. ± Comparison against base commit 7bc7ab2.

@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 6d0c8aa Sep 25, 2026
44 checks passed
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.

2 participants