NewSystemTaskProgressReporter returns a throttled progress callback bound to a running task. Handlers call it with (processed, total) as they iterate work; it persists a {processed,total,progress} state at most once every ~2s, always emitting the first update and the final 100%. Lock-loss errors are
(task *model.SystemTask, runnerID string)
| 476 | // loss, so progress writes are best-effort and never abort the run themselves. |
| 477 | // The returned func is single-goroutine only (call it from the handler loop). |
| 478 | func NewSystemTaskProgressReporter(task *model.SystemTask, runnerID string) func(processed, total int) { |
| 479 | const minWriteInterval = 2 * time.Second |
| 480 | var ( |
| 481 | lastWriteAt time.Time |
| 482 | lastProgress = -1 |
| 483 | ) |
| 484 | return func(processed, total int) { |
| 485 | progress := 100 |
| 486 | if total > 0 { |
| 487 | progress = processed * 100 / total |
| 488 | } |
| 489 | if progress < 0 { |
| 490 | progress = 0 |
| 491 | } else if progress > 100 { |
| 492 | progress = 100 |
| 493 | } |
| 494 | |
| 495 | if progress < 100 { |
| 496 | if progress == lastProgress { |
| 497 | return |
| 498 | } |
| 499 | if !lastWriteAt.IsZero() && time.Since(lastWriteAt) < minWriteInterval { |
| 500 | return |
| 501 | } |
| 502 | } |
| 503 | lastProgress = progress |
| 504 | lastWriteAt = time.Now() |
| 505 | |
| 506 | state := SystemTaskProgress{Total: total, Processed: processed, Progress: progress} |
| 507 | _ = model.UpdateSystemTaskState(task.TaskID, runnerID, state) |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | func failSystemTask(task *model.SystemTask, runnerID string, err error) { |
| 512 | logger.LogWarn(context.Background(), fmt.Sprintf("system task %s failed: %v", task.TaskID, err)) |
no test coverage detected