diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cafb52 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Orbis + +!(orbis demo)[assets/demo.gif] diff --git a/assets/demo.gif b/assets/demo.gif new file mode 100644 index 0000000..96b949d Binary files /dev/null and b/assets/demo.gif differ diff --git a/cmd/orbis/main.go b/cmd/orbis/main.go index 9d7dc61..48d4f69 100644 --- a/cmd/orbis/main.go +++ b/cmd/orbis/main.go @@ -1,8 +1,12 @@ package main import ( + "context" "fmt" "os" + "os/signal" + "syscall" + "time" "git.front.kjuulh.io/kjuulh/orbis/internal/app" "github.com/joho/godotenv" @@ -17,7 +21,23 @@ func main() { app := app.NewApp() - if err := newRoot(app).Execute(); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + + go func() { + <-stop + + app.Logger().Info("stop signal received: shutting down orbis") + cancel() + + // Start timer for hard stop + time.Sleep(time.Second * 10) + fmt.Println("orbis failed to stop in time, forced to hard cancel") + os.Exit(1) + }() + + if err := newRoot(app).ExecuteContext(ctx); err != nil { fmt.Printf("%s\n", err) os.Exit(1) } diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 8897864..0dacb39 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -18,10 +18,20 @@ func NewScheduler(logger *slog.Logger) *Scheduler { } func (s *Scheduler) Execute(ctx context.Context) error { + acquiredLeader, err := s.acquireLeader(ctx) + if err != nil { + return err + } + + if !acquiredLeader { + s.logger.Info("gracefully shutting down non-elected scheduler") + return nil + } + for { select { case <-ctx.Done(): - s.logger.Info("gracefully shutting down scheduler") + s.logger.Info("gracefully shutting down elected scheduler") return nil default: if err := s.process(ctx); err != nil { @@ -31,11 +41,26 @@ func (s *Scheduler) Execute(ctx context.Context) error { } } +func (s *Scheduler) acquireLeader(ctx context.Context) (bool, error) { + for { + select { + case <-ctx.Done(): + return false, nil + + default: + // Attempt to acquire leader + // + return true, nil + + } + } +} + func (s *Scheduler) process(ctx context.Context) error { s.logger.Debug("scheduler processing items") // FIXME: simulate work - time.Sleep(time.Second * 5) + time.Sleep(time.Second * 2) return nil }