Fix and improve task management

This commit is contained in:
Daniel 2020-04-24 09:52:42 +02:00
parent 00d9594ea5
commit adcc916a6a
2 changed files with 140 additions and 47 deletions

View file

@ -18,12 +18,12 @@ type Task struct {
module *Module module *Module
taskFn func(context.Context, *Task) error taskFn func(context.Context, *Task) error
queued bool
canceled bool canceled bool
executing bool executing bool
overtime bool // locked by scheduleLock
// these are populated at task creation // these are populated at task creation
// ctx is canceled when task is shutdown -> all tasks become canceled // ctx is canceled when module is shutdown -> all tasks become canceled
ctx context.Context ctx context.Context
cancelCtx func() cancelCtx func()
@ -110,10 +110,9 @@ func (t *Task) prepForQueueing() (ok bool) {
return false return false
} }
t.queued = true
if t.maxDelay != 0 { if t.maxDelay != 0 {
t.executeAt = time.Now().Add(t.maxDelay) t.executeAt = time.Now().Add(t.maxDelay)
t.addToSchedule() t.addToSchedule(true)
} }
return true return true
@ -129,11 +128,11 @@ func notifyQueue() {
// Queue queues the Task for execution. // Queue queues the Task for execution.
func (t *Task) Queue() *Task { func (t *Task) Queue() *Task {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
if !t.prepForQueueing() { if !t.prepForQueueing() {
t.lock.Unlock()
return t return t
} }
t.lock.Unlock()
if t.queueElement == nil { if t.queueElement == nil {
queuesLock.Lock() queuesLock.Lock()
@ -148,11 +147,11 @@ func (t *Task) Queue() *Task {
// Prioritize puts the task in the prioritized queue. // Prioritize puts the task in the prioritized queue.
func (t *Task) Prioritize() *Task { func (t *Task) Prioritize() *Task {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
if !t.prepForQueueing() { if !t.prepForQueueing() {
t.lock.Unlock()
return t return t
} }
t.lock.Unlock()
if t.prioritizedQueueElement == nil { if t.prioritizedQueueElement == nil {
queuesLock.Lock() queuesLock.Lock()
@ -167,11 +166,11 @@ func (t *Task) Prioritize() *Task {
// StartASAP schedules the task to be executed next. // StartASAP schedules the task to be executed next.
func (t *Task) StartASAP() *Task { func (t *Task) StartASAP() *Task {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
if !t.prepForQueueing() { if !t.prepForQueueing() {
t.lock.Unlock()
return t return t
} }
t.lock.Unlock()
queuesLock.Lock() queuesLock.Lock()
if t.prioritizedQueueElement == nil { if t.prioritizedQueueElement == nil {
@ -188,17 +187,19 @@ func (t *Task) StartASAP() *Task {
// MaxDelay sets a maximum delay within the task should be executed from being queued. Scheduled tasks are queued when they are triggered. The default delay is 3 minutes. // MaxDelay sets a maximum delay within the task should be executed from being queued. Scheduled tasks are queued when they are triggered. The default delay is 3 minutes.
func (t *Task) MaxDelay(maxDelay time.Duration) *Task { func (t *Task) MaxDelay(maxDelay time.Duration) *Task {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
t.maxDelay = maxDelay t.maxDelay = maxDelay
t.lock.Unlock()
return t return t
} }
// Schedule schedules the task for execution at the given time. // Schedule schedules the task for execution at the given time.
func (t *Task) Schedule(executeAt time.Time) *Task { func (t *Task) Schedule(executeAt time.Time) *Task {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
t.executeAt = executeAt t.executeAt = executeAt
t.addToSchedule() t.addToSchedule(false)
t.lock.Unlock()
return t return t
} }
@ -210,22 +211,23 @@ func (t *Task) Repeat(interval time.Duration) *Task {
} }
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
t.repeat = interval t.repeat = interval
t.executeAt = time.Now().Add(t.repeat) t.executeAt = time.Now().Add(t.repeat)
t.addToSchedule() t.addToSchedule(false)
t.lock.Unlock()
return t return t
} }
// Cancel cancels the current and any future execution of the Task. This is not reversible by any other functions. // Cancel cancels the current and any future execution of the Task. This is not reversible by any other functions.
func (t *Task) Cancel() { func (t *Task) Cancel() {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
t.canceled = true t.canceled = true
if t.cancelCtx != nil { if t.cancelCtx != nil {
t.cancelCtx() t.cancelCtx()
} }
t.lock.Unlock()
} }
func (t *Task) removeFromQueues() { func (t *Task) removeFromQueues() {
@ -234,31 +236,29 @@ func (t *Task) removeFromQueues() {
queuesLock.Lock() queuesLock.Lock()
taskQueue.Remove(t.queueElement) taskQueue.Remove(t.queueElement)
queuesLock.Unlock() queuesLock.Unlock()
t.lock.Lock()
t.queueElement = nil t.queueElement = nil
t.lock.Unlock()
} }
if t.prioritizedQueueElement != nil { if t.prioritizedQueueElement != nil {
queuesLock.Lock() queuesLock.Lock()
prioritizedTaskQueue.Remove(t.prioritizedQueueElement) prioritizedTaskQueue.Remove(t.prioritizedQueueElement)
queuesLock.Unlock() queuesLock.Unlock()
t.lock.Lock()
t.prioritizedQueueElement = nil t.prioritizedQueueElement = nil
t.lock.Unlock()
} }
if t.scheduleListElement != nil { if t.scheduleListElement != nil {
scheduleLock.Lock() scheduleLock.Lock()
taskSchedule.Remove(t.scheduleListElement) taskSchedule.Remove(t.scheduleListElement)
t.overtime = false
scheduleLock.Unlock() scheduleLock.Unlock()
t.lock.Lock()
t.scheduleListElement = nil t.scheduleListElement = nil
t.lock.Unlock()
} }
} }
func (t *Task) runWithLocking() { func (t *Task) runWithLocking() {
t.lock.Lock() t.lock.Lock()
// we will not attempt execution, remove from queues
t.removeFromQueues()
// check if task is already executing // check if task is already executing
if t.executing { if t.executing {
t.lock.Unlock() t.lock.Unlock()
@ -266,21 +266,22 @@ func (t *Task) runWithLocking() {
} }
// check if task is active // check if task is active
// - has not been cancelled
// - module is online (soon)
if !t.isActive() { if !t.isActive() {
t.removeFromQueues()
t.lock.Unlock() t.lock.Unlock()
return return
} }
// check if module was stopped // check if module was stopped
select { select {
case <-t.ctx.Done(): // check if module is stopped case <-t.ctx.Done():
t.removeFromQueues()
t.lock.Unlock() t.lock.Unlock()
return return
default: default:
} }
// enter executing state
t.executing = true t.executing = true
t.lock.Unlock() t.lock.Unlock()
@ -296,8 +297,9 @@ func (t *Task) runWithLocking() {
// wait // wait
<-t.module.StartCompleted() <-t.module.StartCompleted()
} else { } else {
// abort, module will not come online
t.lock.Lock() t.lock.Lock()
t.removeFromQueues() t.executing = false
t.lock.Unlock() t.lock.Unlock()
return return
} }
@ -336,22 +338,23 @@ func (t *Task) executeWithLocking() {
atomic.AddInt32(t.module.taskCnt, -1) atomic.AddInt32(t.module.taskCnt, -1)
t.module.waitGroup.Done() t.module.waitGroup.Done()
// reset
t.lock.Lock() t.lock.Lock()
// reset state // reset state
t.executing = false t.executing = false
t.queued = false
// repeat? // repeat?
if t.isActive() && t.repeat != 0 { if t.isActive() && t.repeat != 0 {
t.executeAt = time.Now().Add(t.repeat) t.executeAt = time.Now().Add(t.repeat)
t.addToSchedule() t.addToSchedule(false)
} }
t.lock.Unlock()
// notify that we finished // notify that we finished
if t.cancelCtx != nil { t.cancelCtx()
t.cancelCtx() // refresh context
} t.ctx, t.cancelCtx = context.WithCancel(t.module.Ctx)
t.lock.Unlock()
}() }()
// run // run
@ -361,13 +364,7 @@ func (t *Task) executeWithLocking() {
} }
} }
func (t *Task) getExecuteAtWithLocking() time.Time { func (t *Task) addToSchedule(overtime bool) {
t.lock.Lock()
defer t.lock.Unlock()
return t.executeAt
}
func (t *Task) addToSchedule() {
if !t.isActive() { if !t.isActive() {
return return
} }
@ -376,6 +373,11 @@ func (t *Task) addToSchedule() {
defer scheduleLock.Unlock() defer scheduleLock.Unlock()
// defer printTaskList(taskSchedule) // for debugging // defer printTaskList(taskSchedule) // for debugging
if overtime {
// do not set to false
t.overtime = true
}
// notify scheduler // notify scheduler
defer func() { defer func() {
select { select {
@ -392,7 +394,7 @@ func (t *Task) addToSchedule() {
continue continue
} }
// compare // compare
if t.executeAt.Before(eVal.getExecuteAtWithLocking()) { if t.executeAt.Before(eVal.executeAt) {
// insert/move task // insert/move task
if t.scheduleListElement == nil { if t.scheduleListElement == nil {
t.scheduleListElement = taskSchedule.InsertBefore(t, e) t.scheduleListElement = taskSchedule.InsertBefore(t, e)
@ -489,18 +491,28 @@ func taskScheduleHandler() {
return return
case <-recalculateNextScheduledTask: case <-recalculateNextScheduledTask:
case <-waitUntilNextScheduledTask(): case <-waitUntilNextScheduledTask():
// get first task in schedule
scheduleLock.Lock() scheduleLock.Lock()
// get first task in schedule
e := taskSchedule.Front() e := taskSchedule.Front()
scheduleLock.Unlock() if e == nil {
scheduleLock.Unlock()
continue
}
t := e.Value.(*Task) t := e.Value.(*Task)
// process Task // process Task
if t.queued { if t.overtime {
// already queued and maxDelay reached // already queued and maxDelay reached
t.overtime = false
scheduleLock.Unlock()
t.runWithLocking() t.runWithLocking()
} else { } else {
// place in front of prioritized queue // place in front of prioritized queue
t.overtime = true
scheduleLock.Unlock()
t.StartASAP() t.StartASAP()
} }
} }
@ -513,10 +525,10 @@ func printTaskList(*list.List) { //nolint:unused,deadcode // for debugging, NOT
t, ok := e.Value.(*Task) t, ok := e.Value.(*Task)
if ok { if ok {
fmt.Printf( fmt.Printf(
"%s:%s qu=%v ca=%v exec=%v at=%s rep=%s delay=%s\n", "%s:%s over=%v canc=%v exec=%v exat=%s rep=%s delay=%s\n",
t.module.Name, t.module.Name,
t.name, t.name,
t.queued, t.overtime,
t.canceled, t.canceled,
t.executing, t.executing,
t.executeAt, t.executeAt,

View file

@ -168,3 +168,84 @@ func TestScheduledTaskWaiting(t *testing.T) {
} }
} }
func TestRequeueingTask(t *testing.T) {
blockWg := &sync.WaitGroup{}
wg := &sync.WaitGroup{}
// block task execution
blockWg.Add(1) // mark done at beginning
wg.Add(2) // mark done at end
block := qtModule.NewTask("TestRequeueingTask:block", func(ctx context.Context, t *Task) error {
blockWg.Done()
time.Sleep(100 * time.Millisecond)
wg.Done()
return nil
}).StartASAP()
// make sure first task has started
blockWg.Wait()
// fmt.Printf("%s: %+v\n", time.Now(), block)
// schedule again while executing
blockWg.Add(1) // mark done at beginning
block.StartASAP()
// fmt.Printf("%s: %+v\n", time.Now(), block)
// test task
wg.Add(1)
task := qtModule.NewTask("TestRequeueingTask:test", func(ctx context.Context, t *Task) error {
wg.Done()
return nil
}).Schedule(time.Now().Add(2 * time.Second))
// reschedule
task.Schedule(time.Now().Add(1 * time.Second))
task.Queue()
task.Prioritize()
task.StartASAP()
wg.Wait()
time.Sleep(100 * time.Millisecond) // let tasks finalize execution
// do it again
// block task execution (while first block task is still running!)
blockWg.Add(1) // mark done at beginning
wg.Add(1) // mark done at end
block.StartASAP()
blockWg.Wait()
// reschedule
wg.Add(1)
task.Schedule(time.Now().Add(1 * time.Second))
task.Queue()
task.Prioritize()
task.StartASAP()
wg.Wait()
}
func TestQueueSuccession(t *testing.T) {
var cnt int
wg := &sync.WaitGroup{}
wg.Add(10)
tt := qtModule.NewTask("TestRequeueingTask:test", func(ctx context.Context, task *Task) error {
time.Sleep(10 * time.Millisecond)
wg.Done()
cnt++
fmt.Printf("completed succession %d\n", cnt)
switch cnt {
case 1, 4, 6:
task.Queue()
case 2, 5, 8:
task.StartASAP()
case 3, 7, 9:
task.Schedule(time.Now().Add(10 * time.Millisecond))
}
return nil
})
// fmt.Printf("%+v\n", tt)
tt.StartASAP()
// time.Sleep(100 * time.Millisecond)
// fmt.Printf("%+v\n", tt)
wg.Wait()
}