Fix edge case in TaskHandler when un-pickleable callable supplied

This commit is contained in:
Griatch 2021-05-09 15:25:57 +02:00
parent cec566be79
commit bf4af8b208
2 changed files with 27 additions and 26 deletions

View file

@ -5,6 +5,7 @@ Module containing the task handler for Evennia deferred tasks, persistent or not
from datetime import datetime, timedelta from datetime import datetime, timedelta
from twisted.internet import reactor from twisted.internet import reactor
from pickle import PickleError
from twisted.internet.task import deferLater from twisted.internet.task import deferLater
from twisted.internet.defer import CancelledError as DefCancelledError from twisted.internet.defer import CancelledError as DefCancelledError
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
@ -287,18 +288,8 @@ class TaskHandler(object):
# Check if callback can be pickled. args and kwargs have been checked # Check if callback can be pickled. args and kwargs have been checked
safe_callback = None safe_callback = None
try:
dbserialize(callback)
except (TypeError, AttributeError):
raise ValueError(
"the specified callback {} cannot be pickled. "
"It must be a top-level function in a module or an "
"instance method.".format(callback)
)
else:
safe_callback = callback
self.to_save[task_id] = dbserialize((date, safe_callback, args, kwargs)) self.to_save[task_id] = dbserialize((date, callback, args, kwargs))
ServerConfig.objects.conf("delayed_tasks", self.to_save) ServerConfig.objects.conf("delayed_tasks", self.to_save)
def add(self, timedelay, callback, *args, **kwargs): def add(self, timedelay, callback, *args, **kwargs):
@ -318,8 +309,8 @@ class TaskHandler(object):
any (any): any additional positional arguments to send to the callback any (any): any additional positional arguments to send to the callback
*args: positional arguments to pass to callback. *args: positional arguments to pass to callback.
**kwargs: keyword arguments to pass to callback. **kwargs: keyword arguments to pass to callback.
persistent (bool, optional): persist the task (stores it). - persistent (bool, optional): persist the task (stores it).
persistent key and value is removed from kwargs it will Persistent key and value is removed from kwargs it will
not be passed to callback. not be passed to callback.
Returns: Returns:
@ -346,11 +337,22 @@ class TaskHandler(object):
safe_args = [] safe_args = []
safe_kwargs = {} safe_kwargs = {}
# an unsaveable callback should immediately abort
try:
dbserialize(callback)
except (TypeError, AttributeError, PickleError):
raise ValueError(
"the specified callback {} cannot be pickled. "
"It must be a top-level function in a module or an "
"instance method.".format(callback)
)
return
# 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, PickleError):
log_err( log_err(
"The positional argument {} cannot be " "The positional argument {} cannot be "
"pickled and will not be present in the arguments " "pickled and will not be present in the arguments "
@ -362,7 +364,7 @@ class TaskHandler(object):
for key, value in kwargs.items(): for key, value in kwargs.items():
try: try:
dbserialize(value) dbserialize(value)
except (TypeError, AttributeError): except (TypeError, AttributeError, PickleError):
log_err( log_err(
"The {} keyword argument {} cannot be " "The {} keyword argument {} cannot be "
"pickled and will not be present in the arguments " "pickled and will not be present in the arguments "

View file

@ -1030,20 +1030,19 @@ def delay(timedelay, callback, *args, **kwargs):
after `timedelay` seconds. after `timedelay` seconds.
args (any, optional): Will be used as arguments to callback args (any, optional): Will be used as arguments to callback
Keyword Args: Keyword Args:
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. Defaults to False.
any (any): Will be used as keyword arguments to callback. any (any): Will be used as keyword arguments to callback.
Returns: Returns:
deferred (deferred): Will fire with callback after deferred or int: If ``persistent`` kwarg is `False`, return deferred
`timedelay` seconds. Note that if `timedelay()` is used in the that will fire with callback after `timedelay` seconds. Note that
commandhandler callback chain, the callback chain can be if `timedelay()` is used in the commandhandler callback chain, the
defined directly in the command body and don't need to be callback chain can be defined directly in the command body and
specified here. don't need to be specified here. Reference twisted.internet.defer.Deferred.
Reference twisted.internet.defer.Deferred If persistent kwarg is set, return the task's ID as an integer. This is
if persistent kwarg is truthy: intended for use with ``evennia.scripts.taskhandler.TASK_HANDLER``
task_id (int): the task's id intended for use with `.do_task` and `.remove` methods.
evennia.scripts.taskhandler.TASK_HANDLER's do_task and remove methods.
Note: Note:
The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will