fix(plugins): close LA5 host lifecycle review
This commit is contained in:
parent
95f4be94db
commit
fbe9c8a288
25 changed files with 1043 additions and 120 deletions
|
|
@ -27,7 +27,8 @@ public sealed class PluginSession : IDisposable
|
|||
{
|
||||
private readonly IPluginHost _host;
|
||||
private readonly Action<PluginSessionStatus>? _report;
|
||||
private readonly List<LoadedPlugin> _loaded = [];
|
||||
private readonly List<ActivePlugin> _loaded = [];
|
||||
private readonly List<WeakReference> _releasedContexts = [];
|
||||
private bool _started;
|
||||
private bool _disposed;
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ public sealed class PluginSession : IDisposable
|
|||
public int LoadedCount => _loaded.Count;
|
||||
|
||||
public IReadOnlyList<string> LoadedPluginIds =>
|
||||
_loaded.Select(static plugin => plugin.Manifest.Id).ToArray();
|
||||
_loaded.Select(static active => active.Loaded.Manifest.Id).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Discovers and starts the configured set exactly once. A
|
||||
|
|
@ -143,9 +144,11 @@ public sealed class PluginSession : IDisposable
|
|||
/// owned by this session. The returned weak references do not delay unload.
|
||||
/// </summary>
|
||||
public IReadOnlyList<WeakReference> CaptureLoadContextWeakReferences() =>
|
||||
_loaded
|
||||
.Select(static plugin => new WeakReference(plugin.LoadContext!))
|
||||
.ToArray();
|
||||
[
|
||||
.. _releasedContexts,
|
||||
.. _loaded.Select(static active =>
|
||||
new WeakReference(active.Loaded.LoadContext!)),
|
||||
];
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -155,7 +158,8 @@ public sealed class PluginSession : IDisposable
|
|||
|
||||
for (int index = _loaded.Count - 1; index >= 0; index--)
|
||||
{
|
||||
LoadedPlugin loaded = _loaded[index];
|
||||
ActivePlugin active = _loaded[index];
|
||||
LoadedPlugin loaded = active.Loaded;
|
||||
try
|
||||
{
|
||||
loaded.Plugin!.Disable();
|
||||
|
|
@ -169,6 +173,11 @@ public sealed class PluginSession : IDisposable
|
|||
error);
|
||||
}
|
||||
|
||||
// Host-owned registrations are released even when Disable throws.
|
||||
// This must precede ALC unload so no UI binding or event delegate
|
||||
// can keep the plugin assembly reachable.
|
||||
active.Scope.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
loaded.LoadContext!.Unload();
|
||||
|
|
@ -198,12 +207,16 @@ public sealed class PluginSession : IDisposable
|
|||
{
|
||||
foreach (PluginDiscoveryResult candidate in available)
|
||||
{
|
||||
var scope = new ScopedPluginHost(_host);
|
||||
LoadedPlugin loaded = PluginLoader.Load(
|
||||
candidate.PluginDirectory,
|
||||
candidate.Manifest!,
|
||||
_host);
|
||||
scope);
|
||||
if (!loaded.Success)
|
||||
{
|
||||
scope.Dispose();
|
||||
if (loaded.ReleasedLoadContext is { } released)
|
||||
_releasedContexts.Add(released);
|
||||
AddError(
|
||||
errors,
|
||||
id,
|
||||
|
|
@ -215,7 +228,7 @@ public sealed class PluginSession : IDisposable
|
|||
try
|
||||
{
|
||||
loaded.Plugin!.Enable();
|
||||
_loaded.Add(loaded);
|
||||
_loaded.Add(new ActivePlugin(loaded, scope));
|
||||
SafeLog(
|
||||
static (log, message, _) => log.Info(message),
|
||||
$"plugin loaded: {loaded.Manifest.Id} "
|
||||
|
|
@ -229,7 +242,7 @@ public sealed class PluginSession : IDisposable
|
|||
catch (Exception error)
|
||||
{
|
||||
AddError(errors, id, error);
|
||||
ReleaseFailedEnable(loaded);
|
||||
ReleaseFailedEnable(loaded, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,7 +270,9 @@ public sealed class PluginSession : IDisposable
|
|||
null);
|
||||
}
|
||||
|
||||
private void ReleaseFailedEnable(LoadedPlugin loaded)
|
||||
private void ReleaseFailedEnable(
|
||||
LoadedPlugin loaded,
|
||||
ScopedPluginHost scope)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -272,6 +287,9 @@ public sealed class PluginSession : IDisposable
|
|||
error);
|
||||
}
|
||||
|
||||
scope.Dispose();
|
||||
|
||||
_releasedContexts.Add(new WeakReference(loaded.LoadContext!));
|
||||
try
|
||||
{
|
||||
loaded.LoadContext!.Unload();
|
||||
|
|
@ -358,4 +376,8 @@ public sealed class PluginSession : IDisposable
|
|||
or ArgumentException
|
||||
or NotSupportedException
|
||||
or System.Security.SecurityException;
|
||||
|
||||
private sealed record ActivePlugin(
|
||||
LoadedPlugin Loaded,
|
||||
ScopedPluginHost Scope);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue