dragonfly/scheduler/supervisor/task.go

379 lines
8.5 KiB
Go

/*
* Copyright 2020 The Dragonfly 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.
*/
//go:generate mockgen -destination ./mocks/task_mock.go -package mocks d7y.io/dragonfly/v2/scheduler/supervisor TaskManager
package supervisor
import (
"sync"
"time"
"go.uber.org/atomic"
logger "d7y.io/dragonfly/v2/internal/dflog"
"d7y.io/dragonfly/v2/pkg/container/list"
gc "d7y.io/dragonfly/v2/pkg/gc"
"d7y.io/dragonfly/v2/pkg/rpc/base"
"d7y.io/dragonfly/v2/scheduler/config"
)
const (
TaskGCID = "task"
TinyFileSize = 128
)
type TaskManager interface {
// Add task
Add(*Task)
// Get task
Get(string) (*Task, bool)
// Delete task
Delete(string)
// GetOrAdd or add task
GetOrAdd(*Task) (*Task, bool)
}
type taskManager struct {
// peerManager is peer manager
peerManager PeerManager
// taskTTL is task TTL
taskTTL time.Duration
// taskTTI is task TTI
taskTTI time.Duration
// tasks is task map
tasks *sync.Map
}
func NewTaskManager(cfg *config.GCConfig, gcManager gc.GC, peerManager PeerManager) (TaskManager, error) {
m := &taskManager{
peerManager: peerManager,
taskTTL: cfg.TaskTTL,
taskTTI: cfg.TaskTTI,
tasks: &sync.Map{},
}
if err := gcManager.Add(gc.Task{
ID: TaskGCID,
Interval: cfg.PeerGCInterval,
Timeout: cfg.PeerGCInterval,
Runner: m,
}); err != nil {
return nil, err
}
return m, nil
}
func (m *taskManager) Delete(id string) {
m.tasks.Delete(id)
}
func (m *taskManager) Add(task *Task) {
m.tasks.Store(task.ID, task)
}
func (m *taskManager) Get(id string) (*Task, bool) {
task, ok := m.tasks.Load(id)
if !ok {
return nil, false
}
return task.(*Task), ok
}
func (m *taskManager) GetOrAdd(t *Task) (*Task, bool) {
task, ok := m.tasks.LoadOrStore(t.ID, t)
return task.(*Task), ok
}
func (m *taskManager) RunGC() error {
m.tasks.Range(func(key, value interface{}) bool {
taskID := key.(string)
task := value.(*Task)
elapsed := time.Since(task.lastAccessAt.Load())
if elapsed > m.taskTTI && task.IsSuccess() {
task.Log().Info("elapsed larger than taskTTI, task status become zombie")
task.SetStatus(TaskStatusZombie)
}
if task.GetPeers().Len() == 0 {
task.Log().Info("peers is empty, task status become waiting")
task.SetStatus(TaskStatusWaiting)
}
if elapsed > m.taskTTL {
// TODO lock
peers := m.peerManager.GetPeersByTask(taskID)
for _, peer := range peers {
task.Log().Infof("delete peer %s because task is time to leave", peer.ID)
m.peerManager.Delete(peer.ID)
}
task.Log().Info("delete task because elapsed larger than task TTL")
m.Delete(taskID)
}
return true
})
return nil
}
type TaskStatus uint8
func (status TaskStatus) String() string {
switch status {
case TaskStatusWaiting:
return "Waiting"
case TaskStatusRunning:
return "Running"
case TaskStatusSeeding:
return "Seeding"
case TaskStatusSuccess:
return "Success"
case TaskStatusZombie:
return "Zombie"
case TaskStatusFail:
return "Fail"
default:
return "unknown"
}
}
const (
TaskStatusWaiting TaskStatus = iota
TaskStatusRunning
TaskStatusSeeding
TaskStatusSuccess
TaskStatusZombie
TaskStatusFail
)
type Task struct {
// ID is task id
ID string
// URL is task download url
URL string
// URLMeta is task download url meta
URLMeta *base.UrlMeta
// DirectPiece is tiny piece data
DirectPiece []byte
// ContentLength is task total content length
ContentLength atomic.Int64
// CreateAt is peer create time
CreateAt *atomic.Time
// LastTriggerAt is peer last trigger time
LastTriggerAt *atomic.Time
// lastAccessAt is peer last access time
lastAccessAt *atomic.Time
// status is task status and type is TaskStatus
status atomic.Value
// peers is peer sorted unique list
peers list.SortedUniqueList
// BackToSourceWeight is back-to-source peer weight
BackToSourceWeight atomic.Int32
// backToSourcePeers is back-to-source peers list
backToSourcePeers []string
// pieces is piece map
pieces *sync.Map
// TotalPieceCount is total piece count
TotalPieceCount atomic.Int32
// task logger
logger *logger.SugaredLoggerOnWith
// task lock
lock sync.RWMutex
}
func NewTask(id, url string, meta *base.UrlMeta) *Task {
now := time.Now()
task := &Task{
ID: id,
URL: url,
URLMeta: meta,
CreateAt: atomic.NewTime(now),
LastTriggerAt: atomic.NewTime(now),
lastAccessAt: atomic.NewTime(now),
backToSourcePeers: []string{},
pieces: &sync.Map{},
peers: list.NewSortedUniqueList(),
logger: logger.WithTaskID(id),
}
task.status.Store(TaskStatusWaiting)
return task
}
func (task *Task) SetStatus(status TaskStatus) {
task.status.Store(status)
}
func (task *Task) GetStatus() TaskStatus {
return task.status.Load().(TaskStatus)
}
// IsSuccess determines that whether cdn status is success.
func (task *Task) IsSuccess() bool {
return task.GetStatus() == TaskStatusSuccess
}
// CanSchedule determines whether task can be scheduled
// only task status is seeding or success can be scheduled
func (task *Task) CanSchedule() bool {
return task.GetStatus() == TaskStatusSeeding || task.GetStatus() == TaskStatusSuccess
}
// IsWaiting determines whether task is waiting
func (task *Task) IsWaiting() bool {
return task.GetStatus() == TaskStatusWaiting
}
// IsHealth determines whether task is health
func (task *Task) IsHealth() bool {
return task.GetStatus() == TaskStatusRunning || task.GetStatus() == TaskStatusSeeding || task.GetStatus() == TaskStatusSuccess
}
// IsFail determines whether task is fail
func (task *Task) IsFail() bool {
return task.GetStatus() == TaskStatusFail
}
func (task *Task) Touch() {
task.lastAccessAt.Store(time.Now())
}
func (task *Task) UpdateSuccess(pieceCount int32, contentLength int64) {
task.lock.Lock()
defer task.lock.Unlock()
if task.GetStatus() != TaskStatusSuccess {
task.SetStatus(TaskStatusSuccess)
task.TotalPieceCount.Store(pieceCount)
task.ContentLength.Store(contentLength)
}
}
func (task *Task) AddPeer(peer *Peer) {
task.peers.Insert(peer)
}
func (task *Task) UpdatePeer(peer *Peer) {
task.peers.Insert(peer)
}
func (task *Task) DeletePeer(peer *Peer) {
task.peers.Remove(peer)
}
func (task *Task) GetPeers() list.SortedUniqueList {
return task.peers
}
func (task *Task) GetPiece(n int32) (*base.PieceInfo, bool) {
piece, ok := task.pieces.Load(n)
if !ok {
return nil, false
}
return piece.(*base.PieceInfo), ok
}
func (task *Task) GetOrAddPiece(p *base.PieceInfo) (*base.PieceInfo, bool) {
piece, ok := task.pieces.LoadOrStore(p.PieceNum, p)
return piece.(*base.PieceInfo), ok
}
func (task *Task) CanBackToSource() bool {
return task.BackToSourceWeight.Load() > 0
}
func (task *Task) ContainsBackToSourcePeer(peerID string) bool {
task.lock.RLock()
defer task.lock.RUnlock()
for _, backToSourcePeer := range task.backToSourcePeers {
if backToSourcePeer == peerID {
return true
}
}
return false
}
func (task *Task) AddBackToSourcePeer(peerID string) {
if ok := task.ContainsBackToSourcePeer(peerID); ok {
return
}
if task.BackToSourceWeight.Load() <= 0 {
return
}
task.lock.Lock()
defer task.lock.Unlock()
task.backToSourcePeers = append(task.backToSourcePeers, peerID)
task.BackToSourceWeight.Dec()
}
func (task *Task) GetBackToSourcePeers() []string {
task.lock.RLock()
defer task.lock.RUnlock()
return task.backToSourcePeers
}
func (task *Task) Pick(limit int, pickFn func(peer *Peer) bool) []*Peer {
var peers []*Peer
task.GetPeers().Range(func(item list.Item) bool {
if len(peers) >= limit {
return false
}
peer, ok := item.(*Peer)
if !ok {
return true
}
if pickFn(peer) {
peers = append(peers, peer)
}
return true
})
return peers
}
func (task *Task) PickReverse(limit int, pickFn func(peer *Peer) bool) []*Peer {
var peers []*Peer
task.GetPeers().ReverseRange(func(item list.Item) bool {
if len(peers) >= limit {
return false
}
peer, ok := item.(*Peer)
if !ok {
return true
}
if pickFn(peer) {
peers = append(peers, peer)
}
return true
})
return peers
}
func (task *Task) Log() *logger.SugaredLoggerOnWith {
return task.logger
}