Skip to content

refactor(di): replace Autofac with MeshWeaver's own container, MeshServiceProvider (#7145) - #7147

Merged
rbuergi merged 7 commits into
mainfrom
feat/7145-replace-autofac
Oct 25, 2026
Merged

rbuergi merged 7 commits into
mainfrom
feat/7145-replace-autofac

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Refs #7145 — replaces Autofac with MeshWeaver's own container, MeshServiceProvider, at every level (the mesh root, each hub's level, each module generation's container, every plain scope), and removes the Autofac packages from core. Scope per the owner: the whole replacement in one change, then measure.

Why our own container, and not services.BuildServiceProvider() at the root

The plan in #7145 put the standard provider at the root and our layer only on the hubs. Building it showed the root cannot be the standard provider without losing a feature the portal runs on:

  • The standard provider hands the HOST its own built-in IServiceScopeFactory (it cannot be overridden), so every request and Blazor circuit scope is a plain MS DI scope.
  • Module-declared types (a live-updatable module's own services, e.g. a Razor component's Radzen DialogService) reach a circuit only through a lookup hook on the root (ModuleOwnedRootSource). A plain MS DI scope has no hook, so those types would stop resolving in circuits and requests.

So one class serves every level. It implements the standard Microsoft.Extensions.DependencyInjection contracts over ServiceDescriptors. The standard specification suite (Microsoft.Extensions.DependencyInjection.Specification.Tests 10.0.0, run locally against the container directly, through an empty level, and with every registration on a level) passes except for deliberate differences: hierarchical scopes (a hub's child scopes must see the hub's registrations), a level does not own the root's singletons, and one AnyKey enumerable-caching detail. Design: Doc/Architecture/HubServiceLayers (new).

What changed

  • MeshServiceProvider (src/MeshWeaver.ServiceProvider):
    • Per level: a level holds only its own descriptors, frozen into an immutable lookup, plus a slot for each instance it built. Nothing is copied from a parent.
    • Lookup order: the level, then each parent level, then the root, then the root's IServiceRegistrationSource (the "last step" hook).
    • Lifetimes: a singleton lives in the level that registers it. A scoped or transient service is built by the RESOLVING scope with that scope's dependencies, so a root-scoped INodeValidator resolved in a hub gets that hub's own IMessageHub. A root open generic closed over a collectible type is held by the asking level, never by the root.
    • Locks: each shared instance has its own synchronous lock. There is no scope-wide lock and nothing is awaited.
    • Faults and cycles: a fault is not cached; the next resolve builds afresh, which is why this is not Lazy. A cycle is refused by name, including one that runs through a factory.
    • Disposal: a scope disposes only what it built, in reverse order. Never a provided instance, a NotOwnedServiceDescriptor result or a source stand-in. A parent does not track its children.
    • Implicit relationships the platform's constructors take: collection shapes, Lazy<T>, Func<T>, and IEnumerable<Lazy<T>> as one deferred element per registration of T.
    • Caches: there is no process-static cache, so ReflectionCacheEviction is gone.
  • Module machinery off Autofac types:
    • ModuleServiceProvider, ModuleFallbackSource, ScopedDependencyWalk, the caller-owned module scopes and the ownership diagnostics (now activation observers) run on the container's own API.
    • ModuleOwnedRootSource is an IServiceRegistrationSource.
    • The caller type in ModuleContexts and ModuleServices is MeshServiceProvider.
    • CallerSharedInstanceLock (which set Autofac's private _synchRoot by reflection), HubScopeStartables and PopulateMeshWeaver(ContainerBuilder) are deleted.
  • Disposed-container classification: done in one place, HubDisposingException.IsDisposedContainer / IsDisposedContainerReason, keyed on the container's object name MeshWeaver.ServiceScope. The access pipeline, the test suppressors and the Orleans race suppressor use it. A disposed scope still answers an UNREGISTERED service with null, as before: teardown code probes optional services on dying hubs.
  • Message compatibility: a missing keyed service still reads The requested service '<key> (<type>)' has not been registered., so the module-lease alarm regex still matches.
  • Packages: Autofac and Autofac.Extensions.DependencyInjection are removed from Directory.Packages.props, MeshWeaver.ServiceProvider.csproj and the two test projects. Comments and docs no longer name Autofac except as a history note that says it was removed.

Proof

  • Local gates: dotnet build -c Release -warnaserror is clean for every touched src project and these test projects: Messaging.Hub, Compiler.Pipeline, Data, Layout, Hosting, Graph, Hosting.Orleans, Documentation, Memex.Portal.Shared.
  • Full suites (local): Documentation 937, Messaging.Hub 686, Compiler.Pipeline 1,618, Data 597, Layout 897, Hosting 1,632 (1 skipped), Hosting.Orleans 393, Memex.Portal.Shared 3,094 (1 skipped) — all green; Graph 3,736 with 1 red, FollowedSubscriptionOwnerIdentityTest.AFollowedDirectSubscriber_NeverBecomesTheOwnerWatchersIdentity, which fails identically on an unmodified worktree at the merge base 4287711857 (pre-existing, filed to triage as rbuergi/Feedback/20261025-followed-subscription-owner-identity-red-on-main). After merging origin/main the touched projects rebuild clean and the targeted suites (new main tests in Memex.Portal.Shared and Deployment.Contract, Documentation, the container tests) stay green. MeshWeaver.Plugins against this core: 10 suites, 8,453 tests green (see the paired PR); the core and Plugins operator test scripts pass their dump-analysis fixtures.
  • Defects the suites caught, each fixed at the root and pinned:
    • IEnumerable<Lazy<INodeValidator>> resolved EMPTY. It was read as "registrations of Lazy<T>", so every node validator, row-level security included, was skipped. Three read-access tests in Hosting went red (AutocompleteHonoursReadAccessTest ×2, SystemOwnedSyncConfigIsVisibleToPlatformAdminsTest), and so did AModuleNodeValidatorIsNeverBuiltForNoCallerTest. All four are green after the fix, and MeshServiceProviderTest pins the behaviour.
    • Factories were handed a wrapper provider, so hub.ServiceProvider was not the hub's level. This turned HubDisposesItsServiceProviderTest ×2 and ScopeDisposedDuringInitializationTest red. Factories now get the scope itself, and cycles through factories are caught on slot re-entry.
    • A disposed scope threw on an unregistered optional service (ScopeTeardownRenderTest).
  • Negative controls:
    • The three lock-order tests (RootAndModuleSingletons…, AScopedRootService…, CallerAndModuleScoped…) asserted a DEADLOCK on main, on Autofac's scope-wide lock. They now force the same interleaving, assert that both constructions were inside their builds at once, and complete.
    • ACollectibleTypeIsNeverHeldByTheRootTest: its negative control resolves the same closure at the root and sees the load context retained.
    • HubScopeDeclaresOnlyItsOwnServicesTest: a hub-local override still wins (directly, through a parent-scoped service, and in enumeration order), and a root-scoped service in a hosted hub gets that hub's IMessageHub.
  • Live-mesh sweep before removing public surface:
    • Method: script rbuergi/Script/sweep-retired-di reads every Code and NodeType node per partition. It covered 127 partitions and 1,483 nodes on systemorph, and 17 partitions and 296 nodes on control, with 0 failed reads. The positive control PrebuiltAssemblySeeder was found on both.
    • Callers of the removed surface: ILifetimeScope, ModuleServiceProvider, ReflectionCacheEviction, NotOwnedServiceDescriptor, CallerOfRootScope, IRegistrationSource and the others had 0 callers. CreateMeshWeaverServiceProvider is named by 2 scratch scripts and is kept. "Autofac" appears once, in the text of a doc comment in Hosting/InstanceAction/Source/ObservationQueries (the paired Plugins PR rewords it).
    • Chunk search: search_chunks returned searched:true, count:0.

Footprint (one harness on both sides, dumpheap -stat -live)

The harness: 99 Markdown node hubs activated through GetMeshNodeStream in one Monolith test mesh, subscriptions held, then a --type Heap dump. "Before" is core at the merge base (Autofac, after #7131); everything else is identical. This harness holds 608 MessageHubs on both sides. #7131 measured 516 with a different harness, so compare within the table.

Before (Autofac, after #7131) After (MeshServiceProvider)
Live heap 150.2 MB 119.0 MB
DI-container rows 27.1 MB (44.5 KB/hub) 1.6 MB (2.7 KB/hub)
Wrapper registrations (ExternalComponentRegistration) 18,146 (≈30/hub) 0
TypeRegistry rows 12.6 MB (20.7 KB/hub) 12.6 MB (20.7 KB/hub)
Retained per node-hub activation (GC.GetTotalMemory) 2.31 MB 1.77 MB

#7131's own table, for reference: 133.9 MB live heap and 2.04 MB per node hub, of which 55 KB per hub was Autofac and 23 KB per hub was TypeRegistry. HubScopeDeclaresOnlyItsOwnServicesTest now asserts that a hub level holds zero registrations of its parents. Recorded in Doc/Architecture/PortalHeapIsHubs → Per-hub DI and type metadata.

Post-roll step (owed, not triggered here)

After this rolls to memex: if a fresh dump exists, file a governed AnalyzeDump (heap stats) on the control instance and compare it with the #7082 OOM reading. Check that no ExternalComponentRegistration is present, read the per-hub container rows at production's hub population, and compare the live heap against the 12.3 GB OOM reading. Nothing on the cluster was triggered by this PR.

Order and declarations

The paired MeshWeaver.Plugins PR is compatible with BOTH containers, because it classifies through core's HubDisposingException.IsDisposedContainer and builds real containers through CreateMeshWeaverServiceProvider. It therefore lands FIRST, so Plugins main never goes red when this platform set seals. Memex drops its own Autofac pins in a separate PR.

Pairs-with: Systemorph/MeshWeaver.Plugins#4170
Satellite-pins: Autofac, Autofac.Extensions.DependencyInjection — git grep -i autofac over MeshWeaver.Plugins origin/main: no PackageReference to either (the only uses were using Autofac in MeshWeaver.AI.Test ×2 and MeshWeaver.Import/TableToTypeMapping.cs, all removed by the paired PR); the other satellites have 0 mentions; Memex pins its own versions in its own Directory.Packages.props (removed in Systemorph/Memex#821).

No address needs a recycle. This is framework code and is picked up when replicas roll. Every hub is rebuilt on its new process.

🤖 Generated with Claude Code

rbuergi and others added 2 commits October 25, 2026 00:47
…rviceProvider (#7145)

One container class for the mesh root, every hub's level, every module
generation's container and every plain scope, over standard
ServiceDescriptors. A hub level holds only its own descriptors, frozen, and
the instances it built; a parent's scoped or transient service resolved in a
hub is built with the hub's own dependencies; module-declared types are
answered by an IServiceRegistrationSource at the last step of a lookup.
Per-instance locks (no scope-wide lock), faults never cached, cycles refused
by name, a scope disposes only what it built, no process-static cache.

Module machinery (ModuleServiceProvider, ModuleFallbackSource,
ScopedDependencyWalk, caller-owned module scopes, ownership diagnostics,
ModuleOwnedRootSource, ModuleContexts/ModuleServices callers) moved off
Autofac types; CallerSharedInstanceLock, HubScopeStartables,
ReflectionCacheEviction and PopulateMeshWeaver(ContainerBuilder) are gone.
The disposed-container classification lives in HubDisposingException only.
Autofac and Autofac.Extensions.DependencyInjection are removed from
Directory.Packages.props and every project.

Measured (same harness both sides, 608 hubs): live heap 150.2 -> 119.0 MB,
DI-container rows 44.5 -> 2.7 KB/hub, wrapper registrations 18,146 -> 0,
retained per node-hub activation 2.31 -> 1.77 MB.

Refs #7145

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 24, 2026 23:11
@rbuergi
rbuergi marked this pull request as draft October 24, 2026 23:12

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.

🔵 Needs a closer look

Foundational DI, live-module, and teardown changes require human review; the two recorded findings are documentation nits.

1 open issue
What changed in this PR

Replaces Autofac with MeshServiceProvider across root, hub, module, and ordinary scopes, preserving hierarchical resolution and live-module integration while reducing container overhead.

Changes:

  • Adds registration-source and activation-observer contracts and migrates module caller plumbing.
  • Removes Autofac dependencies and cache-eviction hooks; centralizes disposed-container classification.
  • Adapts tests, architecture documentation, and teardown guidance to the replacement provider.

Review notes:

  • Nit — src/MeshWeaver.ServiceProvider/IServiceRegistrationSource.cs:12 (1 vote): Qualify the uncached-lookup guarantee. Source-provided singleton/scoped instances still follow their declared lifetime and may retain collectible types or delay generation changes; the stronger guarantee applies to transient module stand-ins.
  • Nit — test/MeshWeaver.Messaging.Hub.Test/HubDisposesItsServiceProviderTest.cs:33 (3 votes): Distinguish historical Autofac retention from current behavior: parents neither track nor dispose child scopes, so each hub scope must be closed explicitly.
File Description
test/​MeshWeaver.Messaging.Hub.Test/​TeardownHubCreationFreezeTest.cs Updates scope terminology in comments.
test/​MeshWeaver.Messaging.Hub.Test/​ScopeDisposedDuringInitializationTest.cs Updates disposal comments.
test/​MeshWeaver.Messaging.Hub.Test/​HubDisposesItsServiceProviderTest.cs Updates ownership commentary; child-scope documentation nit noted above.
test/​MeshWeaver.Messaging.Hub.Test/​HubCreationDuringTeardownIsNotAFaultTest.cs Updates container descriptions.
test/​MeshWeaver.Messaging.Hub.Test/​HandlerFaultPastDisposeHostedHubsTest.cs Uses replacement-provider disposal constants.
test/​MeshWeaver.Messaging.Hub.Test/​DisposalRegistrantFaultIsolationTest.cs Uses replacement-provider disposal constants.
test/​MeshWeaver.Messaging.Hub.Test/​AHubAtShutDownIsNotHandedOutTest.cs Updates scope terminology in comments.
test/​MeshWeaver.Layout.Test/​SubscriberTeardownReleasesTheOwnerSyncHubTest.cs Updates retained-scope commentary.
test/​MeshWeaver.Layout.Test/​RenderFaultAcrossScopeTeardownTest.cs Updates diagnostic and teardown comments.
test/​MeshWeaver.Hosting.Test/​RecycledHubRefusalTest.cs Migrates disposal fixtures and reason text.
test/​MeshWeaver.Hosting.Test/​QueryConnectionsReleasedOnTeardownTest.cs Updates disposed-scope commentary.
test/​MeshWeaver.Hosting.Test/​OwnerAnswerRecognitionGuard.cs Uses replacement-provider disposal constants.
test/​MeshWeaver.Hosting.Test/​MeshTeardownRunsAfterEveryOtherHostedServiceTest.cs Updates shutdown comments and assertion wording.
test/​MeshWeaver.Hosting.Test/​MeshHostBuilderTeardownOrderingTest.cs Renames the test and updates scope wording.
test/​MeshWeaver.Hosting.Test/​IoPoolTest.cs Updates pooled-work teardown comments.
test/​MeshWeaver.Hosting.Test/​HubScopeClosesAfterTeardownDrainsTest.cs Updates explicit scope-closure commentary.
test/​MeshWeaver.Hosting.Orleans.TestBase/​OrleansShutdownRaceSuppressor.cs Uses centralized disposal classification.
test/​MeshWeaver.Hosting.Orleans.TestBase/​OrleansClusterDisposal.cs Updates container comments.
test/​MeshWeaver.Hosting.Orleans.Test/​RoutingQuiescenceTest.cs Updates container-ordering terminology.
test/​MeshWeaver.Hosting.Orleans.Test/​RoutingAfterScopeTeardownTest.cs Migrates the disposed-container fixture.
test/​MeshWeaver.Hosting.Orleans.Test/​PodHubDeactivatedActivationClassificationTest.cs Generalizes the synthetic disposed-object name.
test/​MeshWeaver.Hosting.Orleans.Test/​IoPoolSiloTeardownTest.cs Updates disposal comments.
test/​MeshWeaver.Hosting.Orleans.Test/​HubConstructionOutcomeReportingTest.cs Uses replacement-provider disposal constants.
test/​MeshWeaver.Hosting.Orleans.Test/​ClusterDisposalRetentionTest.cs Updates retained-container commentary.
test/​MeshWeaver.Graph.Test/​TeardownWaitsForCollectibleUnloadsTest.cs Updates retention and singleton-release comments.
test/​MeshWeaver.Graph.Test/​RecursiveDeleteSurvivesARecycledLeafTest.cs Updates disposal commentary.
test/​MeshWeaver.Graph.Test/​MeshWeaver.Graph.Test.csproj Removes the Autofac dependency.
test/​MeshWeaver.Graph.Test/​MeshNavigationRegisteredTwiceDedupesTest.cs Inspects registrations through MeshServiceProvider.
test/​MeshWeaver.Graph.Test/​EveryHubShutsDownBeforeItsContainerTest.cs Updates shutdown-order comments.
test/​MeshWeaver.Graph.Test/​ARecycleCascadeEnumeratesFromASurvivorTest.cs Updates diagnostic comments and assertion text.
test/​MeshWeaver.Graph.Test/​ActivityConflictSeverityTest.cs Migrates the disposed-container fixture.
test/​MeshWeaver.Fixture/​XUnitFileOutputHelper.cs Updates teardown-logging commentary.
test/​MeshWeaver.Fixture/​TestTeardownExceptionSuppressor.cs Uses centralized disposal classification.
test/​MeshWeaver.Fixture/​TestBase.cs Updates provider-lifetime commentary.
test/​MeshWeaver.Fixture/​ServiceSetup.cs Updates provider-disposal comments.
test/​MeshWeaver.Documentation.Test/​RootedRxConnectionRatchetGuard.cs Updates historical teardown terminology.
test/​MeshWeaver.Documentation.Test/​HubDisposalJoinRatchetGuard.cs Updates scope-disposal terminology.
test/​MeshWeaver.Data.Test/​StreamReleasesItsHubTest.cs Updates retained-provider descriptions.
test/​MeshWeaver.Data.Test/​ReadPathStreamMintingTest.cs Updates per-hub container terminology.
test/​MeshWeaver.Data.Test/​DeadStreamSafetyTest.cs Updates disposal comments.
test/​MeshWeaver.Data.Test/​BuildMustNotConstructHubsTest.cs Updates nested-container commentary.
test/​MeshWeaver.Data.Test/​AutocompleteSettlesOnConvergenceTest.cs Updates disposed-scope commentary.
test/​MeshWeaver.ContentCollections.Test/​ReferenceAsCacheKeyTest.cs Updates synchronization-scope terminology.
test/​MeshWeaver.Compiler.Pipeline.Test/​PlatformReferences.cs Updates historical leak descriptions.
test/​MeshWeaver.Compiler.Pipeline.Test/​ModuleRootServicesSwapTest.cs Moves the retirement trigger to RegistryRead.
test/​MeshWeaver.Compiler.Pipeline.Test/​ModuleLiveUpdateGuardTest.cs Removes the Autofac-specific cache guard.
test/​MeshWeaver.Compiler.Pipeline.Test/​MeshWeaver.Compiler.Pipeline.Test.csproj Removes the Autofac dependency.
test/​MeshWeaver.Compiler.Pipeline.Test/​AWithdrawnModuleIsAbsentFromTheSweepTest.cs Renames the host test and updates comments.
test/​MeshWeaver.Compiler.Pipeline.Test/​AnotherModulesScopedServiceIsTheCallersTest.cs Resolves callers as MeshServiceProvider.
test/​MeshWeaver.Compiler.Pipeline.Test/​AModuleOverrideHidesTheRootRegistrationTest.cs Adapts the type/key registration predicate.
test/​MeshWeaver.Compiler.Pipeline.Test/​AModuleNodeValidatorIsNeverBuiltForNoCallerTest.cs Migrates caller-scope introspection.
test/​MeshWeaver.Compiler.Pipeline.Test/​AModuleForwarderOwnsNothingItReturnsTest.cs Updates provider identity assertions.
test/​MeshWeaver.Compiler.Pipeline.Test/​AModuleConstructionFaultStaysLocalTest.cs Creates caller scopes through BeginScope.
test/​MeshWeaver.Compiler.Pipeline.Test/​ACreateNeverWaitsOnAModuleContainerUnderConstructionTest.cs Marks scope-wide locking as historical.
test/​MeshWeaver.Compiler.Pipeline.Test/​ACallersModuleScopeEndsWithTheGenerationTest.cs Migrates caller-scope extraction.
test/​Memex.Portal.Shared.Test/​PlatformReferences.cs Updates historical leak descriptions.
test/​HubDisposalJoinSites.allow Updates explanatory scope terminology.
src/​MeshWeaver.ServiceProvider/​README.md Documents the replacement container and design reference.
src/​MeshWeaver.ServiceProvider/​MeshWeaver.ServiceProvider.csproj Removes Autofac packages and exposes internals to tests.
src/​MeshWeaver.ServiceProvider/​IServiceRegistrationSource.cs Adds source and activation-observer contracts; caching documentation nit noted above.
src/​MeshWeaver.Messaging.Hub/​Serialization/​TypeRegistry.cs Generalizes collectible-cache comments.
src/​MeshWeaver.Messaging.Hub/​ScopeTeardown.cs Updates disposed-scope probe comments.
src/​MeshWeaver.Messaging.Hub/​OwnedConnectionExtensions.cs Updates scope-teardown terminology.
src/​MeshWeaver.Messaging.Hub/​MessageService.cs Updates disposal and retention comments.
src/​MeshWeaver.Messaging.Hub/​MessageHubConfiguration.cs Updates provider and ownership comments.
src/​MeshWeaver.Messaging.Hub/​MessageHub.cs Updates construction and scope-disposal commentary.
src/​MeshWeaver.Messaging.Hub/​HubDisposalJoin.cs Updates teardown terminology.
src/​MeshWeaver.Messaging.Contract/​IReleasesRemoteState.cs Updates retained-scope documentation.
src/​MeshWeaver.Messaging.Contract/​IPooledSubscribeScheduler.cs Updates scope-disposal documentation.
src/​MeshWeaver.Messaging.Contract/​IHubScopeDisposalSequencer.cs Revises scope-ownership commentary.
src/​MeshWeaver.Mesh.Contract/​Threading/​TeardownOrderedScopeDisposal.cs Documents untracked children and explicit disposal.
src/​MeshWeaver.Mesh.Contract/​Threading/​JsonMemberAccessorCacheEviction.cs Distinguishes JSON caching from replacement DI.
src/​MeshWeaver.Mesh.Contract/​Threading/​IoPoolRegistry.cs Updates scope-disposal commentary.
src/​MeshWeaver.Mesh.Contract/​Threading/​IoPoolOptions.cs Updates pooled-work teardown comments.
src/​MeshWeaver.Mesh.Contract/​Threading/​IoPool.cs Updates hub-scope construction comments.
src/​MeshWeaver.Mesh.Contract/​Threading/​IIoPool.cs Updates scope-creation documentation.
src/​MeshWeaver.Mesh.Contract/​Security/​PermissionEvaluator.cs Updates disposed-container commentary.
src/​MeshWeaver.Mesh.Contract/​ModuleServiceForwarders.cs Migrates proxy and options-forwarder caller types.
src/​MeshWeaver.Mesh.Contract/​ModuleLoadContext.cs Removes Autofac reflection-cache eviction.
src/​MeshWeaver.Mesh.Contract/​MeshTeardownExtensions.cs Updates teardown documentation.
src/​MeshWeaver.Mesh.Contract/​MeshBuilder.cs Registers the replacement module lookup source.
src/​MeshWeaver.Mesh.Contract/​IsolatedModuleHostedService.cs Updates container-resolution commentary.
src/​MeshWeaver.Mesh.Contract/​InstalledModules.cs Updates host/container compatibility comments.
src/​MeshWeaver.Mesh.Contract/​ActivityTracker.cs Updates pooled scope-construction comments.
src/​MeshWeaver.Layout/​Composition/​LayoutDefinition.cs Updates localization-scope commentary.
src/​MeshWeaver.Layout/​Composition/​LayoutAreaHost.cs Updates render-teardown comments.
src/​MeshWeaver.Layout/​AreaErrorClassifier.cs Updates disposal-classification comments.
src/​MeshWeaver.Hosting/​Security/​AccessControlPipeline.cs Centralizes disposed-container classification.
src/​MeshWeaver.Hosting/​Persistence/​Query/​StorageAdapterMeshQueryProvider.cs Updates dependency-cycle terminology.
src/​MeshWeaver.Hosting/​Persistence/​Query/​MeshQuery.cs Updates pooled-subscription commentary.
src/​MeshWeaver.Hosting/​Persistence/​PersistenceExtensions.cs Updates hierarchical-resolution documentation.
src/​MeshWeaver.Hosting/​MeshTeardownHostedService.cs Updates root-container commentary.
src/​MeshWeaver.Hosting/​MeshNodeStreamCache.cs Updates scope-teardown commentary.
src/​MeshWeaver.Hosting/​MeshHostApplicationBuilder.cs Updates host-shutdown comments.
src/​MeshWeaver.Hosting.Orleans/​RoutingQuiescence.cs Updates root-container terminology.
src/​MeshWeaver.Hosting.Orleans/​RoutingGrain.cs Updates disposed-container diagnostic comments.
src/​MeshWeaver.Hosting.Orleans/​OrleansServerRegistryExtensions.cs Updates routing-teardown comments.
src/​MeshWeaver.Hosting.Orleans/​MessageHubGrain.cs Updates disposal-classification commentary.
src/​MeshWeaver.Graph/​MeshDataSource.cs Updates scope-retention and disposal comments.
src/​MeshWeaver.Graph/​Configuration/​NodeTypeRecycleCascade.cs Updates disposed-provider diagnostic comments.
src/​MeshWeaver.Graph.Contract/​SyncedQueryMeshNodes.cs Updates dependency-cycle terminology.
src/​MeshWeaver.Graph.Contract/​SyncedQueryDataSourceExtensions.cs Updates deferred-resolution commentary.
src/​MeshWeaver.Documentation/​Data/​WhatsNew/​2026-09-04-a-pod-shutting-down-is-no-longer-an-error.md Updates the existing entry’s container terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​WriteVerdictTotality.md Updates disposal and historical locking descriptions.
src/​MeshWeaver.Documentation/​Data/​Architecture/​WorkspaceReferences.md Updates scope-cost terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​TestStateIsolation.md Updates teardown terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​SyncHubPopulation.md Distinguishes historical container measurements.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ReadPathStreamMinting.md Updates retained-scope terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ReadingTheMemoryWatchdog.md Annotates historical container diagnostics.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ReadingASiloEviction.md Marks scope-wide locking as historical.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ReadingARecurrenceReopen.md Updates disposed-scope terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​OrleansTaskScheduler.md Updates scope-teardown terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​NodeTypeCompilation.md Updates straggler-filter terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​MeshLifecycle.md Names the replacement provider in lifecycle guidance.
src/​MeshWeaver.Documentation/​Data/​Architecture/​LogWatchTriage.md Updates the container-disposal incident description.
src/​MeshWeaver.Documentation/​Data/​Architecture/​HubDisposalModel.md Updates scope-disposal terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​EnumeratingFromASurvivor.md Updates closed-scope diagnostic descriptions.
src/​MeshWeaver.Documentation/​Data/​Architecture/​DocumentParts.md Annotates historical allocation evidence.
src/​MeshWeaver.Documentation/​Data/​Architecture/​DisposedScopeAndDyingHubs.md Adds a replacement-container history note.
src/​MeshWeaver.Documentation/​Data/​Architecture/​DisposalStallVerdicts.md Annotates historical container allocation evidence.
src/​MeshWeaver.Documentation/​Data/​Architecture/​DebuggingNativeCrashes.md Adds a container-replacement history note.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ControlledIoPooling.md Updates scope-teardown terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​AReleaseRefusedAtTheSource.md Updates scope-retention descriptions.
src/​MeshWeaver.Documentation/​Data/​Architecture/​AReferenceThatCannotBeAKey.md Updates scope-cost terminology.
src/​MeshWeaver.Documentation/​Data/​Architecture/​ActivityMirrorReleaseLifetime.md Updates disposed-container terminology.
src/​MeshWeaver.Data/​Workspace.cs Updates synchronization-scope commentary.
src/​MeshWeaver.Data/​Serialization/​JsonSynchronizationStream.cs Updates disposal-diagnostic comments.
src/​MeshWeaver.Data/​ISynchronizationStream.cs Updates scope-cost documentation.
src/​MeshWeaver.Data/​GenericUnpartitionedDataSource.cs Updates nested-container commentary.
src/​MeshWeaver.Data/​DataExtensions.cs Updates teardown and read-failure comments.
src/​MeshWeaver.Data.Contract/​Messages.cs Updates subscription-retention documentation.
src/​MeshWeaver.ContentCollections/​ContentCollectionReference.cs Updates synchronization-scope terminology.
src/​MeshWeaver.Connection.Orleans/​IoPoolSiloTeardown.cs Updates startup-abort disposal comments.
src/​MeshWeaver.Compiler.Pipeline/​NodeTypeCompilationHelpers.cs Updates scope terminology.
src/​MeshWeaver.Compiler.Pipeline/​MeshNodeCompilationService.cs Updates disposed-container commentary.
src/​MeshWeaver.Compiler.Pipeline/​CompilationCacheService.cs Removes Autofac eviction hooks and updates documentation.
Directory.Packages.props Removes central Autofac package versions.
.github/​workflows/​dotnet-test.yml Generalizes a memory-diagnostic comment.
.claude/​skills/​sigsegv/​SKILL.md Updates historical container and cache guidance.

Review effort: Balanced

Comment on lines +33 to 36
/// <item>An unclosed child scope <b>keeps everything it built</b> until the parent
/// itself is disposed. A portal that activates and recycles hubs for hours therefore accumulates
/// one undisposed scope per hub, with everything each one holds — which is why a disposable-mesh
/// e2e runner reaches ~7 GB of 7 GB and starts taking 20-second GC pauses.</item>

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.

Correct, and thanks. Item 2 describes the Autofac container this PR removes, where a parent lifetime scope tracked every child it began and so kept each unclosed hub scope alive until the parent itself was disposed. MeshServiceProvider does not track children (its own ownership paragraph says so: a scope nobody closes is garbage once nobody holds it), so under the replacement an unclosed hub scope is not retained by the parent and is not cleaned up by it either: its disposables are simply never disposed, finalizer-only.

The requirement this test pins is unchanged by that difference: each hub scope must be closed explicitly when its hub is disposed (HostedHubsCollection.CloseScopeWhenDisposed), because nothing else will dispose what it built. The assertion is exactly that, and it holds on both containers.

This is a wording correction in a test's XML comment and changes no behaviour, so per the round-5 rule I am not starting another review round for it alone. It will be rewritten as "historically (Autofac) retained until the parent was disposed; now never disposed at all unless closed" in the next push this PR needs for a real defect.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    31 files  ± 0      31 suites  ±0   1h 7m 46s ⏱️ - 1m 55s
14 697 tests +15  14 500 ✅ +15  197 💤 ±0  0 ❌ ±0 
14 727 runs  +15  14 530 ✅ +15  197 💤 ±0  0 ❌ ±0 

Results for commit 4a55e79. ± Comparison against base commit 0bc587e.

This pull request removes 81 and adds 55 tests. Note that renamed tests count towards both.

   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---)
   --- End of inner exception stack trace ---, expected: True)
   --- End of inner exception stack trace ---, isDenial: True)
   --- End of inner exception stack trace ---, kind: "Timeout", layer: null)
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
 ---> (Inner Exception #1) MeshWeaver.Messaging.HubDisposingException: Hub node/a is shutting down — cannot create "x". The address may reactivate (recycle / restart); retry to get the authoritative answer.
 ---> (Inner Exception #1) System.ArgumentException: Value does not fall within the expected range.<---
…
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "3dcfa609-058a-4403-b2cc-6e4d2cf4067f")
Memex.Portal.Shared.Test.PublishStorageFaultIsNotAConflictTest ‑ AStorageFault_IsA500_NamingTheError(fault: System.AggregateException: One or more errors occurred. (Access to the path '/data/modules/activation.d/MeshWeaver.Markdown.Export.json' is denied.)
 ---> System.UnauthorizedAccessException: Access to the path '/data/modules/activation.d/MeshWeaver.Markdown.Export.json' is denied.
   --- End of inner exception stack trace ---)
Memex.Portal.Shared.Test.PublishStorageFaultIsNotAConflictTest ‑ AStorageFault_IsA500_NamingTheError(fault: System.InvalidOperationException: landing faulted
 ---> System.UnauthorizedAccessException: Access to the path '/data/modules/activation.d/MeshWeaver.Markdown.Export.json' is denied.
   --- End of inner exception stack trace ---)
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
 ---> System.UnauthorizedAccessException: Access denied
   --- End of inner exception stack trace ---, isDenial: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall as an aggregate's FIRST member", fault: System.AggregateException: One or more errors occurred. (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall as an aggregate's SECOND member", fault: System.AggregateException: One or more errors occurred. (The operation has timed out.) (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> System.TimeoutException: The operation has timed out.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---
, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall nested in an aggregate inside a wrapper", fault: System.InvalidOperationException: outer
 ---> System.AggregateException: One or more errors occurred. (Value does not fall within the expected range.) (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> System.ArgumentException: Value does not fall within the expected range.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---

   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall wrapped as InnerException", fault: System.InvalidOperationException: outer
 ---> MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "an aggregate of unrelated faults", fault: System.AggregateException: One or more errors occurred. (The operation has timed out.) (Value does not fall within the expected range.)
 ---> System.TimeoutException: The operation has timed out.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.ArgumentException: Value does not fall within the expected range.<---
, expected: False)
MeshWeaver.Graph.Test.ACollectibleTypeIsNeverHeldByTheRootTest ‑ NegativeControl_AClosureResolvedAtTheRoot_IsRetainedByTheRoot
…

♻️ This comment has been updated with latest results.

@rbuergi
rbuergi marked this pull request as ready for review October 25, 2026 06:03
Copilot AI balanced review requested due to automatic review settings October 25, 2026 06:31

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.

🔵 Needs a closer look

The cross-cutting DI replacement requires final human review, particularly because the supplied review scope excludes the container’s main implementation.

1 open issue

Review effort: Balanced

#7147)

NotOwnedServiceDescriptorExtensions (its PopulateMeshWeaver takes an Autofac
ContainerBuilder) and ReflectionCacheEviction (it purged Autofac's reflection caches)
are deleted with Autofac, not moved. No forwarder can keep them without keeping
Autofac, and no fleet repository names them, so they are recorded as transitional
allow entries naming this pull request. check-type-forwards.py passes locally
against merge base 19ac9a0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 25, 2026 06:44

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.

🔵 Needs a closer look

The cross-cutting DI and lifetime migration requires final human review; five non-blocking documentation nits remain.

1 open issue

Review effort: Balanced

rbuergi and others added 3 commits October 25, 2026 07:57
The disposed-container match now lives once, in FaultClassifier (#7117), keyed on
MeshServiceProvider's object name and phrase; HubDisposingException forwards to it.
Main's new FaultClassifier tests are ported from the Autofac exception shape to the
container's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… forwarder onto FaultClassifier

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.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.

🔵 Needs a closer look

The framework-wide DI replacement affects resolution, ownership, disposal, and live-module lifetimes, warranting final human approval despite only non-blocking nits.

1 open issue

Review effort: Balanced

rbuergi added a commit that referenced this pull request Oct 25, 2026
…ister, not that it ran on another thread id (#7164)

TheWaitedType_IsStillDelivered_OffTheRegisteringThread compared the delivery's managed
thread id to the test method's. The delivery is a pool work item and the test's thread is a
pool thread: once the test awaits, that thread returns to the pool and can run the delivery,
which KEEPS the contract and failed the assertion (seen on #7147's local Hub.Test run: 730/731).
The test now marks the window in which Register is on the stack and asks whether the delivery
ran inside it. Negative control: dropping the registry's ObserveOn hop (inline delivery) turns
it red.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 25, 2026
…nt (#7163)

* docs(heap): rank the next per-hub costs after the container replacement

Same harness as #7147 with a dump before and after 99 activations, ownership by
a ClrMD nearest-owner walk: 145 KB per hub, of which type registry 44.5 KB,
messaging 37.7 KB, serialization 34.2 KB. Cross-cutting: ConcurrentDictionary
skeletons are 45.3 KB per hub (29.6 per hub, 60% empty, per-processor locks).
Ranks six candidates with fix shapes and estimates, corrects the reading of
AsyncDelivery (a handler delegate, not a queued message), and explains the two
allocation-churn sources (change-feed observer-array copies, PE header reads).

Refs #7082
Refs #7145

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

* docs(heap): constraints on the key-cell replacement, non-additive estimates, opt-in exact-path feed channel (review of #7163)

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi merged commit 2cc600e into main Oct 25, 2026
42 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