Repository navigation
feat(modules): every module runs in its own collectible load context (live update, slice 1) - #6121
Conversation
…(live update, slice 1) Policy module-live-update-default: a module update goes live in the running process by default. This slice lands the foundation — the load contexts: - ModuleLoadContext: collectible context per module generation; platform first (one copy of every platform contract), then another module's current generation (dependency edges recorded), then the generation's own directory; natives via the ModuleNativeAssets candidates; IPlatformLoadContext; Autofac + STJ cache eviction. - ModuleContexts: the mesh's registry — Load / Commit / Retire (unload on a positive quiescence signal, tracked on CollectibleContextUnloads), Resolve, DependentsOf; disposed with the mesh. - MeshBuilder.InstallModules loads every module the image does not bind (TRUSTED_PLATFORM_ASSEMBLIES) into its own context and commits it only after its contributions materialised; a failed generation is unloaded and never takes the name, so the previous generation / image copy stay reachable. - NodeType contexts and kernel script sessions bind modules through ModuleContexts.Resolve (the current generation). - JsonMemberAccessorCacheEviction moves to MeshWeaver.Mesh.Contract (public) so a module context can use it. Doc: Architecture/LiveModuleUpdate; policy row module-live-update-default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Test Results 17 files 17 suites 42m 4s ⏱️ Results for commit 90a54d5. ♻️ This comment has been updated with latest results. |
Test Results (shard 0) 3 files 3 suites 3m 43s ⏱️ Results for commit 90a54d5. |
Test Results (shard 2) 3 files 3 suites 3m 23s ⏱️ Results for commit 90a54d5. |
Test Results (shard 3)1 629 tests 1 629 ✅ 6m 34s ⏱️ Results for commit 90a54d5. |
Test Results (shard 5) 2 files 2 suites 7m 9s ⏱️ Results for commit 90a54d5. |
Test Results (shard 1) 3 files 3 suites 9m 38s ⏱️ Results for commit 90a54d5. |
Test Results (shard 4) 4 files 4 suites 11m 35s ⏱️ Results for commit 90a54d5. |
Live module update — slice 1: every module runs in its own collectible load context
Policy
module-live-update-default(new register row): a module update goes live in the running process by default; a restart only when a module declaresrestartRequiredor the live swap fails at runtime (then automatic, no approval). Manual:Doc/Architecture/LiveModuleUpdate(new).This slice is the foundation — the contexts. It does not yet change the update path (the swap + its runtime fallback is slice 2; the Plugins module classification /
restartRequireddeclarations / guard is slice 3).What changes
ModuleLoadContext— collectible, one per module generation (module:<Name>#n). Resolution: platform first (any name the default context can bind — one copy of every platform contract; a bundled platform copy is never used) → another module's current generation (edge recorded asDependsOn) → the generation's own directory. Natives via theModuleNativeAssetscandidates.IPlatformLoadContext(impersonation guard treats module code as platform). Purges Autofac + System.Text.Json static caches onUnloading.ModuleContexts— the mesh's registry singleton:Load/Commit/Retire(unload on a positiveAlcLeaseRegistryquiescence signal, never a timer; tracked onCollectibleContextUnloads),Resolve,DependentsOf; disposed with the mesh.MeshBuilder.InstallModules— every module not inTRUSTED_PLATFORM_ASSEMBLIESloads into its own context and is committed only after its contributions materialise. A failed generation is unloaded and never takes the name, so the previous generation / image copy remain reachable (If no module version loads on this platform, the instance keeps running the previous generation #3649/A refused module generation is loaded anyway when it has no previous generation — the image baseline is never used as the fallback, and nothing in the log says so #3735). Image-bound modules load exactly as before.NodeAssemblyLoadContextand the kernelScriptSessionbind modules throughModuleContexts.Resolve(current generation) — noDefault.Resolvinghandler ever hands out a module (it would pin the first generation forever).JsonMemberAccessorCacheEvictionmoves (internal → public) toMeshWeaver.Mesh.Contract.🚨 Behaviour change behind an unchanged signature (shape 7)
On the portal, every landed / seeded module that is not in the image's TPA — notably the
MeshModuleClosureseedsMeshWeaver.AI,MeshWeaver.Blazor.Chat,MeshWeaver.Markdown.Collaboration,MeshWeaver.Mcp— now runs in a collectible context instead of the default one. Three existing tests inConfiguredModuleActivationTestpinned default-context-only limitations and were rewritten to the new truth (a substitution can no longer happen to a context-loaded module; a generation that loaded-then-failed now falls back to the image copy instead of stranding the module).Not established here: a portal-level boot (Plugins
Memex.Portal.*) with modules in their own contexts — Blazor component types and Orleans payloads from a collectible context were not exercised. Recycle after deploy: none needed for this slice beyond the normal roll (load path changes at boot).Tests (local, Release)
ModulesRunInTheirOwnContextTest(new) — 7/7: own collectible context + one platform; image-bound classifier; two generations coexist and the retired one is really collected; negative control: a held instance → reported RETAINED by context name (this control caught a first sentinel that read "collected" the moment the unload started); dependent binds its dependency's current generation and is recorded; a generation whose contributions throw leaves the serving one current; a NodeType compiled against N+1's member fails on N (MissingMember — the 2026-10-05 incident shape) and binds once N+1 is current.MeshWeaver.Compiler.Pipeline.Testfull run before the rewrite: 1192/1195 (the 3 rewritten); after:ConfiguredModuleActivationTest+ new class 51/51.Memex.Portal.Shared.Test(Module* / ConfigurationHandOver / SelfUpdatePendingRestart): 386/386.MeshWeaver.Graph.Test(Teardown / Collectible / Module): 95/95.-warnaserror: Mesh.Contract, Kernel.Hub, Compiler.Pipeline, Documentation, Compiler.Pipeline.Test, Graph.Test, Memex.Portal.Shared.Test — 0 warnings, 0 errors.Pairs-with: none — no public type or member is removed (
JsonMemberAccessorCacheEvictionwas internal).Implementers: none — no interface member added.
Mirror-sync: none — no i18n key added or changed.
🤖 Generated with Claude Code