mirror of https://github.com/k3s-io/k3s
156 lines
5.7 KiB
Go
156 lines
5.7 KiB
Go
|
/*
|
||
|
Copyright The containerd Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package task
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
|
|
||
|
"golang.org/x/sync/semaphore"
|
||
|
)
|
||
|
|
||
|
// NewBackgroundTaskManager provides a task manager. You can specify the
|
||
|
// concurrency of background tasks. When running a background task, this will be
|
||
|
// forced to wait until no prioritized task is running for some period. You can
|
||
|
// specify the period through the argument of this function, too.
|
||
|
func NewBackgroundTaskManager(concurrency int64, period time.Duration) *BackgroundTaskManager {
|
||
|
return &BackgroundTaskManager{
|
||
|
backgroundSem: semaphore.NewWeighted(concurrency),
|
||
|
prioritizedTaskSilencePeriod: period,
|
||
|
prioritizedTaskStartNotify: make(chan struct{}),
|
||
|
prioritizedTaskDoneCond: sync.NewCond(&sync.Mutex{}),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// BackgroundTaskManager is a task manager which manages prioritized tasks and
|
||
|
// background tasks execution. Background tasks are less important than
|
||
|
// prioritized tasks. You can let these background tasks not to use compute
|
||
|
// resources (CPU, NW, etc...) during more important tasks(=prioritized tasks)
|
||
|
// running.
|
||
|
//
|
||
|
// When you run a prioritised task and don't want background tasks to use
|
||
|
// resources you can tell it this manager by calling DoPrioritizedTask method.
|
||
|
// DonePrioritizedTask method must be called at the end of the prioritised task
|
||
|
// execution.
|
||
|
//
|
||
|
// For running a background task, you can use InvokeBackgroundTask method. The
|
||
|
// background task must be able to be cancelled via context.Context argument.
|
||
|
// The task is forced to wait until no prioritized task is running for some
|
||
|
// period. You can specify the period when making this manager instance. The
|
||
|
// limited number of background tasks run simultaneously and you can specify the
|
||
|
// concurrency when making this manager instance too. If a prioritized task
|
||
|
// starts during the execution of background tasks, all background tasks running
|
||
|
// will be cancelled via context. These cancelled tasks will be executed again
|
||
|
// later, same as other background tasks (when no prioritized task is running
|
||
|
// for some period).
|
||
|
type BackgroundTaskManager struct {
|
||
|
prioritizedTasks int64
|
||
|
backgroundSem *semaphore.Weighted
|
||
|
prioritizedTaskSilencePeriod time.Duration
|
||
|
prioritizedTaskStartNotify chan struct{}
|
||
|
prioritizedTaskStartNotifyMu sync.Mutex
|
||
|
prioritizedTaskDoneCond *sync.Cond
|
||
|
}
|
||
|
|
||
|
// DoPrioritizedTask tells the manager that we are running a prioritized task
|
||
|
// and don't want background tasks to disturb resources(CPU, NW, etc...)
|
||
|
func (ts *BackgroundTaskManager) DoPrioritizedTask() {
|
||
|
// Notify the prioritized task execution to background tasks.
|
||
|
ts.prioritizedTaskStartNotifyMu.Lock()
|
||
|
atomic.AddInt64(&ts.prioritizedTasks, 1)
|
||
|
close(ts.prioritizedTaskStartNotify)
|
||
|
ts.prioritizedTaskStartNotify = make(chan struct{})
|
||
|
ts.prioritizedTaskStartNotifyMu.Unlock()
|
||
|
}
|
||
|
|
||
|
// DonePrioritizedTask tells the manager that we've done a prioritized task
|
||
|
// and don't want background tasks to disturb resources(CPU, NW, etc...)
|
||
|
func (ts *BackgroundTaskManager) DonePrioritizedTask() {
|
||
|
go func() {
|
||
|
// Notify the task completion after `ts.prioritizedTaskSilencePeriod`
|
||
|
// so that background tasks aren't invoked immediately.
|
||
|
time.Sleep(ts.prioritizedTaskSilencePeriod)
|
||
|
atomic.AddInt64(&ts.prioritizedTasks, -1)
|
||
|
ts.prioritizedTaskDoneCond.L.Lock()
|
||
|
ts.prioritizedTaskDoneCond.Broadcast()
|
||
|
ts.prioritizedTaskDoneCond.L.Unlock()
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
// InvokeBackgroundTask invokes a background task. The task is started only when
|
||
|
// no prioritized tasks are running. Prioritized task's execution stops the
|
||
|
// execution of all background tasks. Background task must be able to be
|
||
|
// cancelled via context.Context argument and be able to be restarted again.
|
||
|
func (ts *BackgroundTaskManager) InvokeBackgroundTask(do func(context.Context), timeout time.Duration) {
|
||
|
for {
|
||
|
// Wait until all prioritized tasks are done
|
||
|
for {
|
||
|
if atomic.LoadInt64(&ts.prioritizedTasks) <= 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// waits until a prioritized task is done
|
||
|
ts.prioritizedTaskDoneCond.L.Lock()
|
||
|
if atomic.LoadInt64(&ts.prioritizedTasks) > 0 {
|
||
|
ts.prioritizedTaskDoneCond.Wait()
|
||
|
}
|
||
|
ts.prioritizedTaskDoneCond.L.Unlock()
|
||
|
}
|
||
|
|
||
|
// limited number of background tasks can run at once.
|
||
|
// if prioritized tasks are running, cancel this task.
|
||
|
if func() bool {
|
||
|
ts.backgroundSem.Acquire(context.Background(), 1)
|
||
|
defer ts.backgroundSem.Release(1)
|
||
|
|
||
|
// Get notify the prioritized tasks execution.
|
||
|
ts.prioritizedTaskStartNotifyMu.Lock()
|
||
|
ch := ts.prioritizedTaskStartNotify
|
||
|
tasks := atomic.LoadInt64(&ts.prioritizedTasks)
|
||
|
ts.prioritizedTaskStartNotifyMu.Unlock()
|
||
|
if tasks > 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Invoke the background task. if some prioritized tasks added during
|
||
|
// execution, cancel it and try it later.
|
||
|
var (
|
||
|
done = make(chan struct{})
|
||
|
ctx, cancel = context.WithTimeout(context.Background(), timeout)
|
||
|
)
|
||
|
defer cancel()
|
||
|
go func() {
|
||
|
do(ctx)
|
||
|
close(done)
|
||
|
}()
|
||
|
|
||
|
// Wait until the background task is done or canceled.
|
||
|
select {
|
||
|
case <-ch: // some prioritized tasks started; retry it later
|
||
|
cancel()
|
||
|
return false
|
||
|
case <-done: // All tasks completed
|
||
|
}
|
||
|
return true
|
||
|
}() {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|