Skip to content

fix(hub): a BuildupAction never starts after its hub has begun shutting down - #3109

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/probe-cost-one-probe-on-invalid-content
Sep 2, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/probe-cost-one-probe-on-invalid-content

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

A BuildupAction never starts after its hub has begun shutting down

Red: core CD run 33619142646 (head eb172af0) — Module bundle (MeshWeaver.AI) →
ProbeHubCostTest.ValidateContentWithSchema_OnInvalidContent_BuildsOneProbeNotTwo failed on its
last assertion, recorder.Records.Where(IsTeardownFault).Should().BeEmpty() → 1 item:

[Warning] [HostedHubsCollection] Rejecting hosted hub creation for address
content-type-registration/ff3a6622…/_Exec in Host content-type-registration/ff3a6622…
during disposal - collection is disposing

Root cause

ContentTypeRegistration.ProbeRegister (the boot-time sweep, since #2973/2026-09-01) builds a
transient probe hub for every static NodeType with a HubConfiguration and disposes it in the same
breath. Dispose() closes hosted-hub creation synchronously; the probe's init turn
(InitializeHubRequest, queued at Build) then runs whenever the action block reaches it — for the
Thread NodeType that is AddThreadExecution's six WithInitialization actions, the fourth of which
eagerly creates the _Exec child hub (HostedHubCreation.Always). On a hub that is already
leaving, that creation is refused with the Warning above. It is not a regression of one commit in
470cdd58e..eb172af0: none of the involved code moved in the range (sweep, HostedHubsCollection,
the immediate CloseCreation in Dispose()), and the run's own evidence artifact shows the Warning
in 159 of 643 per-test logs, at a random offset from each boot — ProbeHubCostTest is simply the
one test asserting a fault-free probe teardown, and it reds whenever the late creation lands inside
its recording window. The interleaving does not occur on an idle developer machine (both eb172af0
and #3098's tree pass locally with no Warning anywhere in the class lifetime), so it cannot be
bisected locally.

Fix (core seam, the init-turn form of #3026/#3072)

MessageHub.HandleInitialize checks IsShuttingDown at every BuildupAction boundary: once teardown
has begun the remaining actions are skipped (one Debug line), the Initialize gate still opens so the
disposal state machine flows, and nothing is installed on a hub that is leaving. This is the same
policy the existing .Catch applies one step later ("teardown ENDED an action" is a recognised
shutdown, not a failure).

  • src/MeshWeaver.Messaging.Hub/MessageHub.cs — the boundary check.
  • test/MeshWeaver.Messaging.Hub.Test/InitializationStopsAtTeardownStartTest.cs — parks the first
    action on the action block, calls Dispose(), releases the park: the second action (which creates a
    child hub, the _Exec shape) must not have run by DisposalCompleted. Fails before / passes after.
    A control arm on a live hub proves the chain runs to the end and the child exists.
  • Doc/Architecture/HubDisposalModel.md — "The rule for initialization" under "The first instant of
    teardown". What's New entry (Fix): the Warning appeared on most portal starts.

Verified

  • dotnet build test/MeshWeaver.Messaging.Hub.Test -c Release -warnaserror clean; the whole test
    project green; the new test red with the MessageHub.cs change reverted.
  • MeshWeaver.Plugins MeshWeaver.AI.Test --filter ProbeHubCostTest green against this tree.

Left out, deliberately

The Thread NodeType's AddThreadExecution still installs its full control plane on any transient
probe that lives long enough to answer a request (a _schema_validation/ probe validating Thread
content). That is wasted work, not a fault, and it lives in MeshWeaver.Plugins — a follow-up there
(skip the chain when the configuration carries TransientNodeProbe).

🤖 Generated with Claude Code
Claude-Session: https://claude.ai/code/session_01G4xPnGYEbdu8AVvR5jytyj

…ng down

The init turn is queued at Build and runs whenever the action block reaches it —
for a transient probe (ContentTypeRegistration.ProbeRegister, the schema probes)
that is after the probe's own Dispose(). The Thread NodeType's AddThreadExecution
chain then created its _Exec child on a hub that was already leaving, and
HostedHubsCollection refused it with a Warning in 159 of 643 per-test logs of CD
run 33619142646; ProbeHubCostTest reds whenever that late creation lands inside
its recording window.

HandleInitialize now checks IsShuttingDown at every action boundary and skips
the rest — the gate still opens, nothing is installed. The init-turn form of the
#3026/#3072 watcher rule. Pinned by InitializationStopsAtTeardownStartTest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4xPnGYEbdu8AVvR5jytyj
Copilot AI lite review requested due to automatic review settings September 2, 2026 11:26
@meshweaver-cloud
meshweaver-cloud Bot enabled auto-merge September 2, 2026 11:26

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

The new test’s SpinWait park ignores the timeout result, which can make failures less diagnosable (and risks not exercising the intended interleaving under extreme slowness).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Ensures hub initialization (BuildupActions in InitializeHubRequest) does not start additional actions once the hub has begun shutting down, preventing late control-plane installation (e.g., hosted-child creation) during disposal and eliminating the observed disposal-time warning/flaky teardown faulting.

Changes:

  • Gate each BuildupAction boundary in MessageHub.HandleInitialize on IsShuttingDown, skipping remaining actions (with a single debug log) while still opening the Initialize gate.
  • Add a deterministic regression test that disposes the hub mid-initialization and asserts later actions never start, plus a control-arm test for the live-hub case.
  • Document the initialization rule in the hub disposal model and add a user-facing “What’s New” Fix entry.
File summaries
File Description
src/MeshWeaver.Messaging.Hub/MessageHub.cs Adds IsShuttingDown boundary checks around each initialization action so late actions don’t start during teardown while keeping the init gate semantics intact.
test/MeshWeaver.Messaging.Hub.Test/InitializationStopsAtTeardownStartTest.cs New regression + control-arm tests that pin the “no action starts after shutdown begins” rule.
src/MeshWeaver.Documentation/Data/Architecture/HubDisposalModel.md Documents the initialization-specific shutdown rule alongside existing teardown model guidance.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-02-a-host-that-is-leaving-installs-nothing.md Adds a user-visible Fix entry describing the behavioral change and the eliminated warning.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

234 tests  ±0   234 ✅ ±0   1m 57s ⏱️ -31s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

435 tests  +5   435 ✅ +5   52s ⏱️ ±0s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

618 tests  +2   618 ✅ +2   54s ⏱️ -1s
  2 suites ±0     0 💤 ±0 
  2 files   ±0     0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    3 files  ±0      3 suites  ±0   1m 6s ⏱️ ±0s
1 228 tests ±0  1 036 ✅ ±0  192 💤 ±0  0 ❌ ±0 
1 229 runs  ±0  1 037 ✅ ±0  192 💤 ±0  0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

584 tests  +19   582 ✅ +19   2m 46s ⏱️ ±0s
  4 suites ± 0     2 💤 ± 0 
  4 files   ± 0     0 ❌ ± 0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 831 tests  +2   1 831 ✅ +2   2m 6s ⏱️ +11s
    3 suites ±0       0 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   14 files  ± 0     14 suites  ±0   9m 43s ⏱️ -19s
4 930 tests +28  4 736 ✅ +28  194 💤 ±0  0 ❌ ±0 
4 931 runs  +28  4 737 ✅ +28  194 💤 ±0  0 ❌ ±0 

Results for commit 8847cd7. ± Comparison against base commit 73b8833.

♻️ This comment has been updated with latest results.

…from an init action the rule now skips

ContentTypeProbeControlPlaneTest waited for its two swept probes to REACH the Activity
Control Plane install point — an AsyncSubject completed from inside the observable
WithInitialization action. That is exactly the action HandleInitialize now skips once
IsShuttingDown is true, and ContentTypeRegistration.ProbeRegister disposes the probe on
the line after building it, so on CI (run 33624577907, shard 4) and locally the Dispose()
lands before the init turn every time: the signal never fires and both #2990 tests time
out after TestTimeouts.Quick having asserted nothing.

The signal was obsolete, not the assertions. Registration itself is untouched by the skip:
AddMeshDataSource composes WithContentType into AddData, whose RegisterWorkspaceTypes is a
SYNCHRONOUS buildup action inside Build (DataContext.Initialize -> IMeshContentTypeRegistry
.Register), so the sweep's build-and-dispose shape registers every static type whether or
not the probe's init turn ever runs. The test now captures each probe's DisposalCompleted
from the synchronous WithInitialization overload (runs inside Build, provably before the
sweep's Dispose()) and waits on that: the ShutdownRequest is queued behind the init turn,
so disposal completing means the init turn ended (run or skipped) and the teardown wrote
whatever it writes. The ACP install stays in the observable overload, so whenever the init
turn does win the race the #2990 guards are still exercised; which way the race went is
written to the test output.

Also records a timed-out park in InitializationStopsAtTeardownStartTest (Copilot review:
the SpinUntil result was ignored) so a budget-ended park is reported as such and not as
the rule under test failing, and documents in HubDisposalModel what the skip does NOT
touch and how a test observes a probe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4xPnGYEbdu8AVvR5jytyj
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