fix: UI buttons spin while waiting for tasks to complete

This commit is contained in:
garethgeorge
2023-12-31 11:36:36 -08:00
parent 85f70e64d1
commit c767fa7476
5 changed files with 42 additions and 19 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"sync"
"time" "time"
"connectrpc.com/connect" "connectrpc.com/connect"
@@ -268,8 +269,14 @@ func (s *Server) Backup(ctx context.Context, req *connect.Request[types.StringVa
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err) return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err)
} }
s.orchestrator.ScheduleTask(orchestrator.NewOneoffBackupTask(s.orchestrator, plan, time.Now()), orchestrator.TaskPriorityInteractive) var wg sync.WaitGroup
return connect.NewResponse(&emptypb.Empty{}), nil wg.Add(1)
s.orchestrator.ScheduleTask(orchestrator.NewOneoffBackupTask(s.orchestrator, plan, time.Now()), orchestrator.TaskPriorityInteractive, func(e error) {
err = e
wg.Done()
})
wg.Wait()
return connect.NewResponse(&emptypb.Empty{}), err
} }
func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
@@ -279,11 +286,15 @@ func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringVa
} }
at := time.Now() at := time.Now()
var wg sync.WaitGroup
s.orchestrator.ScheduleTask(orchestrator.NewOneoffForgetTask(s.orchestrator, plan, "", at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityForget) wg.Add(1)
s.orchestrator.ScheduleTask(orchestrator.NewOneoffForgetTask(s.orchestrator, plan, "", at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityForget, func(e error) {
err = e
wg.Done()
})
s.orchestrator.ScheduleTask(orchestrator.NewOneoffIndexSnapshotsTask(s.orchestrator, plan.Repo, at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityIndexSnapshots) s.orchestrator.ScheduleTask(orchestrator.NewOneoffIndexSnapshotsTask(s.orchestrator, plan.Repo, at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityIndexSnapshots)
wg.Wait()
return connect.NewResponse(&emptypb.Empty{}), nil return connect.NewResponse(&emptypb.Empty{}), err
} }
func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
@@ -293,7 +304,13 @@ func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringVal
} }
at := time.Now() at := time.Now()
s.orchestrator.ScheduleTask(orchestrator.NewOneoffPruneTask(s.orchestrator, plan, "", at, true), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityPrune) var wg sync.WaitGroup
wg.Add(1)
s.orchestrator.ScheduleTask(orchestrator.NewOneoffPruneTask(s.orchestrator, plan, "", at, true), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityPrune, func(e error) {
err = e
wg.Done()
})
wg.Wait()
return connect.NewResponse(&emptypb.Empty{}), nil return connect.NewResponse(&emptypb.Empty{}), nil
} }

View File

@@ -230,14 +230,18 @@ func (o *Orchestrator) Run(mainCtx context.Context) {
} }
start := time.Now() start := time.Now()
if err := t.task.Run(taskCtx); err != nil { err := t.task.Run(taskCtx)
zap.L().Error("task failed", zap.String("task", t.task.Name()), zap.Error(err)) if err != nil {
zap.L().Error("task failed", zap.String("task", t.task.Name()), zap.Error(err), zap.Duration("duration", time.Since(start)))
} else { } else {
zap.L().Info("task finished", zap.String("task", t.task.Name()), zap.Duration("duration", time.Since(start))) zap.L().Info("task finished", zap.String("task", t.task.Name()), zap.Duration("duration", time.Since(start)))
} }
o.runningTask.Store(nil) o.runningTask.Store(nil)
for _, cb := range t.callbacks {
cb(err)
}
if nextTime := t.task.Next(o.curTime()); nextTime != nil { if nextTime := t.task.Next(o.curTime()); nextTime != nil {
o.taskQueue.Push(scheduledTask{ o.taskQueue.Push(scheduledTask{
task: t.task, task: t.task,
@@ -247,7 +251,7 @@ func (o *Orchestrator) Run(mainCtx context.Context) {
} }
} }
func (o *Orchestrator) ScheduleTask(t Task, priority int) { func (o *Orchestrator) ScheduleTask(t Task, priority int, callbacks ...func(error)) {
nextRun := t.Next(o.curTime()) nextRun := t.Next(o.curTime())
if nextRun == nil { if nextRun == nil {
return return
@@ -257,6 +261,7 @@ func (o *Orchestrator) ScheduleTask(t Task, priority int) {
task: t, task: t,
runAt: *nextRun, runAt: *nextRun,
priority: priority, priority: priority,
callbacks: callbacks,
}) })
} }

View File

@@ -157,6 +157,7 @@ type scheduledTask struct {
task Task task Task
runAt time.Time runAt time.Time
priority int priority int
callbacks []func(error)
} }
type scheduledTaskHeap struct { type scheduledTaskHeap struct {

View File

@@ -24,7 +24,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
const handleBackupNow = async () => { const handleBackupNow = async () => {
try { try {
backrestService.backup({ value: plan.id }); await backrestService.backup({ value: plan.id });
alertsApi.success("Backup scheduled."); alertsApi.success("Backup scheduled.");
} catch (e: any) { } catch (e: any) {
alertsApi.error("Failed to schedule backup: " + e.message); alertsApi.error("Failed to schedule backup: " + e.message);

View File

@@ -67,7 +67,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
<> <>
<h3>Repo stats computed on {formatTime(Number(statsOperation.unixTimeStartMs))}</h3> <h3>Repo stats computed on {formatTime(Number(statsOperation.unixTimeStartMs))}</h3>
{statsOperation.op.case === "operationStats" && <StatsTable stats={statsOperation.op.value.stats!} />} {statsOperation.op.case === "operationStats" && <StatsTable stats={statsOperation.op.value.stats!} />}
<small>Stats are refreshed periodically in the background as new data is added.</small> <small>Stats are refreshed periodically in the background as new data is added (e.g. every 10GB added or every 50 operations).</small>
</> </>
} }
</> </>