PluginManager: Improved startup performance when loading many plugins

for some reason we were reading and parsing the plugin.yml at least twice for every plugin loaded.
We were repeating work already done by the initial loadPlugins() triage (discovering correct loader, loading plugin.yml from disk, parsing plugin.yml, validating plugin.yml) every time loadPlugin() was called with that plugin.
This commit is contained in:
Dylan K. Taylor 2021-10-11 01:11:59 +01:00
parent e1ee320c8d
commit 6d728e8d98
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
2 changed files with 120 additions and 74 deletions

View File

@ -0,0 +1,42 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\plugin;
/**
* @internal
*/
final class PluginLoadTriageEntry{
public function __construct(
private string $file,
private PluginLoader $loader,
private PluginDescription $description
){}
public function getFile() : string{ return $this->file; }
public function getLoader() : PluginLoader{ return $this->loader; }
public function getDescription() : PluginDescription{ return $this->description; }
}

View File

@ -178,77 +178,7 @@ class PluginManager{
if($loader->canLoadPlugin($path)){
$description = $loader->getPluginDescription($path);
if($description instanceof PluginDescription){
$language = $this->server->getLanguage();
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_load($description->getFullName())));
$dataFolder = $this->getDataDirectory($path, $description->getName());
if(file_exists($dataFolder) and !is_dir($dataFolder)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_badDataFolder($dataFolder)
)));
return null;
}
if(!file_exists($dataFolder)){
mkdir($dataFolder, 0777, true);
}
$prefixed = $loader->getAccessProtocol() . $path;
$loader->loadPlugin($prefixed);
$mainClass = $description->getMain();
if(!class_exists($mainClass, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassNotFound()
)));
return null;
}
if(!is_a($mainClass, Plugin::class, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassWrongType(Plugin::class)
)));
return null;
}
$permManager = PermissionManager::getInstance();
$opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){
foreach($perms as $perm){
$permManager->addPermission($perm);
switch($default){
case PermissionParser::DEFAULT_TRUE:
$everyoneRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_OP:
$opRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_NOT_OP:
//TODO: I don't think anyone uses this, and it currently relies on some magic inside PermissibleBase
//to ensure that the operator override actually applies.
//Explore getting rid of this.
//The following grants this permission to anyone who has the "everyone" root permission.
//However, if the operator root node (which has higher priority) is present, the
//permission will be denied instead.
$everyoneRoot->addChild($perm->getName(), true);
$opRoot->addChild($perm->getName(), false);
break;
default:
break;
}
}
}
/**
* @var Plugin $plugin
* @see Plugin::__construct()
*/
$plugin = new $mainClass($loader, $this->server, $description, $dataFolder, $prefixed, new DiskResourceProvider($prefixed . "/resources/"));
$this->plugins[$plugin->getDescription()->getName()] = $plugin;
return $plugin;
$this->internalLoadPlugin($path, $loader, $description);
}
}
}
@ -256,6 +186,80 @@ class PluginManager{
return null;
}
private function internalLoadPlugin(string $path, PluginLoader $loader, PluginDescription $description) : ?Plugin{
$language = $this->server->getLanguage();
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_load($description->getFullName())));
$dataFolder = $this->getDataDirectory($path, $description->getName());
if(file_exists($dataFolder) and !is_dir($dataFolder)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_badDataFolder($dataFolder)
)));
return null;
}
if(!file_exists($dataFolder)){
mkdir($dataFolder, 0777, true);
}
$prefixed = $loader->getAccessProtocol() . $path;
$loader->loadPlugin($prefixed);
$mainClass = $description->getMain();
if(!class_exists($mainClass, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassNotFound()
)));
return null;
}
if(!is_a($mainClass, Plugin::class, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassWrongType(Plugin::class)
)));
return null;
}
$permManager = PermissionManager::getInstance();
$opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){
foreach($perms as $perm){
$permManager->addPermission($perm);
switch($default){
case PermissionParser::DEFAULT_TRUE:
$everyoneRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_OP:
$opRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_NOT_OP:
//TODO: I don't think anyone uses this, and it currently relies on some magic inside PermissibleBase
//to ensure that the operator override actually applies.
//Explore getting rid of this.
//The following grants this permission to anyone who has the "everyone" root permission.
//However, if the operator root node (which has higher priority) is present, the
//permission will be denied instead.
$everyoneRoot->addChild($perm->getName(), true);
$opRoot->addChild($perm->getName(), false);
break;
default:
break;
}
}
}
/**
* @var Plugin $plugin
* @see Plugin::__construct()
*/
$plugin = new $mainClass($loader, $this->server, $description, $dataFolder, $prefixed, new DiskResourceProvider($prefixed . "/resources/"));
$this->plugins[$plugin->getDescription()->getName()] = $plugin;
return $plugin;
}
/**
* @param string[]|null $newLoaders
* @phpstan-param list<class-string<PluginLoader>> $newLoaders
@ -330,7 +334,7 @@ class PluginManager{
)));
continue;
}
$plugins[$name] = $file;
$plugins[$name] = new PluginLoadTriageEntry($file, $loader, $description);
$softDependencies[$name] = array_merge($softDependencies[$name] ?? [], $description->getSoftDepend());
$dependencies[$name] = $description->getDepend();
@ -347,7 +351,7 @@ class PluginManager{
while(count($plugins) > 0){
$loadedThisLoop = 0;
foreach($plugins as $name => $file){
foreach($plugins as $name => $entry){
if(isset($dependencies[$name])){
foreach($dependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
@ -389,7 +393,7 @@ class PluginManager{
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
unset($plugins[$name]);
$loadedThisLoop++;
if(($plugin = $this->loadPlugin($file, $loaders)) instanceof Plugin){
if(($plugin = $this->internalLoadPlugin($entry->getFile(), $entry->getLoader(), $entry->getDescription())) instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}
}