Shutdown gracefully, close DB connection

This commit is contained in:
Deluan
2022-11-21 12:14:06 -05:00
parent 5f3f7afb90
commit cd41d9a419
7 changed files with 163 additions and 111 deletions
+63 -99
View File
@@ -2,6 +2,7 @@ package cmd
import (
"context"
"errors"
"fmt"
"os"
"time"
@@ -12,13 +13,15 @@ import (
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/resources"
"github.com/navidrome/navidrome/scheduler"
"github.com/oklog/run"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/sync/errgroup"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var interrupted = errors.New("service was interrupted")
var (
cfgFile string
noBanner bool
@@ -55,118 +58,79 @@ func preRun() {
func runNavidrome() {
db.EnsureLatestVersion()
defer func() {
if err := db.Close(); err != nil {
log.Error("Error closing DB", err)
}
log.Info("Navidrome stopped gracefully. Bye.")
}()
var g run.Group
g, ctx := errgroup.WithContext(context.Background())
g.Go(startServer(ctx))
g.Go(startSignaler(ctx))
g.Go(startScheduler(ctx))
g.Go(schedulePeriodicScan(ctx))
g.Add(startServer())
g.Add(startSignaler())
g.Add(startScheduler())
schedule := conf.Server.ScanSchedule
if schedule != "" {
go schedulePeriodicScan(schedule)
} else {
log.Warn("Periodic scan is DISABLED")
}
if err := g.Run(); err != nil {
if err := g.Wait(); err != nil && !errors.Is(err, interrupted) {
log.Error("Fatal error in Navidrome. Aborting", err)
os.Exit(1)
}
}
func startServer() (func() error, func(err error)) {
func startServer(ctx context.Context) func() error {
return func() error {
a := CreateServer(conf.Server.MusicFolder)
a.MountRouter("Native API", consts.URLPathNativeAPI, CreateNativeAPIRouter())
a.MountRouter("Subsonic API", consts.URLPathSubsonicAPI, CreateSubsonicAPIRouter())
if conf.Server.LastFM.Enabled {
a.MountRouter("LastFM Auth", consts.URLPathNativeAPI+"/lastfm", CreateLastFMRouter())
}
if conf.Server.ListenBrainz.Enabled {
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
}
if conf.Server.Prometheus.Enabled {
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, promhttp.Handler())
}
return a.Run(fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port))
}, func(err error) {
if err != nil {
log.Error("Shutting down Server due to error", err)
} else {
log.Info("Shutting down Server")
}
a := CreateServer(conf.Server.MusicFolder)
a.MountRouter("Native API", consts.URLPathNativeAPI, CreateNativeAPIRouter())
a.MountRouter("Subsonic API", consts.URLPathSubsonicAPI, CreateSubsonicAPIRouter())
if conf.Server.LastFM.Enabled {
a.MountRouter("LastFM Auth", consts.URLPathNativeAPI+"/lastfm", CreateLastFMRouter())
}
}
var sigChan = make(chan os.Signal, 1)
func startSignaler() (func() error, func(err error)) {
scanner := GetScanner()
ctx, cancel := context.WithCancel(context.Background())
return func() error {
for {
select {
case sig := <-sigChan:
log.Info(ctx, "Received signal, triggering a new scan", "signal", sig)
start := time.Now()
err := scanner.RescanAll(ctx, false)
if err != nil {
log.Error(ctx, "Error scanning", err)
}
log.Info(ctx, "Triggered scan complete", "elapsed", time.Since(start).Round(100*time.Millisecond))
case <-ctx.Done():
return nil
}
}
}, func(err error) {
cancel()
if err != nil {
log.Error("Shutting down Signaler due to error", err)
} else {
log.Info("Shutting down Signaler")
}
if conf.Server.ListenBrainz.Enabled {
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
}
if conf.Server.Prometheus.Enabled {
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, promhttp.Handler())
}
return a.Run(ctx, fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port))
}
}
func schedulePeriodicScan(schedule string) {
scanner := GetScanner()
schedulerInstance := scheduler.GetInstance()
log.Info("Scheduling periodic scan", "schedule", schedule)
err := schedulerInstance.Add(schedule, func() {
_ = scanner.RescanAll(context.Background(), false)
})
if err != nil {
log.Error("Error scheduling periodic scan", err)
}
time.Sleep(2 * time.Second) // Wait 2 seconds before the initial scan
log.Debug("Executing initial scan")
if err := scanner.RescanAll(context.Background(), false); err != nil {
log.Error("Error executing initial scan", err)
}
log.Debug("Finished initial scan")
}
func startScheduler() (func() error, func(err error)) {
log.Info("Starting scheduler")
schedulerInstance := scheduler.GetInstance()
ctx, cancel := context.WithCancel(context.Background())
func schedulePeriodicScan(ctx context.Context) func() error {
return func() error {
schedulerInstance.Run(ctx)
schedule := conf.Server.ScanSchedule
if schedule == "" {
log.Warn("Periodic scan is DISABLED")
return nil
}, func(err error) {
cancel()
if err != nil {
log.Error("Shutting down Scheduler due to error", err)
} else {
log.Info("Shutting down Scheduler")
}
}
scanner := GetScanner()
schedulerInstance := scheduler.GetInstance()
log.Info("Scheduling periodic scan", "schedule", schedule)
err := schedulerInstance.Add(schedule, func() {
_ = scanner.RescanAll(ctx, false)
})
if err != nil {
log.Error("Error scheduling periodic scan", err)
}
time.Sleep(2 * time.Second) // Wait 2 seconds before the initial scan
log.Debug("Executing initial scan")
if err := scanner.RescanAll(ctx, false); err != nil {
log.Error("Error executing initial scan", err)
}
log.Debug("Finished initial scan")
return nil
}
}
func startScheduler(ctx context.Context) func() error {
log.Info(ctx, "Starting scheduler")
schedulerInstance := scheduler.GetInstance()
return func() error {
schedulerInstance.Run(ctx)
return nil
}
}
// TODO: Implement some struct tags to map flags to viper
+26
View File
@@ -0,0 +1,26 @@
//go:build !unix
package cmd
import (
"context"
"os"
"os/signal"
"github.com/navidrome/navidrome/log"
)
func startSignaler(ctx context.Context) func() error {
log.Info(ctx, "Starting signaler")
return func() error {
var sigChan = make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
select {
case <-sigChan:
return interrupted
case <-ctx.Done():
return nil
}
}
}
+39 -5
View File
@@ -1,16 +1,50 @@
//go:build !windows && !plan9
//go:build unix
package cmd
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"github.com/navidrome/navidrome/log"
)
func init() {
signals := []os.Signal{
syscall.SIGUSR1,
const triggerScanSignal = syscall.SIGUSR1
func startSignaler(ctx context.Context) func() error {
log.Info(ctx, "Starting signaler")
scanner := GetScanner()
return func() error {
var sigChan = make(chan os.Signal, 1)
signal.Notify(
sigChan,
os.Interrupt,
triggerScanSignal,
syscall.SIGHUP,
syscall.SIGTERM,
syscall.SIGABRT,
)
for {
select {
case sig := <-sigChan:
if sig != triggerScanSignal {
return interrupted
}
log.Info(ctx, "Received signal, triggering a new scan", "signal", sig)
start := time.Now()
err := scanner.RescanAll(ctx, false)
if err != nil {
log.Error(ctx, "Error scanning", err)
}
log.Info(ctx, "Triggered scan complete", "elapsed", time.Since(start).Round(100*time.Millisecond))
case <-ctx.Done():
return nil
}
}
}
signal.Notify(sigChan, signals...)
}