mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 12:44:16 +01:00
Fix race condition in plugin load
When opening the installer while boot plugins are still loaded, it may have been possible for plugins to be added to the installed plugins list twice, causing various statekeeping issues
This commit is contained in:
parent
096f9c3e52
commit
e29171cc99
6 changed files with 83 additions and 82 deletions
|
|
@ -46,6 +46,8 @@ using ImPlotNET;
|
||||||
using PInvoke;
|
using PInvoke;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal;
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1013,7 +1015,7 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
|
|
||||||
if (ImGui.MenuItem("Scan dev plugins"))
|
if (ImGui.MenuItem("Scan dev plugins"))
|
||||||
{
|
{
|
||||||
pluginManager.ScanDevPlugins();
|
Task.Run(pluginManager.ScanDevPluginsAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
|
||||||
_ = pluginManager.ReloadPluginMastersAsync();
|
_ = pluginManager.ReloadPluginMastersAsync();
|
||||||
Service<PluginManager>.Get().ScanDevPlugins();
|
_ = pluginManager.ScanDevPluginsAsync();
|
||||||
|
|
||||||
if (!this.isSearchTextPrefilled)
|
if (!this.isSearchTextPrefilled)
|
||||||
{
|
{
|
||||||
|
|
@ -782,7 +782,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button(Locs.FooterButton_ScanDevPlugins))
|
if (ImGui.Button(Locs.FooterButton_ScanDevPlugins))
|
||||||
{
|
{
|
||||||
pluginManager.ScanDevPlugins();
|
_ = pluginManager.ScanDevPluginsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public class DevPluginsSettingsEntry : SettingsEntry
|
||||||
|
|
||||||
if (this.devPluginLocationsChanged)
|
if (this.devPluginLocationsChanged)
|
||||||
{
|
{
|
||||||
Service<PluginManager>.Get().ScanDevPlugins();
|
_ = Service<PluginManager>.Get().ScanDevPluginsAsync();
|
||||||
this.devPluginLocationsChanged = false;
|
this.devPluginLocationsChanged = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -776,7 +776,8 @@ internal class PluginManager : IInternalDisposableService
|
||||||
/// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works
|
/// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works
|
||||||
/// a little differently.
|
/// a little differently.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ScanDevPlugins()
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation. This function generally will not block as new plugins aren't loaded.</returns>
|
||||||
|
public async Task ScanDevPluginsAsync()
|
||||||
{
|
{
|
||||||
// devPlugins are more freeform. Look for any dll and hope to get lucky.
|
// devPlugins are more freeform. Look for any dll and hope to get lucky.
|
||||||
var devDllFiles = new List<FileInfo>();
|
var devDllFiles = new List<FileInfo>();
|
||||||
|
|
@ -823,8 +824,7 @@ internal class PluginManager : IInternalDisposableService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Add them to the list and let the user decide, nothing is auto-loaded.
|
// Add them to the list and let the user decide, nothing is auto-loaded.
|
||||||
this.LoadPluginAsync(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true)
|
await this.LoadPluginAsync(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true);
|
||||||
.Wait();
|
|
||||||
listChanged = true;
|
listChanged = true;
|
||||||
}
|
}
|
||||||
catch (InvalidPluginException)
|
catch (InvalidPluginException)
|
||||||
|
|
@ -1188,32 +1188,20 @@ internal class PluginManager : IInternalDisposableService
|
||||||
{
|
{
|
||||||
// Testing exclusive
|
// Testing exclusive
|
||||||
if (manifest.IsTestingExclusive && !this.configuration.DoPluginTest)
|
if (manifest.IsTestingExclusive && !this.configuration.DoPluginTest)
|
||||||
{
|
|
||||||
Log.Verbose($"Testing exclusivity: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Applicable version
|
// Applicable version
|
||||||
if (manifest.ApplicableVersion < this.dalamud.StartInfo.GameVersion)
|
if (manifest.ApplicableVersion < this.dalamud.StartInfo.GameVersion)
|
||||||
{
|
|
||||||
Log.Verbose($"Game version: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// API level - we keep the API before this in the installer to show as "outdated"
|
// API level - we keep the API before this in the installer to show as "outdated"
|
||||||
var effectiveApiLevel = this.UseTesting(manifest) && manifest.TestingDalamudApiLevel != null ? manifest.TestingDalamudApiLevel.Value : manifest.DalamudApiLevel;
|
var effectiveApiLevel = this.UseTesting(manifest) && manifest.TestingDalamudApiLevel != null ? manifest.TestingDalamudApiLevel.Value : manifest.DalamudApiLevel;
|
||||||
if (effectiveApiLevel < DalamudApiLevel - 1 && !this.LoadAllApiLevels)
|
if (effectiveApiLevel < DalamudApiLevel - 1 && !this.LoadAllApiLevels)
|
||||||
{
|
|
||||||
Log.Verbose($"API Level: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Banned
|
// Banned
|
||||||
if (this.IsManifestBanned(manifest))
|
if (this.IsManifestBanned(manifest))
|
||||||
{
|
|
||||||
Log.Verbose($"Banned: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1572,6 +1560,8 @@ internal class PluginManager : IInternalDisposableService
|
||||||
/// <returns>The loaded plugin.</returns>
|
/// <returns>The loaded plugin.</returns>
|
||||||
private async Task<LocalPlugin> LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false)
|
private async Task<LocalPlugin> LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false)
|
||||||
{
|
{
|
||||||
|
// TODO: Split this function - it should only take care of adding the plugin to the list, not loading itself, that should be done through the plugin instance
|
||||||
|
|
||||||
var loadPlugin = !doNotLoad;
|
var loadPlugin = !doNotLoad;
|
||||||
|
|
||||||
LocalPlugin? plugin;
|
LocalPlugin? plugin;
|
||||||
|
|
@ -1582,21 +1572,34 @@ internal class PluginManager : IInternalDisposableService
|
||||||
throw new Exception("No internal name");
|
throw new Exception("No internal name");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDev)
|
// Track the plugin as soon as it is instantiated to prevent it from being loaded twice,
|
||||||
|
// if the installer or DevPlugin scanner is attempting to add plugins while we are still loading boot plugins
|
||||||
|
lock (this.pluginListLock)
|
||||||
{
|
{
|
||||||
Log.Information("Loading dev plugin {Name}", manifest.InternalName);
|
// Check if this plugin is already loaded
|
||||||
plugin = new LocalDevPlugin(dllFile, manifest);
|
if (this.installedPluginsList.Any(lp => lp.DllFile.FullName == dllFile.FullName))
|
||||||
|
throw new InvalidOperationException("Plugin at the provided path is already loaded");
|
||||||
|
|
||||||
// This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet
|
if (isDev)
|
||||||
// TODO(goat): Re-enable this when we have better tracing for what was rendering when
|
{
|
||||||
// this.configuration.ImGuiAssertsEnabledAtStartup ??= true;
|
Log.Information("Loading dev plugin {Name}", manifest.InternalName);
|
||||||
}
|
plugin = new LocalDevPlugin(dllFile, manifest);
|
||||||
else
|
|
||||||
{
|
// This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet
|
||||||
Log.Information("Loading plugin {Name}", manifest.InternalName);
|
// TODO(goat): Re-enable this when we have better tracing for what was rendering when
|
||||||
plugin = new LocalPlugin(dllFile, manifest);
|
// this.configuration.ImGuiAssertsEnabledAtStartup ??= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Information("Loading plugin {Name}", manifest.InternalName);
|
||||||
|
plugin = new LocalPlugin(dllFile, manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.installedPluginsList.Add(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Verbose("Starting to load plugin {Name} at {FileLocation}", manifest.InternalName, dllFile.FullName);
|
||||||
|
|
||||||
// Perform a migration from InternalName to GUIDs. The plugin should definitely have a GUID here.
|
// Perform a migration from InternalName to GUIDs. The plugin should definitely have a GUID here.
|
||||||
// This will also happen if you are installing a plugin with the installer, and that's intended!
|
// This will also happen if you are installing a plugin with the installer, and that's intended!
|
||||||
// It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will
|
// It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will
|
||||||
|
|
@ -1697,43 +1700,34 @@ internal class PluginManager : IInternalDisposableService
|
||||||
catch (BannedPluginException)
|
catch (BannedPluginException)
|
||||||
{
|
{
|
||||||
// Out of date plugins get added so they can be updated.
|
// Out of date plugins get added so they can be updated.
|
||||||
Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}");
|
Log.Information("{InternalName}: Plugin was banned, adding anyways", plugin.Manifest.InternalName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (plugin.IsDev)
|
if (plugin.IsDev)
|
||||||
{
|
{
|
||||||
// Dev plugins always get added to the list so they can be fiddled with in the UI
|
// Dev plugins always get added to the list so they can be fiddled with in the UI
|
||||||
Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}");
|
Log.Information(ex, "{InternalName}: Dev plugin failed to load", plugin.Manifest.InternalName);
|
||||||
|
|
||||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
|
||||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
|
||||||
}
|
}
|
||||||
else if (plugin.IsOutdated)
|
else if (plugin.IsOutdated)
|
||||||
{
|
{
|
||||||
// Out of date plugins get added, so they can be updated.
|
// Out of date plugins get added, so they can be updated.
|
||||||
Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}");
|
Log.Information(ex, "{InternalName}: Plugin was outdated", plugin.Manifest.InternalName);
|
||||||
}
|
}
|
||||||
else if (plugin.IsOrphaned)
|
else if (plugin.IsOrphaned)
|
||||||
{
|
{
|
||||||
// Orphaned plugins get added, so that users aren't confused.
|
// Orphaned plugins get added, so that users aren't confused.
|
||||||
Log.Information(ex, $"Plugin was orphaned, adding anyways: {dllFile.Name}");
|
Log.Information(ex, "{InternalName}: Plugin was orphaned", plugin.Manifest.InternalName);
|
||||||
}
|
}
|
||||||
else if (isBoot)
|
else if (isBoot)
|
||||||
{
|
{
|
||||||
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
||||||
Log.Information(ex, $"Regular plugin failed to load, adding anyways: {dllFile.Name}");
|
Log.Information(ex, "{InternalName}: Regular plugin failed to load", plugin.Manifest.InternalName);
|
||||||
|
|
||||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
|
||||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
|
||||||
}
|
}
|
||||||
else if (!plugin.CheckPolicy())
|
else if (!plugin.CheckPolicy())
|
||||||
{
|
{
|
||||||
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
||||||
Log.Information(ex, $"Plugin not loaded due to policy, adding anyways: {dllFile.Name}");
|
Log.Information(ex, "{InternalName}: Plugin not loaded due to policy", plugin.Manifest.InternalName);
|
||||||
|
|
||||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
|
||||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1742,14 +1736,6 @@ internal class PluginManager : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin == null)
|
|
||||||
throw new Exception("Plugin was null when adding to list");
|
|
||||||
|
|
||||||
lock (this.pluginListLock)
|
|
||||||
{
|
|
||||||
this.installedPluginsList.Add(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as finished loading
|
// Mark as finished loading
|
||||||
if (manifest.LoadSync)
|
if (manifest.LoadSync)
|
||||||
this.StartupLoadTracking?.Finish(manifest.InternalName);
|
this.StartupLoadTracking?.Finish(manifest.InternalName);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Types;
|
||||||
/// This class represents a dev plugin and all facets of its lifecycle.
|
/// This class represents a dev plugin and all facets of its lifecycle.
|
||||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class LocalDevPlugin : LocalPlugin
|
internal sealed class LocalDevPlugin : LocalPlugin
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("PLUGIN");
|
private static readonly ModuleLog Log = new("PLUGIN");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,13 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
this.DllFile = dllFile;
|
this.DllFile = dllFile;
|
||||||
this.State = PluginState.Unloaded;
|
|
||||||
|
|
||||||
// Although it is conditionally used here, we need to set the initial value regardless.
|
// Although it is conditionally used here, we need to set the initial value regardless.
|
||||||
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
|
|
||||||
|
this.State = PluginState.Unloaded;
|
||||||
|
|
||||||
var needsSaveDueToLegacyFiles = false;
|
var needsSaveDueToLegacyFiles = false;
|
||||||
|
|
||||||
// This converts from the ".disabled" file feature to the manifest instead.
|
// This converts from the ".disabled" file feature to the manifest instead.
|
||||||
|
|
@ -352,19 +353,13 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loader.Reload();
|
this.loader.Reload();
|
||||||
|
this.RefreshAssemblyInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the assembly
|
Log.Verbose("{Name} ({Guid}): Have type", this.InternalName, this.EffectiveWorkingPluginId);
|
||||||
this.pluginAssembly ??= this.loader.LoadDefaultAssembly();
|
|
||||||
|
|
||||||
this.AssemblyName = this.pluginAssembly.GetName();
|
|
||||||
|
|
||||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
|
||||||
this.pluginType ??= this.pluginAssembly.GetTypes()
|
|
||||||
.First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
|
||||||
|
|
||||||
// Check for any loaded plugins with the same assembly name
|
// Check for any loaded plugins with the same assembly name
|
||||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
var assemblyName = this.pluginAssembly!.GetName().Name;
|
||||||
foreach (var otherPlugin in pluginManager.InstalledPlugins)
|
foreach (var otherPlugin in pluginManager.InstalledPlugins)
|
||||||
{
|
{
|
||||||
// During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed
|
// During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed
|
||||||
|
|
@ -376,7 +371,7 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
||||||
{
|
{
|
||||||
this.State = PluginState.Unloaded;
|
this.State = PluginState.Unloaded;
|
||||||
Log.Debug($"Duplicate assembly: {this.Name}");
|
Log.Debug("Duplicate assembly: {Name}", this.InternalName);
|
||||||
|
|
||||||
throw new DuplicatePluginException(assemblyName);
|
throw new DuplicatePluginException(assemblyName);
|
||||||
}
|
}
|
||||||
|
|
@ -392,7 +387,7 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
this.instance = await CreatePluginInstance(
|
this.instance = await CreatePluginInstance(
|
||||||
this.manifest,
|
this.manifest,
|
||||||
this.serviceScope,
|
this.serviceScope,
|
||||||
this.pluginType,
|
this.pluginType!,
|
||||||
this.dalamudInterface);
|
this.dalamudInterface);
|
||||||
this.State = PluginState.Loaded;
|
this.State = PluginState.Loaded;
|
||||||
Log.Information("Finished loading {PluginName}", this.InternalName);
|
Log.Information("Finished loading {PluginName}", this.InternalName);
|
||||||
|
|
@ -620,42 +615,60 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.RefreshAssemblyInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshAssemblyInformation()
|
||||||
|
{
|
||||||
|
if (this.loader == null)
|
||||||
|
throw new InvalidOperationException("No loader available");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.pluginAssembly = this.loader.LoadDefaultAssembly();
|
this.pluginAssembly = this.loader.LoadDefaultAssembly();
|
||||||
|
this.AssemblyName = this.pluginAssembly.GetName();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.pluginAssembly = null;
|
this.ResetLoader();
|
||||||
this.pluginType = null;
|
|
||||||
this.loader.Dispose();
|
|
||||||
|
|
||||||
Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}");
|
Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}");
|
||||||
throw new InvalidPluginException(this.DllFile);
|
throw new InvalidPluginException(this.DllFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.pluginAssembly == null)
|
||||||
|
{
|
||||||
|
this.ResetLoader();
|
||||||
|
Log.Error("Plugin assembly is null: {DllFileFullName}", this.DllFile.FullName);
|
||||||
|
throw new InvalidPluginException(this.DllFile);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||||
}
|
}
|
||||||
catch (ReflectionTypeLoadException ex)
|
catch (ReflectionTypeLoadException ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}");
|
this.ResetLoader();
|
||||||
// Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error.
|
Log.Error(ex, "Could not load one or more types when searching for IDalamudPlugin: {DllFileFullName}", this.DllFile.FullName);
|
||||||
this.pluginType = ex.Types.FirstOrDefault(type => type != null && type.IsAssignableTo(typeof(IDalamudPlugin)));
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pluginType == default)
|
if (this.pluginType == null)
|
||||||
{
|
{
|
||||||
this.pluginAssembly = null;
|
this.ResetLoader();
|
||||||
this.pluginType = null;
|
Log.Error("Nothing inherits from IDalamudPlugin: {DllFileFullName}", this.DllFile.FullName);
|
||||||
this.loader.Dispose();
|
|
||||||
|
|
||||||
Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}");
|
|
||||||
throw new InvalidPluginException(this.DllFile);
|
throw new InvalidPluginException(this.DllFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetLoader()
|
||||||
|
{
|
||||||
|
this.pluginAssembly = null;
|
||||||
|
this.pluginType = null;
|
||||||
|
this.loader?.Dispose();
|
||||||
|
this.loader = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Clears and disposes all resources associated with the plugin instance.</summary>
|
/// <summary>Clears and disposes all resources associated with the plugin instance.</summary>
|
||||||
/// <param name="disposalMode">Whether to clear and dispose <see cref="loader"/>.</param>
|
/// <param name="disposalMode">Whether to clear and dispose <see cref="loader"/>.</param>
|
||||||
/// <returns>Exceptions, if any occurred.</returns>
|
/// <returns>Exceptions, if any occurred.</returns>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue