diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index ef5b7a61a..3aa1d6171 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -117,7 +117,7 @@ func (o *Orchestrator) Run(mainCtx context.Context) error { // runImmutable is a helper function for Run() that runs the orchestration loop with a single version of the config. func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bool { - lock := sync.Mutex{} + var lock sync.Mutex ctx, cancel := context.WithCancel(mainCtx) var wg sync.WaitGroup @@ -140,7 +140,9 @@ func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bo defer wg.Done() select { case <-ctx.Done(): - timer.Stop() + if !timer.Stop() { + <-timer.C + } zap.L().Debug("cancelled scheduled (but not running) task, orchestrator context is cancelled.", zap.String("task", t.Name())) return case <-timer.C: diff --git a/internal/orchestrator/repo.go b/internal/orchestrator/repo.go index 366a98b64..84a9dede9 100644 --- a/internal/orchestrator/repo.go +++ b/internal/orchestrator/repo.go @@ -19,12 +19,6 @@ type RepoOrchestrator struct { repoConfig *v1.Repo repo *restic.Repo - - // TODO: decide if snapshot caching is a good idea. We gain performance but - // increase background memory use by a small amount at all times (probably on the order of 1MB). - snapshotsMu sync.Mutex // enable very fast snapshot access IF no update is required. - snapshotsResetTimer *time.Timer - snapshots []*restic.Snapshot } func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrator { @@ -34,64 +28,26 @@ func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrat } } -func (r *RepoOrchestrator) updateSnapshotsIfNeeded(ctx context.Context, force bool) error { - if r.snapshots != nil { - return nil - } - - if r.snapshotsResetTimer != nil { - if !r.snapshotsResetTimer.Stop() { - <-r.snapshotsResetTimer.C - } - } - - r.snapshotsResetTimer = time.AfterFunc(10 * time.Minute, func() { - r.snapshotsMu.Lock() - defer r.snapshotsMu.Unlock() - r.snapshots = nil - }) - - if r.snapshots != nil { - return nil - } - - - startTime := time.Now() - +func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) { snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...), restic.WithFlags("--latest", "1000")) if err != nil { - return fmt.Errorf("failed to update snapshots: %w", err) + return nil, fmt.Errorf("restic.Snapshots: %w", err) } sort.SliceStable(snapshots, func(i, j int) bool { return snapshots[i].UnixTimeMs() < snapshots[j].UnixTimeMs() }) - r.snapshots = snapshots - - zap.L().Debug("updated snapshots", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime))) - - return nil -} - -func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) { - r.snapshotsMu.Lock() - defer r.snapshotsMu.Unlock() - if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil { - return nil, err - } - return r.snapshots, nil + return snapshots, nil } func (r *RepoOrchestrator) SnapshotsForPlan(ctx context.Context, plan *v1.Plan) ([]*restic.Snapshot, error) { - r.snapshotsMu.Lock() - defer r.snapshotsMu.Unlock() - - if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil { + snapshots, err := r.Snapshots(ctx) + if err != nil { return nil, err } - return filterSnapshotsForPlan(r.snapshots, plan), nil + return filterSnapshotsForPlan(snapshots, plan), nil } func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCallback func(event *restic.BackupProgressEntry)) (*restic.BackupProgressEntry, error) { @@ -123,14 +79,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa return nil, fmt.Errorf("failed to backup: %w", err) } - // Reset snapshots since a new backup has been added. - r.snapshotsMu.Lock() - r.snapshots = nil - r.snapshotsMu.Unlock() - zap.L().Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime))) - - return summary, nil } diff --git a/webui/src/components/OperationList.tsx b/webui/src/components/OperationList.tsx index 6963d5d05..f55e89148 100644 --- a/webui/src/components/OperationList.tsx +++ b/webui/src/components/OperationList.tsx @@ -17,10 +17,6 @@ export const OperationList = ({ }: React.PropsWithoutRef<{ operations: EOperation[] }>) => { operations.sort((a, b) => b.parsedTime - a.parsedTime); - const elems = operations.map((operation) => ( - - )); - if (operations.length === 0) { return ( ( )} + pagination={ + operations.length > 50 + ? { position: "both", align: "center", defaultPageSize: 50 } + : {} + } /> ); }; @@ -68,7 +69,10 @@ export const OperationRow = ({ const backupOp = operation.operationBackup; let desc = `Backup at ${formatTime(operation.unixTimeStartMs!)}`; if (operation.status !== OperationStatus.STATUS_INPROGRESS) { - desc += ` and finished at ${formatTime(operation.unixTimeEndMs!)}`; + desc += ` completed in ${formatDuration( + parseInt(operation.unixTimeEndMs!) - + parseInt(operation.unixTimeStartMs!) + )}`; } else { desc += " and is still running."; } @@ -273,5 +277,17 @@ const formatTime = (time: number | string) => { } const d = new Date(); d.setTime(time); - return d.toLocaleString(); + return d.toISOString(); }; + +const formatDuration = (ms: number) => { + const seconds = Math.floor(ms / 100) / 10; + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + if (hours === 0 && minutes === 0) { + return `${seconds % 60}s`; + } else if (hours === 0) { + return `${minutes}m${seconds % 60}s`; + } + return `${hours}h${minutes % 60}m${seconds % 60}s`; +}; \ No newline at end of file