You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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):
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.
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.
Look at ApiExtensibility.Register and Register<TOwner>: TryAdd / ContainsKey then return. The type has no Unregister or removal method, and no AssemblyLoadContext.Unloading hook.
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.
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 collectibleAssemblyLoadContextunloads.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):
Registerfor the same contract type. It is a no-op, so the new generation keeps using the first generation's builder, not its own.MethodInfo, aType, an instance) stay rooted by the static dictionary. That keeps the collectible ALC from ever being collected, even afterUnload()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.
ApiExtensibility.RegisterandRegister<TOwner>:TryAdd/ContainsKeythen return. The type has noUnregisteror removal method, and noAssemblyLoadContext.Unloadinghook.Uno.Foundation.[ApiExtension]-generated code) callsApiExtensibility.Register(typeof(IFoo), builder)wherebuildercloses over something from the secondary ALC.IFooagain.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? 💬
nullonce a second ALC has loaded the owner assembly #24581, AlcContentHost: hosted Uno.Themes content still resolves the host's styles and brushes #24846. None of them touchApiExtensibility. Related teardown leak: Win32: closed windows stay rooted by_hwndToWrapperand an unrevokedRegisterDragDrop, so they (and collectible ALCs) never get collected #24920.Unloading; the default-ALC registration stays the fallback.