Skip to content

ApiExtensibility.Register keeps the first registration forever, pinning the first ALC generation and ignoring later ones #24922

Description

@MartinZikmund

Current behavior 🐛

ApiExtensibility.Register / Register<TOwner> is first-writer-wins. After #23819 it silently ignores any registration for a contract type that is already present. There is no unregister path, and nothing purges entries when a collectible AssemblyLoadContext unloads.

The registry lives in Uno.Foundation, which is shared by the host and secondary-ALC apps (default ALC). It is a single process-lifetime static dictionary.

Consequences when a secondary app is loaded, unloaded and loaded again (a rebuilt assembly, a "restart" of a hosted app):

  1. Stale provider: the new generation calls Register for the same contract type. It is a no-op, so the new generation keeps using the first generation's builder, not its own.
  2. Leak: if the first registrant was a secondary-ALC app, its builder delegate and anything it captures (a MethodInfo, a Type, an instance) stay rooted by the static dictionary. That keeps the collectible ALC from ever being collected, even after Unload() and dropping every other reference.

Expected behavior 🎯

A provider registered from a collectible ALC should be removed (or replaced) when that ALC unloads, and a later generation should be able to register its own provider. The registry should not keep an unloaded ALC alive.

How to reproduce it (as minimally and precisely as possible) 🔬

Confirmed by code inspection only (no runtime repro yet). Permalinks are at commit 7a22141.

  1. Look at ApiExtensibility.Register and Register<TOwner>: TryAdd / ContainsKey then return. The type has no Unregister or removal method, and no AssemblyLoadContext.Unloading hook.
  2. Scenario to reproduce at runtime:
    • A host (default ALC) hosts a secondary Uno app in a collectible ALC that shares Uno.Foundation.
    • The secondary app (first registrant of a contract type, e.g. via [ApiExtension]-generated code) calls ApiExtensibility.Register(typeof(IFoo), builder) where builder closes over something from the secondary ALC.
    • Unload the ALC, load a rebuilt version of the app into a new ALC, and let it register IFoo again.
    • ApiExtensibility.CreateInstance<IFoo> still runs the first generation's builder, and a weak reference to the first ALC never dies after GC.

Workaround 🛠️

None. A host that repeatedly loads and unloads the same secondary app leaks one ALC generation per pinned contract type.

Works on UWP/WinUI

N/A (Uno-specific hosting scenario).

Renderer 🎨

Skia

Affected platforms 📱💻🖥️

Skia hosts that run secondary-ALC apps (Desktop).

Uno.Sdk version (and other relevant versions) 📦

master @ 7a22141

Anything else we need to know? 💬

Activity

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

    area/skia/lifecycle ✏️difficulty/medium 🤔Categorizes an issue for which the difficulty level is reachable with a good understanding of WinUIkind/bugSomething isn't workingplatform/allCategorizes an issue or PR as relevant to the all platformstriage/untriagedIndicates an issue requires triaging or verification

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions