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.
This commit is contained in:
davewiththenicehat 2021-04-18 00:43:09 -04:00
parent c7bf773605
commit 97f7806348
6 changed files with 86 additions and 41 deletions

View file

@ -36,6 +36,7 @@ TickerHandler might be better.
""" """
from evennia import DefaultExit, utils, Command 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} 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)) traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed))
# create a delayed movement # 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 # we store the deferred on the character, this will allow us
# to abort the movement. We must use an ndb here since # to abort the movement. We must use an ndb here since
# deferreds cannot be pickled. # deferreds cannot be pickled.
traversing_object.ndb.currently_moving = deferred traversing_object.ndb.currently_moving = _TASK_HANDLER.get_deferred(task_id)
# #

View file

@ -1152,13 +1152,7 @@ from evennia.contrib import slow_exit
slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0} 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): class TestSlowExit(CommandTest):
@patch("evennia.utils.delay", _cancellable_mockdelay)
def test_exit(self): def test_exit(self):
exi = create_object( exi = create_object(
slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2 slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2

View file

@ -24,6 +24,7 @@ import random
from evennia import DefaultObject, DefaultExit, Command, CmdSet from evennia import DefaultObject, DefaultExit, Command, CmdSet
from evennia.utils import search, delay, dedent from evennia.utils import search, delay, dedent
from evennia.prototypes.spawner import spawn 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 # start the burn timer. When it runs out, self._burnout
# will be called. We store the deferred so it can be # will be called. We store the deferred so it can be
# killed in unittesting. # 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 return True
@ -687,7 +689,8 @@ class CrumblingWall(TutorialObject, DefaultExit):
self.db.exit_open = True self.db.exit_open = True
# start a 45 second timer before closing again. We store the deferred so it can be # start a 45 second timer before closing again. We store the deferred so it can be
# killed in unittesting. # 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 return True
def _translate_position(self, root, ipos): def _translate_position(self, root, ipos):

View file

@ -44,7 +44,6 @@ class TaskHandler(object):
Dev notes: Dev notes:
deferLater creates an instance of IDelayedCall using reactor.callLater. deferLater creates an instance of IDelayedCall using reactor.callLater.
deferLater uses the cancel method on the IDelayedCall instance to create deferLater uses the cancel method on the IDelayedCall instance to create
the defer instance it returns.
""" """
@ -79,16 +78,18 @@ class TaskHandler(object):
continue continue
callback = getattr(obj, method) 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: if to_save:
self.save() self.save()
def save(self): def save(self):
"""Save the tasks in ServerConfig.""" """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: if task_id in self.to_save:
continue continue
if not persistent:
continue
if getattr(callback, "__self__", None): if getattr(callback, "__self__", None):
# `callback` is an instance method # `callback` is an instance method
@ -127,8 +128,8 @@ class TaskHandler(object):
any (any): additional keyword arguments to send to the callback any (any): additional keyword arguments to send to the callback
Returns: Returns:
twisted.internet.defer.Deferred instance of the deferred task
task_id (int), the task's id intended for use with this class. task_id (int), the task's id intended for use with this class.
False, if the task has completed before addition finishes.
Notes: Notes:
This method has two return types. This method has two return types.
@ -144,19 +145,23 @@ class TaskHandler(object):
As those memory references will no longer acurately point to As those memory references will no longer acurately point to
the variable desired. 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) persistent = kwargs.get("persistent", False)
if persistent: if persistent:
del kwargs["persistent"] del kwargs["persistent"]
now = datetime.now()
delta = timedelta(seconds=timedelay)
# Choose a free task_id
safe_args = [] safe_args = []
safe_kwargs = {} 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 # Check that args and kwargs contain picklable information
for arg in args: for arg in args:
@ -183,17 +188,28 @@ class TaskHandler(object):
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] = (comp_time, callback, safe_args, safe_kwargs, True, None)
self.save() self.save()
callback = self.do_task else: # this is a non-persitent task
args = [task_id] self.tasks[task_id] = (comp_time, callback, args, kwargs, True, None)
kwargs = {}
deferLater(self.clock, timedelay, callback, *args, **kwargs)
return task_id
# defer the task
callback = self.do_task
args = [task_id]
kwargs = {}
d = deferLater(self.clock, timedelay, callback, *args, **kwargs) d = deferLater(self.clock, timedelay, callback, *args, **kwargs)
d.addErrback(handle_error) 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): def remove(self, task_id):
"""Remove a persistent task without executing it. """Remove a persistent task without executing it.
@ -206,7 +222,8 @@ class TaskHandler(object):
in the TaskHandler. 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: if task_id in self.to_save:
del self.to_save[task_id] del self.to_save[task_id]
@ -222,13 +239,30 @@ class TaskHandler(object):
This will also remove it from the list of current tasks. 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: if task_id in self.to_save:
del self.to_save[task_id] del self.to_save[task_id]
self.save() self.save()
callback(*args, **kwargs) 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): def create_delays(self):
"""Create the delayed tasks for the persistent tasks. """Create the delayed tasks for the persistent tasks.
@ -237,9 +271,14 @@ class TaskHandler(object):
""" """
now = datetime.now() 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()) 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 # Create the soft singleton

View file

@ -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.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi from evennia.utils import ansi
from evennia.utils.utils import to_bytes from evennia.utils.utils import to_bytes
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
_RE_N = re.compile(r"\|n$") _RE_N = re.compile(r"\|n$")
_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _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 from evennia.utils.utils import delay
# timeout the handshakes in case the client doesn't reply at all task_id = delay(2, callback=self.handshake_done, timeout=True)
self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True) self._handshake_delay = _TASK_HANDLER.get_deferred(task_id)
# TCP/IP keepalive watches for dead links # TCP/IP keepalive watches for dead links
self.transport.setTcpKeepAlive(1) self.transport.setTcpKeepAlive(1)

View file

@ -323,24 +323,31 @@ class TestDelay(EvenniaTest):
def test_delay(self): def test_delay(self):
# get a reference of TASK_HANDLER # get a reference of TASK_HANDLER
timedelay = 5
global _TASK_HANDLER global _TASK_HANDLER
if _TASK_HANDLER is None: if _TASK_HANDLER is None:
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
_TASK_HANDLER.clock = task.Clock() _TASK_HANDLER.clock = task.Clock()
self.char1.ndb.dummy_var = False self.char1.ndb.dummy_var = False
# test a persistent deferral, that completes after delay time # test a persistent deferral, that completes after delay time
task_id = utils.delay(1, dummy_func, self.char1.dbref, persistent=True) task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True)
_TASK_HANDLER.clock.advance(1) # make time pass _TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False self.char1.ndb.dummy_var = False
# test a non persisten deferral, that completes after delay time. # test a non persisten deferral, that completes after delay time.
deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) utils.delay(timedelay, dummy_func, self.char1.dbref)
_TASK_HANDLER.clock.advance(1) # make time pass _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.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False self.char1.ndb.dummy_var = False
# test canceling a deferral. # 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() 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.assertEqual(self.char1.ndb.dummy_var, False)
self.char1.ndb.dummy_var = False self.char1.ndb.dummy_var = False