Skip to content

Commit

Permalink
Merge pull request #376 from georgejecook/fix/delayed-task-manager-do…
Browse files Browse the repository at this point in the history
…es-not-cancel-tasks

Fix/delayed task manager does not cancel tasks
  • Loading branch information
georgejecook authored May 12, 2023
2 parents dad3a84 + a4b604e commit 48b7e1e
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 22 deletions.
64 changes: 42 additions & 22 deletions src/source/core/DelayedTaskManager.bs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "pkg:/source/core/Tasks.bs"
import "pkg:/source/core/Utils.bs"
import "pkg:/source/core/BaseClass.bs"
import "pkg:/source/view/NodeClass.bs"

namespace mc.tasks

Expand All @@ -9,7 +10,7 @@ namespace mc.tasks
' * @description allows certain tasks to be scheduled at a certain time. Useful for things such as refreshing tokens, etc
' */
@node("mc_DelayedTaskManager", "Group")
class DelayedTaskManager extends mc.BaseClass
class DelayedTaskManager extends mv.NodeClass

private activeTimers = {}
private delayedTasks = {}
Expand All @@ -19,28 +20,14 @@ namespace mc.tasks
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'++ Delayed task support
'++ Public Methods
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

function cancelDelayedTaskWithId(id as string)
m.log.info("requesting cancel of delayed task with id:", id)

timer = m.activeTimers[id]
if timer <> invalid
m.log.info("found timer - cancelling")
mc.tasks.cancelWaitTimer(timer)
m.activeTimers.delete(id)
end if

task = m.delayedTasks[id]
if task <> invalid
m.log.info("a task for this timer was already in flight: cancelling it now")
mc.tasks.cancelTask(task)
if task.getParent() <> invalid
task.getParent().removeChild(task)
end if
m.delayedTasks.delete(id)
end if
m.removeTimer(id)
m.removeTask(id)
end function

function scheduleDelayedTask(nodeType as string, id as string, delay as float, fields as mc.types.assocarray, resultField = "output" as string)
Expand All @@ -50,10 +37,17 @@ namespace mc.tasks
m.cancelDelayedTaskWithId(id)
end if

timer = mc.tasks.waitAFrame(m.onDelayedTaskTimerFire, delay, "node", m, m.top)
m.activeTimers[id] = m.createTimer(nodeType, id, delay, asAA(fields), resultField)
end function

fields = fields ?? {}
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'++ Private Methods
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

private function createTimer(nodeType as string, id as string, delay as float, fields as mc.types.assocarray, resultField = "output" as string)
fields.id = id

timer = m.setTimeout(m.onDelayedTaskTimerFire, delay, "node")
mc.setOrAddNodeFields(timer, {
id: id
taskInfo: {
Expand All @@ -62,12 +56,38 @@ namespace mc.tasks
resultField: resultField
}
})
return timer
end function

private function removeTimer(id as string)
timer = m.activeTimers[id]
if timer <> invalid
m.log.info("found timer - cancelling")
m.cancelTimeout(timer)
m.activeTimers.delete(id)
end if
end function

private function removeTask(id as string)
task = m.delayedTasks[id]
if task <> invalid
m.log.info("a task for this timer was already in flight: cancelling it now")
m.cancelTask(task)
if task.getParent() <> invalid
task.getParent().removeChild(task)
end if
m.delayedTasks.delete(id)
end if
end function

private function onDelayedTaskTimerFire(timer as mc.types.node)
taskInfo = timer.taskInfo
m.activeTimers.delete(timer.id)
m.delayedTasks[timer.id] = mc.tasks.createTask(taskInfo.nodeType, taskInfo.fields)
if taskInfo <> invalid
m.activeTimers.delete(timer.id)
m.delayedTasks[timer.id] = m.createTask(taskInfo.nodeType, taskInfo.fields)
else
m.log.error("ignoring unknown timer", timer.id, "it had no task info")
end if
end function

end class
Expand Down
234 changes: 234 additions & 0 deletions src/source/core/DelayedTaskManager.spec.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import "pkg:/source/tests/BaseTestSuite.spec.bs"
import "pkg:/source/core/DelayedTaskManager.bs"

namespace tests

@suite("DelayedTaskManager tests")
class DelayedTaskManagerTests extends tests.BaseTestSuite

private manager

protected override function beforeEach()
super.beforeEach()
m.manager = m.createNodeClass(mc.tasks.DelayedTaskManager)
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("constructor")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("has correct initial values")
function _()
m.assertEmpty(m.manager.activeTimers)
m.assertEmpty(m.manager.delayedTasks)
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("cancelDelayedTaskWithId")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("deletes timer and task")
@params("i1")
@params("i2")
function _(id)
m.expectCalled(m.manager.removeTimer(id))
m.expectCalled(m.manager.removeTask(id))

m.manager.cancelDelayedTaskWithId(id)
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("removeTimer")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("removes timer if present")
function _()

timer1 = { "id": "timer1" }
timer2 = { "id": "timer2" }
m.manager.activeTimers["i1"] = timer1
m.manager.activeTimers["i2"] = timer2

m.expectCalled(m.manager.cancelTimeout(timer1))
m.expectCalled(m.manager.cancelTimeout(timer2))

m.manager.removeTimer("i1")
m.assertEqual(m.manager.activeTimers, { i2: timer2 })
m.manager.removeTimer("i2")
m.assertEmpty(m.manager.activeTimers)
end function

@it("does not remove timer with unknown id")
function _()

timer1 = { "id": "timer1" }
timer2 = { "id": "timer2" }
m.manager.activeTimers["i1"] = timer1
m.manager.activeTimers["i2"] = timer2
m.expectNotCalled(m.manager.cancelTimeout)

m.manager.removeTimer("i3")
m.assertEqual(m.manager.activeTimers, { i1: timer1, i2: timer2 })
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("removeTask")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("does nothing if task is unknown")
function _()
task1 = { "id": "task1" }

m.manager.delayedTasks = {
"i1": task1
}

m.expectNotCalled(m.manager.cancelTask)
m.manager.removeTask("i3")

m.assertEqual(m.manager.delayedTasks, { i1: task1 })
end function

@it("removes the tasks and cancels it, also removing it from it's parent")
function _()
parent = mc.createSGNode("Group", invalid, "parent")
task1 = mc.createSGNode("Group", parent, "i1")
task2 = mc.createSGNode("Group", parent, "i2")

m.manager.delayedTasks = {
"i1": task1
"i2": task2
}

m.expectCalled(m.manager.cancelTask(task1))
m.expectCalled(m.manager.cancelTask(task2))

m.manager.removeTask("i1")
m.assertEqual(m.manager.delayedTasks, { i2: task2 })
m.assertInvalid(task1.getParent())

m.manager.removeTask("i2")
m.assertEmpty(m.manager.delayedTasks)
m.assertInvalid(task2.getParent())

end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("scheduleDelayedTask")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("cancels delayed task if present, and creates new task, plus sets timeout")
@params(true, invalid)
@params(invalid, true)
function _(activeTimer, delayedTask)
m.manager.activeTimers["i1"] = activeTimer
m.manager.delayedTasks["i1"] = delayedTask

m.expectCalled(m.manager.cancelDelayedTaskWithId("i1"))

timer = { "id": "timer" }
m.expectCalled(m.manager.createTimer("Group", "i1", 10, { data: true }, "output"), timer)

m.manager.scheduleDelayedTask("Group", "i1", 10, { data: true })
m.assertEqual(m.manager.activeTimers["i1"], timer)
end function

@it("does not cancels delayed task if not present, and creates new task, plus sets timeout")
function _()
m.manager.activeTimers["i1"] = invalid
m.manager.delayedTasks["i1"] = invalid

m.expectNotCalled(m.manager.cancelDelayedTaskWithId)

timer = { "id": "timer" }
m.expectCalled(m.manager.createTimer("Group", "i1", 10, { data: true }, "output"), timer)

m.manager.scheduleDelayedTask("Group", "i1", 10, { data: true })
m.assertEqual(m.manager.activeTimers["i1"], timer)

end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("createTimer")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("creates timer type and sets all fields")
function _()
group = mc.createSGNode("Group")
m.expectCalled(m.manager.setTimeout(m.manager.onDelayedTaskTimerFire, 10, "node"), group)

timer = m.manager.createTimer("Group", "i1", 10, { data: "data" }, "output")

m.assertEqual(timer, group)
m.assertEqual(timer.id, "i1")
m.assertEqual(timer.taskInfo, {
fields: { id: "i1", data: "data" }
nodeType: "Group"
resultField: "output"
})
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("onDelayedTaskTimerFire")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

@it("creates the delayed task and removes the active timer")
function _()
taskInfo = {
nodeType: "Group"
fields: { data: "data" }
}
timer1 = { "id": "i1", "taskInfo": taskInfo }
m.manager.activeTimers = { "i1": timer1 }

task = { "id": "task" }
m.expectCalled(m.manager.createTask("Group", { data: "data" }), task)

m.manager.onDelayedTaskTimerFire(timer1)

m.assertEqual(m.manager.delayedTasks["i1"], task)
m.assertEmpty(m.manager.activeTimers)
end function

@it("does not remove timers")
function _()
taskInfo = {
nodeType: "Group"
fields: { data: "data" }
}
timer1 = { "id": "i1", "taskInfo": taskInfo }
timer2 = { "id": "i2", "taskInfo": taskInfo }
m.manager.activeTimers = { "i1": timer1, "i2": timer2 }

task = { "id": "task" }
m.expectCalled(m.manager.createTask("Group", { data: "data" }), task)

m.manager.onDelayedTaskTimerFire(timer1)

m.assertEqual(m.manager.delayedTasks["i1"], task)
m.assertEqual(m.manager.activeTimers, { i2: timer2 })
end function

@it("ignores unknown timers")
function _()
taskInfo = {
nodeType: "Group"
fields: { data: "data" }
}

timer1 = { "id": "i1", "taskInfo": taskInfo }
timer2 = { "id": "i2", "taskInfo": taskInfo }
timer3 = { "id": "i3" }
m.manager.activeTimers = { "i1": timer1, "i2": timer2 }

m.expectNotCalled(m.manager.createTask)

m.manager.onDelayedTaskTimerFire(timer3)

m.assertEmpty(m.manager.delayedTasks)
m.assertEqual(m.manager.activeTimers, { i1: timer1, i2: timer2 })
end function


end class
end namespace

0 comments on commit 48b7e1e

Please sign in to comment.