Repository navigation
Pinned module generations break static-asset discovery — every store-landed view pack renders unstyled (#2509 × #1724) #2562
Description
Summary
Since #2509 pins a store-landed module generation into process-local storage before loading it,
MeshModuleStaticAssetExtensions.Discover no longer recognises the loaded assembly's directory —
so it mounts nothing, and every static asset the module ships 404s. The module itself loads and its
components render; only their CSS and JS are unreachable, so the portal comes up unstyled.
The two changes are individually correct. ModuleWwwrootPath decides whether to trust the
assembly's own directory by asking whether its grandparent folder is literally named modules,
and the pin root is not.
Impact
Any deployment that installs a module carrying static web assets. Since #2169 Phase B2,
DefaultViews (MeshWeaver.Blazor.Views) and GraphViews (MeshWeaver.Blazor.Graph) reach a
deployment only as registry bundles, so they are landed modules everywhere — which makes the
default control views the affected case, i.e. the whole portal chrome.
It fails silently and in the worst possible shape: the assemblies load, the views render, the log
says nothing, and the only symptom is that the page looks broken.
Root cause
src/MeshWeaver.Hosting.AspNetCore/MeshModuleStaticAssetExtensions.cs:
var parent = Path.GetFileName(Path.GetDirectoryName(assemblyDirectory));
return string.Equals(parent, "modules", StringComparison.OrdinalIgnoreCase)
? Path.Combine(assemblyDirectory!, "wwwroot") // correct branch
: Path.Combine(baseDirectory, "modules", moduleName, "wwwroot"); // taken insteadPath shapes, before and after #2509:
| assembly directory | grandparent | branch taken | |
|---|---|---|---|
| before | /data/modules/<Name>@<gen>/ |
modules |
assembly directory ✅ |
| after | /tmp/meshweaver-pinned-modules/<n>-<hash>/<Name>@<gen>/ |
<n>-<hash> |
baseDirectory fallback ❌ |
The fallback resolves to /app/modules/<Name>/wwwroot, which does not exist for a store-landed
module — the legacy fixed folder is not written at all any more (#1949). Directory.Exists fails
and the loop hits continue with no log line, so nothing mounts and nothing explains why.
Note the pinned copy does carry the assets: wwwroot is copied along with the DLLs and sits
directly beside the loaded assembly. The bytes are in the right place; only the path test rejects
them.
Evidence (measured on a portal with both packs landed)
[ModuleLoad] MeshWeaver.Blazor.Views ←
/tmp/meshweaver-pinned-modules/<n>-<hash>/MeshWeaver.Blazor.Views@<gen>/MeshWeaver.Blazor.Views.dll
(source=store)
pinned copy: wwwroot present, 254 files, incl. MeshWeaver.Blazor.Views.styles.css (102,617 B)
fallback path: /app/modules/MeshWeaver.Blazor.Views/wwwroot → does not exist
GET /_content/MeshWeaver.Blazor.Views/Components/DocumentSourceView.razor.js → 404
GET /_content/MeshWeaver.Blazor.Views/MeshWeaver.Blazor.Views.modules.json → 404
GET /_content/MeshWeaver.Blazor/standard-page-layout.css → 200 (host RCL, unaffected)
<App>.styles.css (host scoped-CSS aggregate) → 200, but 387 bytes
stylesheets linked by the page → 3, none of them the module's
AddMeshModuleStaticAssets() and UseMeshModuleStaticAssets() are both wired, and
ModuleWwwrootPath is present in the running binary — the mount is active and simply discovers
nothing. MeshModuleStaticAssetExtensions emits zero log lines, which is the silent continue.
How to reproduce
Unit level — the tightest repro; ModuleWwwrootPath is internal and takes baseDirectory
explicitly so the mapping rules can be exercised directly:
ModuleWwwrootPath(
assemblyLocation: "/tmp/meshweaver-pinned-modules/1-a457e6a1/Foo@0badc0de/Foo.dll",
baseDirectory: "/app",
moduleName: "Foo")
// actual: /app/modules/Foo/wwwroot ← does not exist; module is skipped
// expected: /tmp/meshweaver-pinned-modules/1-a457e6a1/Foo@0badc0de/wwwrootEnd to end, on any portal carrying #2509:
- Install a package whose module ships static web assets —
DefaultViewsis the canonical case. - Restart the portal so the module is landed and loaded.
- In the log, confirm the module loads from the pin root:
[ModuleLoad] <Name> ← /tmp/meshweaver-pinned-modules/<n>-<hash>/<Name>@<gen>/<Name>.dll - Request any asset the module ships:
GET /_content/<Name>/<file>→ 404, while the file is
present under that same pinned directory'swwwroot/. - Open the portal: controls render, but unstyled.
Reverting only the pin (loading from the shared modules/ tree) restores the assets, which
isolates the interaction.
Suggested fix
- Stop keying on the folder name. The invariant that test protects is "never mount the app's
own web root through a module namespace" — so test that the assembly directory is not the
app base directory. That is stricter about the actual hazard and blind to where the loader put
the bytes, so it survives the next relocation too. - Log the skip. A module with no resolvable
wwwrootshould say so at Debug, naming the path
it tried. The silentcontinueis why this presents as "the portal looks broken" with nothing in
the log — the same failure modeModuleWwwrootPath's own remarks warn about.
A regression test should pin the pinned-path shape specifically, not just the two shapes that
existed when #1724 was written.
Related
- Module generations GC deletes lazily-loaded dependency DLLs out from under a running process; activation can point at a never-landed generation #2509 — pins a landed generation into process-local storage (the change that exposed this)
- Module static-asset provider: the one blocker keeping the Blazor view packs (Radzen, GoogleMaps, Analysis) out of modules/ and the Store #1724 — module static-asset mounting
- 🚨 Generation-landed modules NEVER load at boot — activation gate checks legacy modules/<name>/ while landing writes modules/<name>@<gen>/ #1949 — landing writes
<Name>@<gen>/generations, not the legacy fixed folder - Extract the GUI implementations from the core repo: RN app + Blazor packs move to MeshWeaver.Plugins #2169 Phase B2 — the view packs leave the image and arrive only as bundles
Not verified
Whether deployed portals are affected. The reasoning says any instance running a build with #2509
and landed view packs would be, but this was measured on a local install only.
Activity
Merge pull request #2577 from Systemorph/fix/2562-pinned-module-stati…