12 changed files with 397 additions and 73 deletions
@ -0,0 +1,169 @@ |
|||||||
|
package progress |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sig-pub/pkg/types" |
||||||
|
"sig-pub/pkg/zlog" |
||||||
|
"sync" |
||||||
|
"sync/atomic" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// ProgressReporter 进度上报接口
|
||||||
|
type IProgressReporter interface { |
||||||
|
SetTotal(total int64) |
||||||
|
SetProgress(progress int64) |
||||||
|
AddProgress(progress int64) |
||||||
|
Get() (progress, total int64) |
||||||
|
} |
||||||
|
|
||||||
|
type reporter struct { |
||||||
|
total atomic.Int64 |
||||||
|
progress atomic.Int64 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *reporter) SetTotal(total int64) { |
||||||
|
r.total.Store(total) |
||||||
|
} |
||||||
|
func (r *reporter) SetProgress(progress int64) { |
||||||
|
r.progress.Store(progress) |
||||||
|
} |
||||||
|
func (r *reporter) AddProgress(progress int64) { |
||||||
|
r.progress.Add(progress) |
||||||
|
} |
||||||
|
func (r *reporter) Get() (progress, total int64) { |
||||||
|
return r.progress.Load(), r.total.Load() |
||||||
|
} |
||||||
|
|
||||||
|
// ==================== 任务状态 ====================
|
||||||
|
type Status int32 |
||||||
|
|
||||||
|
const ( |
||||||
|
// StatusPending Status = "pending"
|
||||||
|
StatusRunning Status = 1 //"running"
|
||||||
|
StatusSuccess Status = 2 //"success"
|
||||||
|
StatusFailed Status = 3 //"failed"
|
||||||
|
StatusCancelled Status = 4 //"cancelled"
|
||||||
|
) |
||||||
|
|
||||||
|
// Task 任务结构体(带锁)
|
||||||
|
type Task struct { |
||||||
|
ID string `json:"id"` // 任务id
|
||||||
|
Name string `json:"name"` |
||||||
|
Status atomic.Int64 `json:"status"` |
||||||
|
Error error `json:"error,omitempty"` |
||||||
|
Ctime int64 `json:"ctime"` |
||||||
|
Etime int64 `json:"etime,omitempty"` |
||||||
|
Progress IProgressReporter |
||||||
|
cancel context.CancelFunc |
||||||
|
} |
||||||
|
|
||||||
|
// ProgressManager 管理器核心
|
||||||
|
type ProgressManager struct { |
||||||
|
nextID atomic.Int64 |
||||||
|
tasks sync.Map // string -> *Task
|
||||||
|
completeTasks *types.RingSeries[*Task] |
||||||
|
mu sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
// NewProgressManager 创建管理器实例
|
||||||
|
func NewProgressManager() *ProgressManager { |
||||||
|
return &ProgressManager{ |
||||||
|
completeTasks: types.NewRingSeries[*Task](100, 0), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) generateID() string { |
||||||
|
id := m.nextID.Add(1) |
||||||
|
return fmt.Sprintf("task_%d_%d", id, time.Now().UnixNano()%100000) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) StartTask(ctx context.Context, name string, fn func(ctx context.Context, progress IProgressReporter) error) string { |
||||||
|
id := m.generateID() |
||||||
|
ctx, cancel := context.WithCancel(ctx) |
||||||
|
|
||||||
|
task := &Task{ |
||||||
|
ID: id, |
||||||
|
Name: name, |
||||||
|
Status: atomic.Int64{}, |
||||||
|
Error: nil, |
||||||
|
Ctime: time.Now().UnixMilli(), |
||||||
|
Etime: 0, |
||||||
|
Progress: &reporter{}, |
||||||
|
cancel: cancel, |
||||||
|
} |
||||||
|
m.tasks.Store(id, task) |
||||||
|
|
||||||
|
go m.execTask(ctx, task, fn) |
||||||
|
|
||||||
|
return id |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) execTask(ctx context.Context, task *Task, fn func(ctx context.Context, progress IProgressReporter) error) { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
if !task.Status.CompareAndSwap(int64(StatusRunning), int64(StatusFailed)) { |
||||||
|
zlog.Error("task status not running, can not set panic failed: %s, name=%s, status=%d", task.ID, task.Name, task.Status.Load()) |
||||||
|
} |
||||||
|
task.Error = fmt.Errorf("panic: %v", r) |
||||||
|
task.Etime = time.Now().UnixMilli() |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
defer func() { |
||||||
|
m.tasks.Delete(task.ID) |
||||||
|
|
||||||
|
m.mu.Lock() |
||||||
|
defer m.mu.Unlock() |
||||||
|
m.completeTasks.Push(task) |
||||||
|
}() |
||||||
|
|
||||||
|
task.Status.Store(int64(StatusRunning)) |
||||||
|
err := fn(ctx, task.Progress) |
||||||
|
if err != nil { |
||||||
|
if !task.Status.CompareAndSwap(int64(StatusRunning), int64(StatusFailed)) { |
||||||
|
zlog.Error("task status not running, can not set failed: %s, name=%s, status=%d", task.ID, task.Name, task.Status.Load()) |
||||||
|
} |
||||||
|
task.Error = err |
||||||
|
task.Etime = time.Now().UnixMilli() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
task.Etime = time.Now().UnixMilli() |
||||||
|
if !task.Status.CompareAndSwap(int64(StatusRunning), int64(StatusSuccess)) { |
||||||
|
zlog.Error("task status not running, can not set success: %s, name=%s, status=%d", task.ID, task.Name, task.Status.Load()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) GetTask(id string) (*Task, bool) { |
||||||
|
task, ok := m.tasks.Load(id) |
||||||
|
if !ok { |
||||||
|
return nil, false |
||||||
|
} |
||||||
|
return task.(*Task), true |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) ListTask() ([]*Task, bool) { |
||||||
|
runningTasks := m.ListRunningTask() |
||||||
|
|
||||||
|
m.mu.RLock() |
||||||
|
defer m.mu.RUnlock() |
||||||
|
tasks, ok := m.completeTasks.Series(0, m.completeTasks.Length()) |
||||||
|
if !ok { |
||||||
|
return nil, false |
||||||
|
} |
||||||
|
|
||||||
|
return append(runningTasks, tasks...), true |
||||||
|
} |
||||||
|
|
||||||
|
func (m *ProgressManager) ListRunningTask() (tasks []*Task) { |
||||||
|
m.tasks.Range(func(key, value any) bool { |
||||||
|
task := value.(*Task) |
||||||
|
if task.Status.Load() == int64(StatusRunning) { |
||||||
|
tasks = append(tasks, task) |
||||||
|
} |
||||||
|
return true |
||||||
|
}) |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
package progress |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func TestProgressManager(t *testing.T) { |
||||||
|
c := make(chan struct{}) |
||||||
|
|
||||||
|
m := NewProgressManager() |
||||||
|
taskId := m.StartTask(context.Background(), "testing", func(ctx context.Context, progress IProgressReporter) error { |
||||||
|
defer close(c) |
||||||
|
|
||||||
|
progress.SetTotal(100) |
||||||
|
for range 100 { |
||||||
|
progress.AddProgress(1) |
||||||
|
time.Sleep(110 * time.Millisecond) |
||||||
|
} |
||||||
|
return nil |
||||||
|
}) |
||||||
|
|
||||||
|
go func() { |
||||||
|
task, _ := m.GetTask(taskId) |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-c: |
||||||
|
return |
||||||
|
default: |
||||||
|
} |
||||||
|
progress, total := task.Progress.Get() |
||||||
|
if total != 0 { |
||||||
|
fmt.Printf("task %s progress: %.2f%%\n", task.Name, float64(progress)/float64(total)*100) |
||||||
|
if progress == total { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
time.Sleep(300 * time.Millisecond) |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
<-c |
||||||
|
} |
||||||
Loading…
Reference in new issue