Skip to content

Fix CultureNotFoundException in ProjectionGraph.IsAssemblyKnownToHaveNoEvolvers under invariant-globalization mode#200

Merged
jeremydmiller merged 1 commit intomainfrom
fix-projectiongraph-culture-error
Apr 30, 2026
Merged

Fix CultureNotFoundException in ProjectionGraph.IsAssemblyKnownToHaveNoEvolvers under invariant-globalization mode#200
jeremydmiller merged 1 commit intomainfrom
fix-projectiongraph-culture-error

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Problem

Assembly.GetName().Name, used by the cold-start prefilter in DiscoverGeneratedEvolvers, walks AssemblyName.GetLocale()CultureInfo.GetCultureInfo(name) under the hood. Under globalization-invariant mode (<InvariantGlobalization>true</InvariantGlobalization> — implied by <PublishAot>true</PublishAot>) any culture-tagged satellite assembly (e.g. pt-br) makes that lookup throw CultureNotFoundException. The result for downstream consumers is a crash on DocumentStore..ctor (or the equivalent host-build entry point):

System.Globalization.CultureNotFoundException: Only the invariant culture is supported in globalization-invariant mode.
See https://aka.ms/GlobalizationInvariantMode for more information. (Parameter 'name') pt-br is an invalid culture identifier.
   at CultureInfo System.Globalization.CultureInfo.GetCultureInfo(string name)
   at CultureInfo System.Reflection.RuntimeAssembly.GetLocale()
   at AssemblyName System.Reflection.RuntimeAssembly.GetName(bool copiedName)
   at bool JasperFx.Events.Projections.ProjectionGraph.IsAssemblyKnownToHaveNoEvolvers(Assembly assembly)
   at void JasperFx.Events.Projections.ProjectionGraph.DiscoverGeneratedEvolvers(params Assembly[] assemblies)

Reported today against Marten's cold-start optimization changes.

Fix

Use Assembly.FullName and split on the first comma to get the simple name. FullName is pre-cached on the runtime assembly and doesn't traverse CultureInfo. Wrap in try/catch as a belt-and-braces guard for exotic loader behaviour — failing to recognise an assembly as "known empty" just falls through to the existing GetCustomAttributes path that has its own try/catch.

Test plan

  • dotnet build src/JasperFx.Events/JasperFx.Events.csproj clean
  • A precise unit-level repro requires <InvariantGlobalization>true</InvariantGlobalization> plus a loaded satellite assembly, which is hard to wire up in a portable test rig — covered instead by the existing prefilter tests + this fix's defensive try/catch fallback. Will follow up if anyone has a clean way to harness the invariant-mode case.

🤖 Generated with Claude Code

…NoEvolvers

The cold-start optimization in DiscoverGeneratedEvolvers calls
`assembly.GetName().Name` to do a cheap string-prefix check before paying
for the GetCustomAttributes reflection on framework assemblies.

`Assembly.GetName()` walks `AssemblyName.GetLocale()` under the hood, which
calls `CultureInfo.GetCultureInfo(name)`. Under globalization-invariant
mode (`<InvariantGlobalization>true</InvariantGlobalization>` — implied
by NativeAOT publishing) that lookup throws
`CultureNotFoundException` for any culture-tagged satellite assembly such
as `pt-br`. This crashes Marten's `DocumentStore..ctor` at host build
time on AOT-prepped apps that happen to have a localized assembly loaded.

Use `Assembly.FullName` and split on the first comma to get the simple
name. FullName is pre-cached on the runtime assembly and doesn't go
through CultureInfo. Wrap in try/catch as a belt-and-braces guard for
exotic loader behaviour; failing to recognise an assembly as
"known empty" just falls through to the GetCustomAttributes path that
already has its own try/catch.

Reported on Marten today against the cold-start optimization changes.
See https://aka.ms/GlobalizationInvariantMode.
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.

1 participant