mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-05 23:58:24 -08:00
feat: implement TerminateSelf(string reason) to allow plugins to safely terminate themselves (#1047)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
@@ -72,6 +73,22 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public void Start()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||
{
|
||||
if ((e.ExceptionObject as Exception) is PluginTerminationException pluginEx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||
{
|
||||
if (e.Exception.InnerExceptions.Any(ex => ex is PluginTerminationException))
|
||||
{
|
||||
e.SetObserved();
|
||||
}
|
||||
};
|
||||
|
||||
Logger.LogInformation("CounterStrikeSharp is starting up...");
|
||||
|
||||
_coreConfig.Load();
|
||||
@@ -127,123 +144,129 @@ namespace CounterStrikeSharp.API.Core
|
||||
switch (info.GetArg(1))
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.");
|
||||
|
||||
foreach (var plugin in _pluginManager.GetLoadedPlugins())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin?.ModuleName ?? "Unknown",
|
||||
plugin.Plugin?.ModuleVersion ?? "Unknown");
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Plugin.ModuleDescription);
|
||||
}
|
||||
|
||||
info.ReplyToCommand(sb.ToString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n");
|
||||
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.");
|
||||
|
||||
foreach (var plugin in _pluginManager.GetLoadedPlugins())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin?.ModuleName ?? "Unknown",
|
||||
plugin.Plugin?.ModuleVersion ?? "Unknown");
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Plugin.ModuleDescription);
|
||||
}
|
||||
|
||||
if (plugin.State == PluginState.Unloaded && !string.IsNullOrEmpty(plugin.TerminationReason))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(" Termination Reason: {0}", plugin.TerminationReason);
|
||||
}
|
||||
|
||||
info.ReplyToCommand(sb.ToString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// If our argument doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
|
||||
// We'll assume we have a full path if we have ".dll".
|
||||
var path = info.GetArg(2);
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath, !path.EndsWith(".dll") ? $"plugins/{path}/{path}.dll" : path);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
try
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// If our argument doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
|
||||
// We'll assume we have a full path if we have ".dll".
|
||||
var path = info.GetArg(2);
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath, !path.EndsWith(".dll") ? $"plugins/{path}/{path}.dll" : path);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\"");
|
||||
Logger.LogError(e, "Could not load plugin \"{Path}\"", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\"");
|
||||
Logger.LogError(e, "Could not load plugin \"{Path}\"", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "stop":
|
||||
case "unload":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n");
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
string path;
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath,
|
||||
!pluginIdentifier.EndsWith(".dll") ? $"plugins/{pluginIdentifier}/{pluginIdentifier}.dll" : pluginIdentifier);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier)
|
||||
?? _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
string path;
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath,
|
||||
!pluginIdentifier.EndsWith(".dll") ? $"plugins/{pluginIdentifier}/{pluginIdentifier}.dll" : pluginIdentifier);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier)
|
||||
?? _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "restart":
|
||||
case "reload":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n");
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
plugin.Plugin.OnAllPluginsLoaded(true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
plugin.Plugin.OnAllPluginsLoaded(true);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
|
||||
@@ -53,20 +53,27 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public abstract string ModuleName { get; }
|
||||
public abstract string ModuleVersion { get; }
|
||||
|
||||
|
||||
public virtual string ModuleAuthor { get; }
|
||||
|
||||
|
||||
public virtual string ModuleDescription { get; }
|
||||
|
||||
public string ModulePath { get; set; }
|
||||
|
||||
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
|
||||
public ICommandManager CommandManager { get; set; }
|
||||
|
||||
public IStringLocalizer Localizer { get; set; }
|
||||
|
||||
|
||||
internal Plugin.ISelfPluginControl SelfControl { get; set; }
|
||||
|
||||
public void TerminateSelf(string reason)
|
||||
{
|
||||
SelfControl?.TerminateSelf(reason);
|
||||
}
|
||||
|
||||
public virtual void Load(bool hotReload)
|
||||
{
|
||||
}
|
||||
@@ -74,7 +81,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public virtual void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
}
|
||||
@@ -116,7 +123,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public readonly Dictionary<Delegate, CallbackSubscriber> Handlers =
|
||||
new Dictionary<Delegate, CallbackSubscriber>();
|
||||
|
||||
|
||||
public readonly Dictionary<Delegate, CallbackSubscriber> CommandListeners =
|
||||
new Dictionary<Delegate, CallbackSubscriber>();
|
||||
|
||||
@@ -132,7 +139,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public readonly List<CommandDefinition> CommandDefinitions = new List<CommandDefinition>();
|
||||
|
||||
public readonly List<Timer> Timers = new List<Timer>();
|
||||
|
||||
|
||||
public delegate HookResult GameEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent;
|
||||
|
||||
private void RegisterEventHandlerInternal<T>(string name, GameEventHandler<T> handler, bool post)
|
||||
@@ -156,7 +163,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()?.Name;
|
||||
RegisterEventHandlerInternal(name, handler, hookMode == HookMode.Post);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// De-registers a game event handler.
|
||||
/// </summary>
|
||||
@@ -164,7 +171,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public void DeregisterEventHandler<T>(GameEventHandler<T> handler, HookMode hookMode = HookMode.Post) where T : GameEvent
|
||||
{
|
||||
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()!.Name;
|
||||
|
||||
|
||||
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
|
||||
|
||||
NativeAPI.UnhookEvent(name, subscriber.GetInputArgument(), hookMode == HookMode.Post);
|
||||
@@ -195,7 +202,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
CommandDefinitions.Add(definition);
|
||||
CommandManager.RegisterCommand(definition);
|
||||
}
|
||||
|
||||
|
||||
private void AddCommand(CommandDefinition definition)
|
||||
{
|
||||
CommandDefinitions.Add(definition);
|
||||
@@ -319,9 +326,9 @@ namespace CounterStrikeSharp.API.Core
|
||||
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
|
||||
nameof(T));
|
||||
}
|
||||
|
||||
|
||||
if (!Listeners.TryGetValue(handler, out var subscriber)) return;
|
||||
|
||||
|
||||
NativeAPI.RemoveListener(listenerName, subscriber.GetInputArgument());
|
||||
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
|
||||
Listeners.Remove(handler);
|
||||
@@ -408,7 +415,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
.Where(method =>
|
||||
method.GetParameters().FirstOrDefault()?.ParameterType.IsSubclassOf(typeof(GameEvent)) == true)
|
||||
.ToArray();
|
||||
|
||||
|
||||
var listenerHandlers = methods
|
||||
.Where(method => method.GetCustomAttribute(typeof(ListenerHandlerAttribute<>)) != null)
|
||||
.ToArray();
|
||||
@@ -440,7 +447,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
|
||||
listenerType.Name);
|
||||
|
||||
var listenerDelegate = Delegate.CreateDelegate(listenerType, instance, listnerHandler);
|
||||
var listenerDelegate = Delegate.CreateDelegate(listenerType, instance, listnerHandler);
|
||||
|
||||
registerListener.MakeGenericMethod(listenerType).Invoke(this, [listenerDelegate]);
|
||||
}
|
||||
@@ -502,29 +509,29 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
var convars = type
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Where(prop => prop.FieldType.IsGenericType &&
|
||||
.Where(prop => prop.FieldType.IsGenericType &&
|
||||
prop.FieldType.GetGenericTypeDefinition() == typeof(FakeConVar<>));
|
||||
|
||||
|
||||
foreach (var prop in convars)
|
||||
{
|
||||
object propValue = prop.GetValue(instance); // FakeConvar<?> instance
|
||||
var propValueType = prop.FieldType.GenericTypeArguments[0];
|
||||
var name = prop.FieldType.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance)
|
||||
.GetValue(propValue);
|
||||
|
||||
|
||||
var description = prop.FieldType.GetProperty("Description", BindingFlags.Public | BindingFlags.Instance)
|
||||
.GetValue(propValue);
|
||||
|
||||
MethodInfo executeCommandMethod = prop.FieldType
|
||||
.GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
this.AddCommand((string)name, (string) description, (caller, command) =>
|
||||
|
||||
this.AddCommand((string)name, (string)description, (caller, command) =>
|
||||
{
|
||||
executeCommandMethod.Invoke(propValue, new object[] {caller, command});
|
||||
executeCommandMethod.Invoke(propValue, new object[] { caller, command });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to bind a fake ConVar to a plugin command. Only required for ConVars that are not public properties of the plugin class.
|
||||
/// </summary>
|
||||
@@ -549,7 +556,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
NativeAPI.HookEntityOutput(classname, outputName, subscriber.GetInputArgument(), mode);
|
||||
EntityOutputHooks[handler] = subscriber;
|
||||
}
|
||||
|
||||
|
||||
public void HookUserMessage(int messageId, UserMessage.UserMessageHandler handler, HookMode mode = HookMode.Pre)
|
||||
{
|
||||
var subscriber = new CallbackSubscriber(handler, handler,
|
||||
@@ -558,7 +565,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
NativeAPI.HookUsermessage(messageId, subscriber.GetInputArgument(), mode);
|
||||
Handlers[handler] = subscriber;
|
||||
}
|
||||
|
||||
|
||||
public void UnhookUserMessage(int messageId, UserMessage.UserMessageHandler handler, HookMode mode = HookMode.Pre)
|
||||
{
|
||||
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
|
||||
@@ -641,7 +648,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
subscriber.Dispose();
|
||||
}
|
||||
|
||||
|
||||
foreach (var subscriber in CommandListeners.Values)
|
||||
{
|
||||
subscriber.Dispose();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -107,6 +107,11 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if ((e.InnerException ?? e) is Plugin.PluginTerminationException pluginEx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -31,10 +31,17 @@ using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin
|
||||
{
|
||||
public class PluginContext : IPluginContext
|
||||
public interface ISelfPluginControl
|
||||
{
|
||||
void TerminateSelf(string reason);
|
||||
}
|
||||
|
||||
public class PluginContext : IPluginContext, ISelfPluginControl
|
||||
{
|
||||
public PluginState State { get; set; } = PluginState.Unregistered;
|
||||
public IPlugin Plugin { get; private set; }
|
||||
@@ -50,10 +57,12 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
private readonly string _path;
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
private readonly IServiceProvider _applicationServiceProvider;
|
||||
|
||||
|
||||
public string FilePath => _path;
|
||||
private IServiceScope _serviceScope;
|
||||
|
||||
public string TerminationReason { get; private set; }
|
||||
|
||||
// TOOD: ServiceCollection
|
||||
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
|
||||
|
||||
@@ -65,7 +74,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
_hostConfiguration = hostConfiguration;
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
|
||||
|
||||
Loader = PluginLoader.CreateFromAssemblyFile(path,
|
||||
new[]
|
||||
{
|
||||
@@ -77,7 +86,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
config.IsUnloadable = true;
|
||||
config.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
|
||||
if (CoreConfig.PluginHotReloadEnabled)
|
||||
{
|
||||
_fileWatcher = new FileSystemWatcher
|
||||
@@ -113,14 +122,14 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Load(hotReload: true);
|
||||
Plugin.OnAllPluginsLoaded(hotReload: true);
|
||||
});
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Load(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Loaded) return;
|
||||
|
||||
|
||||
using (Loader.EnterContextualReflection())
|
||||
{
|
||||
var defaultAssembly = Loader.LoadDefaultAssembly();
|
||||
@@ -178,7 +187,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
serviceCollection.AddScoped<ICommandManager>(c => _commandManager);
|
||||
serviceCollection.DecorateSingleton<ICommandManager, PluginCommandManagerDecorator>();
|
||||
|
||||
@@ -215,7 +224,33 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
|
||||
|
||||
Plugin.InitializeConfig(Plugin, pluginType);
|
||||
Plugin.Load(hotReload);
|
||||
|
||||
if (Plugin is BasePlugin basePlugin)
|
||||
{
|
||||
basePlugin.SelfControl = this;
|
||||
}
|
||||
|
||||
this.TerminationReason = string.Empty;
|
||||
try
|
||||
{
|
||||
Plugin.Load(hotReload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if ((ex.InnerException ?? ex) is PluginTerminationException pluginEx)
|
||||
{
|
||||
_logger.LogCritical("Terminating plugin {Name} with reason: {Reason}", Plugin.ModuleName, pluginEx.TerminationReason);
|
||||
this.TerminationReason = pluginEx.TerminationReason;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load plugin {Name}", Plugin.ModuleName);
|
||||
this.TerminationReason = ex.Message ?? "Unknown";
|
||||
}
|
||||
|
||||
Unload(hotReload);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished loading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
@@ -233,12 +268,60 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
|
||||
_logger.LogInformation("Unloading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
Plugin.Unload(hotReload);
|
||||
|
||||
Plugin.Dispose();
|
||||
_serviceScope.Dispose();
|
||||
try
|
||||
{
|
||||
Plugin.Unload(hotReload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.LogError("Failed to unload {Name} during error recovery, forcing cleanup", Plugin.ModuleName);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Plugin.Dispose();
|
||||
_serviceScope.Dispose();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished unloading plugin {Name}", cachedName);
|
||||
}
|
||||
|
||||
public void TerminateWithReason(string reason)
|
||||
{
|
||||
this.TerminationReason = reason;
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case PluginState.Unloaded:
|
||||
case PluginState.Loading:
|
||||
break;
|
||||
case PluginState.Loaded:
|
||||
_logger.LogInformation("Terminating plugin {Name} with reason: {Reason}", Plugin.ModuleName, reason);
|
||||
Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Force execution flow interruption via globally-handled exception to prevent stack unwinding
|
||||
throw new PluginTerminationException(reason);
|
||||
}
|
||||
|
||||
void ISelfPluginControl.TerminateSelf(string reason)
|
||||
{
|
||||
if (State != PluginState.Unloaded)
|
||||
{
|
||||
if (Thread.CurrentThread.IsThreadPoolThread)
|
||||
{
|
||||
Server.NextFrame(() => TerminateWithReason(reason));
|
||||
}
|
||||
else
|
||||
{
|
||||
TerminateWithReason(reason);
|
||||
}
|
||||
|
||||
// **Failsafe mechanism** ensures execution termination
|
||||
// Prevents control flow leakage back to plugin execution context
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin
|
||||
{
|
||||
public class PluginTerminationException : Exception
|
||||
{
|
||||
public string PluginName { get; }
|
||||
public string TerminationReason { get; }
|
||||
|
||||
public PluginTerminationException(string reason) : base($"Plugin terminated: {reason}")
|
||||
{
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string pluginName, string reason) : base($"Plugin '{pluginName}' terminated: {reason}")
|
||||
{
|
||||
PluginName = pluginName;
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string reason, Exception innerException) : base($"Plugin terminated: {reason}", innerException)
|
||||
{
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string pluginName, string reason, Exception innerException) : base($"Plugin '{pluginName}' terminated: {reason}", innerException)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
TerminationReason = reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user