Skip to content

Commit

Permalink
fix: activate plugins in deterministic order
Browse files Browse the repository at this point in the history
  • Loading branch information
browniebroke committed Jan 30, 2021
1 parent de02cc8 commit d7cf318
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 2 deletions.
35 changes: 33 additions & 2 deletions errbot/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import sys
import traceback
from copy import deepcopy
from importlib import machinery
from graphlib import CycleError
from graphlib import TopologicalSorter as BaseTopologicalSorter
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type

Expand Down Expand Up @@ -165,6 +166,12 @@ def check_errbot_version(plugin_info: PluginInfo):
BL_PLUGINS = "bl_plugins"


class TopologicalSorter(BaseTopologicalSorter):
def find_cycle(self):
"""Wraps private method as public one."""
return self._find_cycle()


class BotPluginManager(StoreMixin):
def __init__(
self,
Expand Down Expand Up @@ -420,7 +427,8 @@ def activate_non_started_plugins(self):
"""
log.info("Activate bot plugins...")
errors = ""
for name, plugin in self.plugins.items():
for name in self.get_plugins_activation_order():
plugin = self.plugins.get(name)
try:
if self.is_plugin_blacklisted(name):
errors += (
Expand All @@ -446,6 +454,29 @@ def activate_non_started_plugins(self):
errors += f"Error: flow {name} failed to start: {e}.\n"
return errors

def get_plugins_activation_order(self) -> List[str]:
"""
Calculate plugin activation order, based on their dependencies.
:return: list of plugin names, in the best order to start them.
"""
plugins_graph = {
name: set(info.dependencies) for name, info in self.plugin_infos.items()
}
plugins_in_cycle = set()
while 1:
plugins_sorter = TopologicalSorter(plugins_graph)
try:
# Return plugins which are part of a circular dependency at the end,
# the rest of the code expects to have all plugins returned
return list(plugins_sorter.static_order()) + list(plugins_in_cycle)
except CycleError:
# Remove cycle from the graph, and
cycle = set(plugins_sorter.find_cycle())
plugins_in_cycle.update(cycle)
for plugin_name in cycle:
plugins_graph.pop(plugin_name)

def _activate_plugin(self, plugin: BotPlugin, plugin_info: PluginInfo):
"""
Activate a specific plugin with no check.
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"deepmerge>=0.1.0",
]

if py_version < (3, 9):
deps.append("graphlib-backport")

src_root = os.curdir


Expand Down

0 comments on commit d7cf318

Please sign in to comment.