From 97f7806348bb2c0bc8ef18ed7116804f9ccffdc9 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:43:09 -0400 Subject: [PATCH] task handler, updated to only return task id Updated task handler to only return task id. updated code within evennia that relied on the deferral directly. Including unit test for one. all unit tests pass. Test server functional after restarting, no issues found would telnet web client. (delay was used in the telnet module in the portal folder. I needed to commit this before continuing forward. There is already a high line count change. --- evennia/contrib/slow_exit.py | 5 +- evennia/contrib/tests.py | 6 -- evennia/contrib/tutorial_world/objects.py | 7 +- evennia/scripts/taskhandler.py | 85 +++++++++++++++++------ evennia/server/portal/telnet.py | 5 +- evennia/utils/tests/test_utils.py | 19 +++-- 6 files changed, 86 insertions(+), 41 deletions(-) diff --git a/evennia/contrib/slow_exit.py b/evennia/contrib/slow_exit.py index 107d274b8c..3b060f7471 100644 --- a/evennia/contrib/slow_exit.py +++ b/evennia/contrib/slow_exit.py @@ -36,6 +36,7 @@ TickerHandler might be better. """ from evennia import DefaultExit, utils, Command +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1} @@ -70,11 +71,11 @@ class SlowExit(DefaultExit): traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed)) # create a delayed movement - deferred = utils.delay(move_delay, move_callback) + task_id = utils.delay(move_delay, move_callback) # we store the deferred on the character, this will allow us # to abort the movement. We must use an ndb here since # deferreds cannot be pickled. - traversing_object.ndb.currently_moving = deferred + traversing_object.ndb.currently_moving = _TASK_HANDLER.get_deferred(task_id) # diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index a4e50a29f7..7b69d07cc9 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1152,13 +1152,7 @@ from evennia.contrib import slow_exit slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0} -def _cancellable_mockdelay(time, callback, *args, **kwargs): - callback(*args, **kwargs) - return Mock() - - class TestSlowExit(CommandTest): - @patch("evennia.utils.delay", _cancellable_mockdelay) def test_exit(self): exi = create_object( slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2 diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index ca4147a715..759d1f1dc0 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -24,6 +24,7 @@ import random from evennia import DefaultObject, DefaultExit, Command, CmdSet from evennia.utils import search, delay, dedent from evennia.prototypes.spawner import spawn +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER # ------------------------------------------------------------- # @@ -389,7 +390,8 @@ class LightSource(TutorialObject): # start the burn timer. When it runs out, self._burnout # will be called. We store the deferred so it can be # killed in unittesting. - self.deferred = delay(60 * 3, self._burnout) + task_id = delay(60 * 3, self._burnout) + self.deferred = _TASK_HANDLER.get_deferred(task_id) return True @@ -687,7 +689,8 @@ class CrumblingWall(TutorialObject, DefaultExit): self.db.exit_open = True # start a 45 second timer before closing again. We store the deferred so it can be # killed in unittesting. - self.deferred = delay(45, self.reset) + task_id = delay(45, self.reset) + self.deferred = _TASK_HANDLER.get_deferred(task_id) return True def _translate_position(self, root, ipos): diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 3273e27e8a..b865a74185 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -44,7 +44,6 @@ class TaskHandler(object): Dev notes: deferLater creates an instance of IDelayedCall using reactor.callLater. deferLater uses the cancel method on the IDelayedCall instance to create - the defer instance it returns. """ @@ -79,16 +78,18 @@ class TaskHandler(object): continue callback = getattr(obj, method) - self.tasks[task_id] = (date, callback, args, kwargs) + self.tasks[task_id] = date, callback, args, kwargs, True, None if to_save: self.save() def save(self): """Save the tasks in ServerConfig.""" - for task_id, (date, callback, args, kwargs) in self.tasks.items(): + for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): if task_id in self.to_save: continue + if not persistent: + continue if getattr(callback, "__self__", None): # `callback` is an instance method @@ -127,8 +128,8 @@ class TaskHandler(object): any (any): additional keyword arguments to send to the callback Returns: - twisted.internet.defer.Deferred instance of the deferred task task_id (int), the task's id intended for use with this class. + False, if the task has completed before addition finishes. Notes: This method has two return types. @@ -144,19 +145,23 @@ class TaskHandler(object): As those memory references will no longer acurately point to the variable desired. """ + # set the completion time + # Only used on persistent tasks after a restart + now = datetime.now() + delta = timedelta(seconds=timedelay) + comp_time = now + delta + # get an open task id + used_ids = list(self.tasks.keys()) + task_id = 1 + while task_id in used_ids: + task_id += 1 + + # record the task to the tasks dictionary persistent = kwargs.get("persistent", False) if persistent: del kwargs["persistent"] - now = datetime.now() - delta = timedelta(seconds=timedelay) - - # Choose a free task_id safe_args = [] safe_kwargs = {} - used_ids = list(self.tasks.keys()) - task_id = 1 - while task_id in used_ids: - task_id += 1 # Check that args and kwargs contain picklable information for arg in args: @@ -183,17 +188,28 @@ class TaskHandler(object): else: safe_kwargs[key] = value - self.tasks[task_id] = (now + delta, callback, safe_args, safe_kwargs) + self.tasks[task_id] = (comp_time, callback, safe_args, safe_kwargs, True, None) self.save() - callback = self.do_task - args = [task_id] - kwargs = {} - deferLater(self.clock, timedelay, callback, *args, **kwargs) - return task_id + else: # this is a non-persitent task + self.tasks[task_id] = (comp_time, callback, args, kwargs, True, None) + # defer the task + callback = self.do_task + args = [task_id] + kwargs = {} d = deferLater(self.clock, timedelay, callback, *args, **kwargs) d.addErrback(handle_error) - return d + + # some tasks may complete before the deferal can be added + if task_id in self.tasks: + task = self.tasks.get(task_id) + task = list(task) + task[4] = persistent + task[5] = d + self.tasks[task_id] = task + else: # the task already completed + return False + return task_id def remove(self, task_id): """Remove a persistent task without executing it. @@ -206,7 +222,8 @@ class TaskHandler(object): in the TaskHandler. """ - del self.tasks[task_id] + if task_id in self.tasks: + del self.tasks[task_id] if task_id in self.to_save: del self.to_save[task_id] @@ -222,13 +239,30 @@ class TaskHandler(object): This will also remove it from the list of current tasks. """ - date, callback, args, kwargs = self.tasks.pop(task_id) + date, callback, args, kwargs, persistent, d = self.tasks.pop(task_id) + if task_id in self.to_save: del self.to_save[task_id] self.save() callback(*args, **kwargs) + def get_deferred(self, task_id): + """ + Return the instance of the deferred the task id is using. + + Args: + task_id (int): a valid task ID. + + Returns: + An instance of a deferral or False if there is no task with the id. + None is returned if there is no deferral affiliated with this id. + """ + if task_id in self.tasks: + return self.tasks[task_id][5] + else: + return False + def create_delays(self): """Create the delayed tasks for the persistent tasks. @@ -237,9 +271,14 @@ class TaskHandler(object): """ now = datetime.now() - for task_id, (date, callbac, args, kwargs) in self.tasks.items(): + for task_id, (date, callbac, args, kwargs, _, _) in self.tasks.items(): + self.tasks[task_id] = date, callbac, args, kwargs, True, None seconds = max(0, (date - now).total_seconds()) - deferLater(self.clock, seconds, self.do_task, task_id) + d = deferLater(self.clock, seconds, self.do_task, task_id) + d.addErrback(handle_error) + # some tasks may complete before the deferal can be added + if self.tasks.get(task_id, False): + self.tasks[task_id] = date, callbac, args, kwargs, True, d # Create the soft singleton diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 821e96aa24..42facf7e65 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -31,6 +31,7 @@ from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.utils import ansi from evennia.utils.utils import to_bytes +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _RE_N = re.compile(r"\|n$") _RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) @@ -127,8 +128,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): from evennia.utils.utils import delay - # timeout the handshakes in case the client doesn't reply at all - self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True) + task_id = delay(2, callback=self.handshake_done, timeout=True) + self._handshake_delay = _TASK_HANDLER.get_deferred(task_id) # TCP/IP keepalive watches for dead links self.transport.setTcpKeepAlive(1) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index bebea149ce..ae4d49e90f 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -323,24 +323,31 @@ class TestDelay(EvenniaTest): def test_delay(self): # get a reference of TASK_HANDLER + timedelay = 5 global _TASK_HANDLER if _TASK_HANDLER is None: from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _TASK_HANDLER.clock = task.Clock() self.char1.ndb.dummy_var = False # test a persistent deferral, that completes after delay time - task_id = utils.delay(1, dummy_func, self.char1.dbref, persistent=True) - _TASK_HANDLER.clock.advance(1) # make time pass + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, that completes after delay time. - deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) - _TASK_HANDLER.clock.advance(1) # make time pass + utils.delay(timedelay, dummy_func, self.char1.dbref) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False + # test a non persisten deferral, with a short timedelay + utils.delay(.1, dummy_func, self.char1.dbref) + _TASK_HANDLER.clock.advance(.1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test canceling a deferral. - deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + deferal_inst = _TASK_HANDLER.get_deferred(task_id) deferal_inst.cancel() - _TASK_HANDLER.clock.advance(1) # make time pass + _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.char1.ndb.dummy_var = False