fix(plugins): close LA5 ownership races
This commit is contained in:
parent
fbe9c8a288
commit
f820eb258d
14 changed files with 467 additions and 107 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using AcDream.Plugin.Abstractions;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace AcDream.Plugin.Tests.Fixtures.HostPlugin;
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ public sealed class HostPlugin : IAcDreamPlugin
|
|||
private IPluginHost? _host;
|
||||
private string? _assemblyDirectory;
|
||||
private bool _throwAfterRegistration;
|
||||
private bool _throwDuringInitialize;
|
||||
private int _entitiesSeen;
|
||||
|
||||
public void Initialize(IPluginHost host)
|
||||
|
|
@ -22,17 +24,24 @@ public sealed class HostPlugin : IAcDreamPlugin
|
|||
typeof(HostPlugin).Assembly.Location);
|
||||
_throwAfterRegistration = File.Exists(
|
||||
Path.Combine(_assemblyDirectory!, "throw-after-register"));
|
||||
_throwDuringInitialize = File.Exists(
|
||||
Path.Combine(_assemblyDirectory!, "throw-during-initialize"));
|
||||
host.Log.Info($"fixture-initialized:hasUi={host.HasUi}");
|
||||
if (_throwDuringInitialize)
|
||||
{
|
||||
RegisterHostCallbacks(host);
|
||||
AssemblyLoadContext.GetLoadContext(typeof(HostPlugin).Assembly)!
|
||||
.Unloading += OnUnloading;
|
||||
throw new InvalidOperationException(
|
||||
"fixture initialize failed after registering UI, entity, and selection callbacks");
|
||||
}
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
IPluginHost host = _host
|
||||
?? throw new InvalidOperationException("The fixture was not initialized.");
|
||||
host.Ui.AddMarkupPanel(
|
||||
Path.Combine(AppContext.BaseDirectory, "fixture-panel.xml"),
|
||||
this);
|
||||
host.Events.EntitySpawned += OnEntitySpawned;
|
||||
RegisterHostCallbacks(host);
|
||||
if (_throwAfterRegistration)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -47,24 +56,75 @@ public sealed class HostPlugin : IAcDreamPlugin
|
|||
IPluginHost? host = _host;
|
||||
if (host is null)
|
||||
return;
|
||||
if (_throwAfterRegistration)
|
||||
if (_throwAfterRegistration || _throwDuringInitialize)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"fixture disable intentionally refuses cleanup");
|
||||
}
|
||||
host.Events.EntitySpawned -= OnEntitySpawned;
|
||||
host.Selection.Changed -= OnSelectionChanged;
|
||||
host.Log.Info($"fixture-disabled:entitiesSeen={_entitiesSeen}");
|
||||
_host = null;
|
||||
}
|
||||
|
||||
private void RegisterHostCallbacks(IPluginHost host)
|
||||
{
|
||||
host.Ui.AddMarkupPanel(
|
||||
Path.Combine(AppContext.BaseDirectory, "fixture-panel.xml"),
|
||||
this);
|
||||
host.Events.EntitySpawned += OnEntitySpawned;
|
||||
host.Selection.Changed += OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnEntitySpawned(WorldEntitySnapshot snapshot)
|
||||
{
|
||||
_entitiesSeen++;
|
||||
if (_throwAfterRegistration && _assemblyDirectory is not null)
|
||||
RecordUnexpectedCallback(snapshot.Id);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(SelectionChangedEvent change) =>
|
||||
RecordUnexpectedCallback(change.SelectedObjectId ?? 0u);
|
||||
|
||||
private void RecordUnexpectedCallback(uint objectId)
|
||||
{
|
||||
if ((_throwAfterRegistration || _throwDuringInitialize)
|
||||
&& _assemblyDirectory is not null)
|
||||
{
|
||||
File.AppendAllText(
|
||||
Path.Combine(_assemblyDirectory, "unexpected-callback"),
|
||||
$"{snapshot.Id}{Environment.NewLine}");
|
||||
$"{objectId}{Environment.NewLine}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnloading(AssemblyLoadContext context)
|
||||
{
|
||||
IPluginHost host = _host!;
|
||||
bool uiClosed = Rejects(() => host.Ui.AddMarkupPanel(
|
||||
Path.Combine(AppContext.BaseDirectory, "unloading-panel.xml"),
|
||||
this));
|
||||
bool eventsClosed = Rejects(() =>
|
||||
{
|
||||
host.Events.EntitySpawned += OnEntitySpawned;
|
||||
});
|
||||
bool selectionClosed = Rejects(() =>
|
||||
{
|
||||
host.Selection.Changed += OnSelectionChanged;
|
||||
});
|
||||
File.WriteAllText(
|
||||
Path.Combine(_assemblyDirectory!, "unload-observation"),
|
||||
$"ui={uiClosed};events={eventsClosed};selection={selectionClosed}");
|
||||
}
|
||||
|
||||
private static bool Rejects(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return false;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue