Skip to content

PluginFaultHandler 5.46.0 ​

final class · pocketmine\plugin\PluginFaultHandler · source

Isolates failures of plugin code so that a buggy plugin cannot crash the whole server.

Every place where the core calls into plugin code (event handlers, scheduled tasks, commands, async task completions, form responses, promise callbacks, chunk listeners, plugin-defined blocks/entities, plugin load/enable/disable...) catches \Throwable and passes it to PluginFaultHandler::isolate(), which:

  1. Attributes the failure to a plugin: first the registered owner of the entry point (the plugin which registered the listener / owns the task scheduler / owns the command), then the file in which the called object's class or closure was declared, then the files of the stack trace (innermost first), like the crash dump does.
  2. If no plugin can be blamed, the throwable is rethrown: core bugs must never be hidden, so they still crash the server as before.
  3. Otherwise a PluginErrorEvent is called, the failure is logged at ERROR level as "[Plugin vX] Unhandled exception in <entry point>" with the full stack trace, and the server carries on. Whatever the plugin did before failing stays done (e.g. an event keeps the state the failing handler left it in).
  4. If the plugin fails PluginFaultHandler::getDisableAfterErrors() times within PluginFaultHandler::getErrorWindowSeconds() seconds (by default 10 failures in 60 seconds), it is scheduled to be disabled, with a CRITICAL log.

Disabling is deferred: the plugin is only marked as pending, and actually disabled by PluginFaultHandler::processPendingDisables() at the end of the current server tick. Disabling a plugin from inside its own failing callback (while the core is iterating a handler list, a scheduler heap, a task queue...) is therefore never needed. Until then, the core skips the plugin's remaining event handlers and scheduled tasks (PluginFaultHandler::isPendingDisable()).

Exceptions used for control flow by the core (task cancellation, command usage errors, bad packets) are always rethrown. A DisablePluginException thrown from any entry point disables the plugin that threw it.

Uncatchable fatal errors (memory exhaustion, compile errors, segfaults...) cannot be isolated and still crash the server.

The old behaviour (any plugin exception crashes the server) can be restored by setting plugins.crash-on-unhandled-exception: true in pocketmine.yml.

Constants ​

DEFAULT_DISABLE_AFTER_ERRORS ​

php
public const DEFAULT_DISABLE_AFTER_ERRORS = 10

DEFAULT_ERROR_WINDOW_SECONDS ​

php
public const DEFAULT_ERROR_WINDOW_SECONDS = 60

Methods ​

__construct() ​

php
public function __construct(\Logger $logger, \Closure $pluginsProvider, \Closure $pluginDisabler, bool $crashOnUnhandledException = false, int $disableAfterErrors = self::DEFAULT_DISABLE_AFTER_ERRORS, float $errorWindowSeconds = self::DEFAULT_ERROR_WINDOW_SECONDS, ?\Closure $clock = null)

attribute() ​

php
public function attribute(\Throwable $throwable, ?\pocketmine\plugin\Plugin $owner = null, ?object $origin = null) : ?\pocketmine\plugin\Plugin

Determines which plugin is responsible for the given throwable, or null if it doesn't look like a plugin's fault.

findPluginByFile() ​

php
public function findPluginByFile(string $file) : ?\pocketmine\plugin\Plugin

Returns the plugin whose files contain the given source file, if any. Only works for plugins extending PluginBase (like the crash dump).

findPluginByObject() ​

php
public function findPluginByObject(object $object) : ?\pocketmine\plugin\Plugin

Returns the plugin which declared the given closure or the class of the given object, if any.

getDisableAfterErrors() ​

php
public function getDisableAfterErrors() : int

Number of failures within the error window after which a plugin is disabled. 0 or less means never.

getErrorWindowSeconds() ​

php
public function getErrorWindowSeconds() : float

getInstance() ​

php
public static function getInstance() : ?self

Returns the fault handler used by the server, or null if none is installed (in which case plugin failures propagate as they used to).

getRecentErrorCount() ​

php
public function getRecentErrorCount(\pocketmine\plugin\Plugin $plugin) : int

Returns the number of failures counted for the given plugin within the error window.

handle() ​

php
public function handle(\Throwable $throwable, ?\pocketmine\plugin\Plugin $owner, string $context, ?object $origin = null) : void

isControlFlowException() ​

php
public static function isControlFlowException(\Throwable $throwable) : bool

Returns whether the given throwable is used by the core for control flow and must therefore never be swallowed.

isCrashOnUnhandledException() ​

php
public function isCrashOnUnhandledException() : bool

Whether plugin failures crash the server like they used to (pmmp behaviour).

isPendingDisable() ​

php
public function isPendingDisable(\pocketmine\plugin\Plugin $plugin) : bool

isPluginPendingDisable() ​

php
public static function isPluginPendingDisable(\pocketmine\plugin\Plugin $plugin) : bool

Returns whether the plugin has been scheduled to be disabled because of its failures and must not be called any more until then (the core skips its remaining event handlers and scheduled tasks).

processPendingDisables() ​

php
public function processPendingDisables() : void

Disables the plugins scheduled to be disabled. Called by the server at the end of every tick, when no plugin code is on the stack.

VanillaMine is a fork of PocketMine-MP, maintained by Luxury Nodes. Not affiliated with the PMMP team or Mojang.