Always sort plugins by key, then dependencies

This has been benchmarked and appears to have minimal impact on performance and solves unnecessary randomness and race conditions during the app's registration and boot cycle

Fixes #4826
This commit is contained in:
Samuel Georges 2019-12-21 20:50:28 +11:00
parent 21f8c5f272
commit adb303a53c
2 changed files with 56 additions and 33 deletions

View File

@ -11,7 +11,7 @@ use Config;
use Schema;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use ApplicationException;
use SystemException;
/**
* Plugin manager
@ -108,6 +108,8 @@ class PluginManager
$this->loadPlugin($namespace, $path);
}
$this->sortDependencies();
return $this->plugins;
}
@ -674,43 +676,25 @@ class PluginManager
}
}
/**
* Returns the plugin identifiers that are required by the supplied plugin.
* @param string $plugin Plugin identifier, object or class
* @return array
*/
public function getDependencies($plugin)
{
if (is_string($plugin) && (!$plugin = $this->findByIdentifier($plugin))) {
return false;
}
if (!isset($plugin->require) || !$plugin->require) {
return null;
}
return is_array($plugin->require) ? $plugin->require : [$plugin->require];
}
/**
* Sorts a collection of plugins, in the order that they should be actioned,
* according to their given dependencies. Least dependent come first.
* @param array $plugins Object collection to sort, or null to sort all.
* @return array Collection of sorted plugin identifiers
*/
public function sortByDependencies($plugins = null)
protected function sortDependencies()
{
if (!is_array($plugins)) {
$plugins = $this->getPlugins();
}
ksort($this->plugins);
/*
* Canvas the dependency tree
*/
$checklist = $this->plugins;
$result = [];
$checklist = $plugins;
$loopCount = 0;
while (count($checklist)) {
if (++$loopCount > 999) {
throw new ApplicationException('Too much recursion');
if (++$loopCount > 2048) {
throw new SystemException('Too much recursion! Check for circular dependencies in your plugins.');
}
foreach ($checklist as $code => $plugin) {
@ -718,8 +702,8 @@ class PluginManager
* Get dependencies and remove any aliens
*/
$depends = $this->getDependencies($plugin) ?: [];
$depends = array_filter($depends, function ($pluginCode) use ($plugins) {
return isset($plugins[$pluginCode]);
$depends = array_filter($depends, function ($pluginCode) {
return isset($this->plugins[$pluginCode]);
});
/*
@ -746,7 +730,46 @@ class PluginManager
unset($checklist[$code]);
}
}
return $result;
/*
* Reassemble plugin map
*/
$sortedPlugins = [];
foreach ($result as $code) {
$sortedPlugins[$code] = $this->plugins[$code];
}
return $this->plugins = $sortedPlugins;
}
/**
* Returns the plugin identifiers that are required by the supplied plugin.
* @param string $plugin Plugin identifier, object or class
* @return array
*/
public function getDependencies($plugin)
{
if (is_string($plugin) && (!$plugin = $this->findByIdentifier($plugin))) {
return false;
}
if (!isset($plugin->require) || !$plugin->require) {
return null;
}
return is_array($plugin->require) ? $plugin->require : [$plugin->require];
}
/**
* @deprecated Plugins are now sorted by default. See getPlugins()
* Remove if year >= 2022
*/
public function sortByDependencies($plugins = null)
{
traceLog('PluginManager::sortByDependencies is deprecated. Plugins are now sorted by default. Use PluginManager::getPlugins()');
return array_keys($plugins ?: $this->getPlugins());
}
//

View File

@ -149,9 +149,9 @@ class UpdateManager
/*
* Update plugins
*/
$plugins = $this->pluginManager->sortByDependencies();
foreach ($plugins as $plugin) {
$this->updatePlugin($plugin);
$plugins = $this->pluginManager->getPlugins();
foreach ($plugins as $code => $plugin) {
$this->updatePlugin($code);
}
Parameter::set('system::update.count', 0);