Shutdown gracefully, close DB connection
This commit is contained in:
+63
-99
@@ -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
|
||||
|
||||
@@ -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
@@ -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...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user