forked from apps/featurer
115 lines
2.9 KiB
Go
115 lines
2.9 KiB
Go
package application
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
)
|
|
|
|
var (
|
|
// ErrApplication indicates that error appeared somewhere in application's lifecycle controller.
|
|
ErrApplication = errors.New("application")
|
|
|
|
// Version is application's version.
|
|
Version string
|
|
// Branch is a branch name from which application was built.
|
|
Branch string
|
|
// Commit is a commit hash from which application was built.
|
|
Commit string
|
|
// Build is a build number.
|
|
Build string
|
|
// BuildDate is a date on which application was built.
|
|
BuildDate string
|
|
)
|
|
|
|
// Application is an application's lifecycle controlling structure.
|
|
type Application struct {
|
|
ctx context.Context
|
|
shutdownDone chan struct{}
|
|
cancelFunc context.CancelFunc
|
|
services map[string]Service
|
|
dataPath string
|
|
servicesMutex sync.RWMutex
|
|
}
|
|
|
|
// New creates new application's lifecycle controlling structure.
|
|
func New() *Application {
|
|
appl := &Application{}
|
|
appl.initialize()
|
|
|
|
return appl
|
|
}
|
|
|
|
// GetContext returns application's global context.
|
|
func (a *Application) GetContext() context.Context {
|
|
return a.ctx
|
|
}
|
|
|
|
// GetShutdownDoneChannel returns channel on which lifecycle controller will tell about shutdown completion.
|
|
// Should be used only in main()!
|
|
func (a *Application) GetShutdownDoneChannel() chan struct{} {
|
|
slog.Debug("Returning shutdown completion channel.")
|
|
|
|
return a.shutdownDone
|
|
}
|
|
|
|
func (a *Application) initialize() {
|
|
a.ctx, a.cancelFunc = context.WithCancel(context.Background())
|
|
a.ctx, a.cancelFunc = signal.NotifyContext(a.ctx, os.Interrupt, syscall.SIGTERM)
|
|
|
|
a.services = make(map[string]Service)
|
|
|
|
a.shutdownDone = make(chan struct{}, 1)
|
|
}
|
|
|
|
// Shutdown stops application and all registered services.
|
|
func (a *Application) Shutdown() error {
|
|
a.cancelFunc()
|
|
|
|
a.servicesMutex.RLock()
|
|
defer a.servicesMutex.RUnlock()
|
|
|
|
for _, service := range a.services {
|
|
if err := service.Shutdown(); err != nil {
|
|
slog.Error("Error appeared when trying to shut down service", "service", service.GetName(), "error", err.Error())
|
|
}
|
|
}
|
|
|
|
a.shutdownDone <- struct{}{}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Start starts application and all registered services.
|
|
func (a *Application) Start() error {
|
|
go a.signalsListener()
|
|
|
|
a.servicesMutex.RLock()
|
|
defer a.servicesMutex.RUnlock()
|
|
|
|
// First pass - connecting dependencies.
|
|
for _, service := range a.services {
|
|
if err := service.ConnectDependencies(); err != nil {
|
|
return fmt.Errorf("%w: connecting dependencies for service '%s': %w", ErrApplication, service.GetName(), err)
|
|
}
|
|
}
|
|
|
|
// Second pass - launching startup tasks.
|
|
for name, service := range a.services {
|
|
slog.Debug("Launching startup tasks for service", "service", name)
|
|
|
|
if err := service.LaunchStartupTasks(); err != nil {
|
|
return fmt.Errorf("%w: launching startup tasks for '%s': %w", ErrApplication, service.GetName(), err)
|
|
}
|
|
}
|
|
|
|
slog.Debug("Application started.")
|
|
|
|
return nil
|
|
}
|