379 lines
8.5 KiB
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
|
|
}
|