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.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)
#

View file

@ -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

View file

@ -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):

View file

@ -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

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.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)

View file

@ -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