clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / workbench / supervisor.go
supervisor.go 2.1K
1package workbench23import (4 "context"5 "sync"6 "time"78 "github.com/FelineStateMachine/atlas/internal/workbench/oprunner"9)1011// supervisedRun is the durable in-memory view of the latest build. The12// subprocess belongs to the workbench, not to the HTTP request that started it,13// so navigating away and returning never cancels or loses its progress.14type supervisedRun struct {15 Name string16 Command string17 StartedAt time.Time18 FinishedAt time.Time19 Running bool20 Failed bool21 Rows []oprunner.Row22 Artifact string23}2425type runSupervisor struct {26 runner *oprunner.Runner27 ctx context.Context28 cancel context.CancelFunc29 wg sync.WaitGroup3031 mu sync.Mutex32 last supervisedRun33}3435func newRunSupervisor(runner *oprunner.Runner) *runSupervisor {36 ctx, cancel := context.WithCancel(context.Background())37 return &runSupervisor{runner: runner, ctx: ctx, cancel: cancel}38}3940func (s *runSupervisor) Start(op oprunner.Operation) error {41 if err := op.Validate(); err != nil {42 return err43 }44 release, ok := s.runner.Acquire(op.Name)45 if !ok {46 return oprunner.ErrBusy47 }48 s.mu.Lock()49 s.last = supervisedRun{50 Name: op.Name, Command: op.Command(), StartedAt: time.Now().UTC(), Running: true,51 }52 s.mu.Unlock()53 s.wg.Add(1)54 go func() {55 defer s.wg.Done()56 defer release()57 err := oprunner.Stream(s.ctx, op, func(row oprunner.Row) error {58 s.mu.Lock()59 defer s.mu.Unlock()60 s.last.Rows = append(s.last.Rows, row)61 if row.Failed {62 s.last.Failed = true63 }64 for _, attr := range row.Attrs {65 if attr.Key == "artifact" {66 s.last.Artifact = attr.Value67 }68 }69 return nil70 })71 s.mu.Lock()72 s.last.Running = false73 s.last.FinishedAt = time.Now().UTC()74 if err != nil {75 s.last.Failed = true76 s.last.Rows = append(s.last.Rows, oprunner.Row{77 Seq: len(s.last.Rows) + 1, Kind: oprunner.KindResult,78 Message: err.Error(), Failed: true,79 })80 }81 s.mu.Unlock()82 }()83 return nil84}8586func (s *runSupervisor) Close() {87 s.cancel()88 s.wg.Wait()89}9091func (s *runSupervisor) Snapshot() supervisedRun {92 s.mu.Lock()93 defer s.mu.Unlock()94 held := s.last95 held.Rows = append([]oprunner.Row(nil), held.Rows...)96 return held97}