Place the persistent task in a task handler

This commit is contained in:
Vincent Le Goff 2017-08-08 19:47:22 +02:00
parent e0eb490814
commit 37c9d65a9d
3 changed files with 72 additions and 69 deletions

View file

@ -1,7 +1,5 @@
""" """
Module containing persistent features and the persistent differed Module containing the task handler for Evennia deferred tasks, persistent or not.
tasks, generated by utils.delay(persistent=True).
""" """
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -11,17 +9,20 @@ from evennia.server.models import ServerConfig
from evennia.utils.logger import log_trace, log_err from evennia.utils.logger import log_trace, log_err
from evennia.utils.dbserialize import dbserialize, dbunserialize from evennia.utils.dbserialize import dbserialize, dbunserialize
PERSISTENT_TASKS = None TASK_HANDLER = None
class PersistentTasks(object): class TaskHandler(object):
"""A light singleton wrapper allowing to access permanent tasks. """
A light singleton wrapper allowing to access permanent tasks.
When `utils.delay` is called, the task handler is used to create
the task. If `utils.delay` is called with `persistent=True`, the
task handler stores the new task and saves.
Permament tasks are created by `utils.delay` when the `persistent`
keyword argument is set to True. Tasks are saved in ServerConfig.
It's easier to access these tasks (should it be necessary) using It's easier to access these tasks (should it be necessary) using
`evennia.utils.persistent.PERSISTSENT_TASKS`, which contains one `evennia.scripts.taskhandler.TASK_HANDLER`, which contains one
instance of this class, and use its `add and `remove` methods. instance of this class, and use its `add` and `remove` methods.
""" """
@ -88,56 +89,63 @@ class PersistentTasks(object):
any (any): any additional positional arguments to send to the callback any (any): any additional positional arguments to send to the callback
Kwargs: Kwargs:
persistent (bool, optional): persist the task (store it).
any (any): additional keyword arguments to send to the callback any (any): additional keyword arguments to send to the callback
Note:
This doesn't create any delay at this time to call the task,
but the task is written in ServerConfig along with the
others.
""" """
now = datetime.now() persistent = kwargs.get("persistent", False)
delta = timedelta(seconds=timedelay) if persistent:
del kwargs["persistent"]
now = datetime.now()
delta = timedelta(seconds=timedelay)
# Choose a free task_id # Choose a free task_id
safe_args = [] safe_args = []
safe_kwargs = {} safe_kwargs = {}
used_ids = self.tasks.keys() used_ids = self.tasks.keys()
task_id = 1 task_id = 1
while task_id in used_ids: while task_id in used_ids:
task_id += 1 task_id += 1
# Check that args and kwargs contain picklable information # Check that args and kwargs contain picklable information
for arg in args: for arg in args:
try: try:
dbserialize(arg) dbserialize(arg)
except (TypeError, AttributeError): except (TypeError, AttributeError):
logger.log_err("The positional argument {} cannot be " \ logger.log_err("The positional argument {} cannot be " \
"pickled and will not be present in the arguments " \ "pickled and will not be present in the arguments " \
"fed to the callback {}".format(arg, callback)) "fed to the callback {}".format(arg, callback))
else: else:
safe_args.append(arg) safe_args.append(arg)
for key, value in kwargs.items(): for key, value in kwargs.items():
try: try:
dbserialize(value) dbserialize(value)
except (TypeError, AttributeError): except (TypeError, AttributeError):
logger.log_err("The {} keyword argument {} cannot be " \ logger.log_err("The {} keyword argument {} cannot be " \
"pickled and will not be present in the arguments " \ "pickled and will not be present in the arguments " \
"fed to the callback {}".format(key, value, callback)) "fed to the callback {}".format(key, value, callback))
else: else:
safe_kwargs[key] = value safe_kwargs[key] = value
self.tasks[task_id] = (now + delta, callback, safe_args, safe_kwargs) self.tasks[task_id] = (now + delta, callback, safe_args, safe_kwargs)
self.save() self.save()
return task_id callback = self.do_task
args = [task_id]
kwargs = {}
return task.deferLater(reactor, timedelay, callback, *args, **kwargs)
def remove(self, task_id): def remove(self, task_id):
"""Remove a task without executing it. """Remove a persistent task without executing it.
Args: Args:
task_id (int): an existing task ID. task_id (int): an existing task ID.
Note:
A non-persistent task doesn't have a task_id, it is not stored
in the TaskHandler.
""" """
del self.tasks[task_id] del self.tasks[task_id]
if task_id in self.to_save: if task_id in self.to_save:
@ -179,4 +187,5 @@ class PersistentTasks(object):
# Create the soft singleton # Create the soft singleton
PERSISTENT_TASKS = PersistentTasks() TASK_HANDLER = TaskHandler()

View file

@ -455,10 +455,10 @@ class Evennia(object):
# (this also starts any that didn't yet start) # (this also starts any that didn't yet start)
ScriptDB.objects.validate(init_mode=mode) ScriptDB.objects.validate(init_mode=mode)
# start the persistent tasks # start the task handler
from evennia.utils.persistent import PERSISTENT_TASKS from evennia.scripts.taskhandler import TASK_HANDLER
PERSISTENT_TASKS.load() TASK_HANDLER.load()
PERSISTENT_TASKS.create_delays() TASK_HANDLER.create_delays()
# delete the temporary setting # delete the temporary setting
ServerConfig.objects.conf("server_restart_mode", delete=True) ServerConfig.objects.conf("server_restart_mode", delete=True)

View file

@ -919,7 +919,7 @@ def uses_database(name="sqlite3"):
return engine == "django.db.backends.%s" % name return engine == "django.db.backends.%s" % name
_PERSISTENT_TASKS = None _TASK_HANDLER = None
def delay(timedelay, callback, *args, **kwargs): def delay(timedelay, callback, *args, **kwargs):
""" """
@ -931,9 +931,9 @@ def delay(timedelay, callback, *args, **kwargs):
arguments after `timedelay` seconds. arguments after `timedelay` seconds.
args (any, optional): Will be used as arguments to callback args (any, optional): Will be used as arguments to callback
Kwargs: Kwargs:
persistent (bool, optional): should make the delay persistent persistent (bool, optional): should make the delay persistent
over a reboot or reload over a reboot or reload
any (any): Will be used to call the callback. any (any): Will be used to call the callback.
Returns: Returns:
deferred (deferred): Will fire fire with callback after deferred (deferred): Will fire fire with callback after
@ -943,6 +943,8 @@ def delay(timedelay, callback, *args, **kwargs):
specified here. specified here.
Note: Note:
The task handler (`evennia.scripts.taskhandler.TASK_HANDLEr`) will
be called for persistent or non-persistent tasks.
If persistent is set to True, the callback, its arguments If persistent is set to True, the callback, its arguments
and other keyword arguments will be saved in the database, and other keyword arguments will be saved in the database,
assuming they can be. The callback will be executed even after assuming they can be. The callback will be executed even after
@ -950,19 +952,11 @@ def delay(timedelay, callback, *args, **kwargs):
(and server down time). (and server down time).
""" """
global _PERSISTENT_TASKS global _TASK_HANDLER
persistent = kwargs.get("persistent", False) # Do some imports here to avoid circular import and speed things up
if persistent: if _TASK_HANDLER is None:
del kwargs["persistent"] from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
# Do some imports here to avoid circular import and speed things up return _TASK_HANDLER.add(timedelay, callback, *args, **kwargs)
if _PERSISTENT_TASKS is None:
from evennia.utils.persistent import PERSISTENT_TASKS as _PERSISTENT_TASKS
task_id = _PERSISTENT_TASKS.add(timedelay, callback, *args, **kwargs)
callback = _PERSISTENT_TASKS.do_task
args = [task_id]
kwargs = {}
return task.deferLater(reactor, timedelay, callback, *args, **kwargs)
_TYPECLASSMODELS = None _TYPECLASSMODELS = None