Skip to content

MeshWeaver.FutuRe.Test SIGSEGVs after all tests pass: SourcesWatcher keeps requesting against a hub disposed 2s earlier, and reddens a required Plugins gate #3026

Closed

Description

@rbuergi

MeshWeaver.FutuRe.Test intermittently crashes its test host with SIGSEGV (exit 139) after every test has passed. It reddens Build + test the portal hosts — a required context on MeshWeaver.Plugins — so a green PR becomes an un-mergeable one for a reason nothing in the diff can reach. Observed on Plugins PR #1122 (run 33570183207, shard 3) while shards 0/1/2 passed and a sibling PR's run had all four green, so it is intermittent rather than deterministic.

The CI harness's own annotation states the class exactly, and I am quoting it rather than re-deriving it:

THE TEST HOST EXITED 1 WITH NO FAILING TEST. Every test passed and the host STILL exited non-zero, so nothing failed ON a test thread and there is no failing assertion to find. An exception escaped on a non-test thread and reached AppDomain.UnhandledException … xUnit v3 does NOT hook TaskScheduler.UnobservedTaskException, so this can only be a raw throw on a timer / ThreadPool / dedicated thread — typically a DI resolve inside a single-argument Subscribe(onNext) body, which Rx RETHROWS rather than routing to onError. Fix it by GATING the callback on the hub's teardown state, not by catching the exception; do not re-run.

The measured mechanism: a watcher that keeps running after dispose

From the shard's _meshweaver-test-trace.log (pid 51286), one fixture's teardown:

23:30:20.819  DISPOSE_CLIENTS_DONE elapsed=13ms
23:30:20.825  DISPOSE_INVOKED      elapsed=19ms
23:30:22.877  [FAULT] [Warning] [MeshWeaver.Graph.CompileWatcher] SourcesWatcher: the @@-include
              closure for FutuRe/LocalAnalysis could not be established …
              MeshWeaver.Graph.Configuration.SourceIncludeUnavailableException
               ---> System.ObjectDisposedException: Hub FutuRe/LocalAnalysis was disposed before
                    the response arrived (request type GetDataRequest,
                    target FutuRe/GroupAnalysis/Source/ExternalDependencies).
23:30:22.894  DISPOSE_DONE elapsed=2088ms teardown clean — all pooled I/O joined, async dispose queue drained

So SourcesWatcher is still issuing GetDataRequests two seconds after DISPOSE_INVOKED, against a hub that is already gone. It happens 22 times in this one shard, every one of them for FutuRe/LocalAnalysis, spanning 23:30:22.877 → 23:31:47.274 — i.e. it continues for a minute and a half, across subsequent fixtures.

Note what the teardown assertion says while this is happening: teardown clean — all pooled I/O joined, async dispose queue drained. The straggler is not in the pooled-I/O set the fixture knows how to join, which is why the existing teardown gate does not catch it.

The 22 logged instances are caught (they surface as [FAULT] [Warning]). The crash is the one that is not — same window, same watcher, a path where the throw reaches a raw thread instead of the log. A core dump for the crashing process is in the run's artifact teardown-stragglers-33570183207-1-shard3 (MeshWeaver.Futu-51286.dmp, 1.3 GB) if you want the stack.

Where it lives

Core, not Plugins: MeshWeaver.Graph.CompileWatcher / SourcesWatcher, and SourceIncludeUnavailableException is src/MeshWeaver.Compiler.Pipeline (SourceFingerprintIncludeReader.cs, NodeTypeCompilationHelpers.cs). The suite is merely the observation site — every frame in the failure is core code.

What the fix is not

Per the standing directive, exit 139 is use-after-dispose, never a flake to retry, so please do not close this by re-running or by wrapping the throw in a catch. Catching would convert a crash into 22 more warnings and leave the watcher doing work for a disposed hub. The watcher needs to observe the hub's teardown state and stop — and the fixture's teardown clean verdict should not be able to read clean while a watcher of one of its hubs is still issuing requests.

Possibly the same subject

MeshWeaver.Plugins#1141 (now closed) recorded a one-sample 50s timeout in this same suite where the area resolved to a RedirectControl pointing at itself. I disproved its stated cause (the type-registration warnings appear on green runs too — 114 of them across 28 types on a 2.9s passing run). A compile watcher that cannot establish a source-include closure is a plausible route to a view that never renders its control, so the two may share a root. Not asserted — flagging it so whoever fixes this checks.

Activity

  1. added a commit that references this issue on Sep 2, 2026
  2. rbuergi commented on Sep 2, 2026

    @rbuergi
    ContributorAuthor

    Fixed by #3072 (merged today). Hub-owned watchers (sources/compile/release/adopted-stamp) stop at the first instant of teardown via the new IMessageHub.ShuttingDown signal and SubscribeHubWatcher; no request is issued against a disposing hub, so the raw-thread throw that killed the FutuRe test host has no path.

    🤖 Generated with Claude Code

  3. rbuergi commented on Sep 3, 2026

    @rbuergi
    ContributorAuthor

    Recurred twice against trees that contain #3072 — and the dump says the remaining fault is not a watcher

    Not asking to reopen; recording the measurement so the fix is not read as complete.

    Base rate, Plugin Catalog CI, 1,197 runs over 2026-08-29 → 2026-09-03 (every failed run's every failed job's annotations read — 7,362 rows — plus all 92 cancelled main runs, so a crash that preceded a cancellation could not hide):

    5 occurrences, all MeshWeaver.FutuRe.Test, one on main (run 33575831719). No other suite shows the signature. Onset 2026-09-01T23:15; nothing matching in the preceding ~2.5 days.

    Two of the five ran against core commits that already contain #3072 (merge 17ee6d9fa, 2026-09-02T08:43Z):

    run core pin vs 17ee6d9fa
    33668812982 7e9858ec 87 commits ahead
    33778900486 e7f1d699 156 commits ahead

    3 sightings in the ~9 h before the fix, 2 in the ~32 h after: the rate dropped, the hole did not close.

    What the later dump shows

    I pulled the core dump out of run 33778900486's own artifact (MeshWeaver.Futu-49849.dmp):

    si_signo=11  si_code=1 (SEGV_MAPERR)  si_addr=0x0  TRAPNO=14  ERR=0x4  CR2=0x0
    RIP = libcoreclr + 0x5d89e7 = WKS::gc_heap::background_mark_simple1(unsigned char*) + 0x827
    
    49 8b 08   mov rcx,[r8]     ; rcx = object->MethodTable
    48 83 e1 f8 and rcx,~7
    44 8b 09   mov r9d,[rcx]    ; ← FAULT, RCX = 0
    

    A hardware page fault at address 0 on a dedicated background-GC thread, RIP inside a file-backed libcoreclr.so, zero managed frames on the faulting thread. Disposal in that process was clean and the trace record is provably complete (both FAULT-BUDGET lines belong to a different pid): every DISPOSE_DONE reads teardown clean — all pooled I/O joined, async dispose queue drained, leakedIoLeaves=0, zero DISPOSE_QUIESCE_LEAK, zero DISPOSE_DIRTY_TEARDOWN.

    That is sighting #9 of the zeroed-MethodTable family in Doc/Architecture/DebuggingNativeCrashes — a fourth distinct gc_heap frame, same dereference. Written up in #3241.

    So the reading that fits both halves is the one that page already states: the watcher work was part of the workload that provokes a CoreCLR GC fault, not the fault itself. Removing it lowered the exposure; what remains is the family, and it is still live on main.

    The reporting has been fixed too — Systemorph/MeshWeaver.Plugins#1289 — because the shard's classifier had been calling this SIGSEGV an escaped exception and printing a static remedy naming a DI resolve in a Subscribe(onNext), which is what sent #3229 at a teardown gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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