aboutsummaryrefslogtreecommitdiff
path: root/src/ModuleManager.py
diff options
context:
space:
mode:
authorGravatar jesopo2019-10-14 12:55:32 +0100
committerGravatar jesopo2019-10-14 12:56:04 +0100
commita9111c724148634486524f3744c3bbe039f3cc1d (patch)
treeaaffef8d9b6923a921c0406ca63c28ccb5b35f20 /src/ModuleManager.py
parentrefactor out chosing loadable modules in to _list_valid_modules() (diff)
signature
add ModuleManager.try_reload_modules(), to try reloading in a transaction
if any of the modules fails to reload, rollback and use the already loaded modules. closes #179
Diffstat (limited to 'src/ModuleManager.py')
-rw-r--r--src/ModuleManager.py67
1 files changed, 46 insertions, 21 deletions
diff --git a/src/ModuleManager.py b/src/ModuleManager.py
index bbfcc618..74ef7d01 100644
--- a/src/ModuleManager.py
+++ b/src/ModuleManager.py
@@ -36,6 +36,11 @@ class ModuleType(enum.Enum):
FILE = 0
DIRECTORY = 1
+class TryReloadResult(object):
+ def __init__(self, success: bool, message: str):
+ self.success = success
+ self.message = message
+
class BaseModule(object):
def __init__(self,
bot: "IRCBot.Bot",
@@ -155,8 +160,8 @@ class ModuleManager(object):
for directory in self.directories:
paths.append(os.path.join(directory, name))
return paths
- def _import_name(self, name: str) -> str:
- return "bitbot_%s" % name
+ def _import_name(self, name: str, context: str) -> str:
+ return "%s_%s" % (name, context)
def from_context(self, context: str) -> typing.Optional[LoadedModule]:
for module in self.modules.values():
@@ -198,7 +203,9 @@ class ModuleManager(object):
self._check_hashflags(bot, definition)
- import_name = self._import_name(definition.name)
+ context = str(uuid.uuid4())
+ import_name = self._import_name(definition.name, context)
+
import_spec = importlib.util.spec_from_file_location(import_name,
definition.filename)
module = importlib.util.module_from_spec(import_spec)
@@ -214,7 +221,6 @@ class ModuleManager(object):
raise ModuleLoadException("module '%s' has a 'Module' attribute "
"but it is not a class." % definition.name)
- context = str(uuid.uuid4())
context_events = self.events.new_context(context)
context_exports = self.exports.new_context(context)
context_timers = self.timers.new_context(context)
@@ -316,39 +322,27 @@ class ModuleManager(object):
def load_modules(self, bot: "IRCBot.Bot", whitelist: typing.List[str]=[],
blacklist: typing.List[str]=[], safe: bool=False
) -> typing.Tuple[typing.List[str], typing.List[str]]:
- fail = []
- success = []
-
loadable, nonloadable = self._list_valid_modules(bot, whitelist, blacklist)
for definition in nonloadable:
self.log.warn("Not loading module '%s'", [definition.name])
for definition in loadable:
- try:
- self.load_module(bot, definition)
- except ModuleWarning:
- fail.append(definition.name)
- continue
- except Exception as e:
- if safe:
- fail.append(definition.name)
- continue
- else:
- raise
- success.append(definition.name)
- return success, fail
+ self.load_module(bot, definition)
def unload_module(self, name: str):
if not name in self.modules:
raise ModuleNotLoadedException(name)
loaded_module = self.modules[name]
+ self._unload_module(loaded_module)
+ del self.modules[loaded_module.name]
+
+ def _unload_module(self, loaded_module: LoadedModule):
if hasattr(loaded_module.module, "unload"):
try:
loaded_module.module.unload()
except:
pass
- del self.modules[loaded_module.name]
context = loaded_module.context
self.events.purge_context(context)
@@ -378,6 +372,37 @@ class ModuleManager(object):
[loaded_module.name,
", ".join([str(referrer) for referrer in referrers])])
+ def try_reload_modules(self, bot: "IRCBot.Bot",
+ whitelist: typing.List[str], blacklist: typing.List[str]):
+ loadable, nonloadable = self._list_valid_modules(
+ bot, whitelist, blacklist)
+
+ old_modules = self.modules
+ self.modules = {}
+
+ failed = None
+ for definition in loadable:
+ try:
+ self.load_module(bot, definition)
+ except Exception as e:
+ failed = (definition, e)
+ break
+
+ if not failed == None:
+ for module in self.modules.values():
+ self._unload_module(module)
+ self.modules = old_modules
+
+ definition, exception = failed
+ return TryReloadResult(False,
+ "Failed to load %s (%s), rolling back reload" %
+ (definition.name, str(exception)))
+ else:
+ for module in old_modules.values():
+ self._unload_module(module)
+ return TryReloadResult(True, "Reloaded %d modules" %
+ len(self.modules.keys()))
+
def _list_valid_modules(self, bot: "IRCBot.Bot",
whitelist: typing.List[str], blacklist: typing.List[str]):
module_definitions = self.list_modules()