Skip to content

Commit

Permalink
Refactor application shutdown (evcc-io#4757)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Oct 8, 2022
1 parent 4881aaa commit 430c91c
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 139 deletions.
8 changes: 2 additions & 6 deletions cmd/charger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"strings"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/util"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -57,9 +56,6 @@ func runCharger(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}

stopC := make(chan struct{})
go shutdown.Run(stopC)

chargers := cp.chargers
if len(args) == 1 {
name := args[0]
Expand Down Expand Up @@ -146,6 +142,6 @@ func runCharger(cmd *cobra.Command, args []string) {
}
}

close(stopC)
<-shutdown.Done()
// wait for shutdown
<-shutdownDoneC()
}
8 changes: 2 additions & 6 deletions cmd/charger_ramp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/util"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -91,9 +90,6 @@ func runChargerRamp(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}

stopC := make(chan struct{})
go shutdown.Run(stopC)

chargers := cp.chargers
if len(args) == 1 {
name := args[0]
Expand Down Expand Up @@ -121,6 +117,6 @@ func runChargerRamp(cmd *cobra.Command, args []string) {
ramp(c, digits, delay)
}

close(stopC)
<-shutdown.Done()
// wait for shutdown
<-shutdownDoneC()
}
12 changes: 1 addition & 11 deletions cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"syscall"

"github.com/evcc-io/evcc/cmd/configure"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -53,24 +52,15 @@ func runConfigure(cmd *cobra.Command, args []string) {

util.LogLevel(viper.GetString("log"), nil)

stopC := make(chan struct{})
go shutdown.Run(stopC)

// catch signals
go func() {
signalC := make(chan os.Signal, 1)
signal.Notify(signalC, os.Interrupt, syscall.SIGTERM)

<-signalC // wait for signal
close(stopC) // signal loop to end

<-shutdown.Done()
<-signalC // wait for signal

os.Exit(1)
}()

impl.Run(log, lang, advanced, expand, category)

close(stopC)
<-shutdown.Done()
}
93 changes: 93 additions & 0 deletions cmd/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cmd

import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

"github.com/evcc-io/evcc/cmd/shutdown"
)

// unwrap converts a wrapped error into slice of strings
func unwrap(err error) (res []string) {
for err != nil {
inner := errors.Unwrap(err)
if inner == nil {
res = append(res, err.Error())
} else {
cur := strings.TrimSuffix(err.Error(), ": "+inner.Error())
cur = strings.TrimSuffix(cur, inner.Error())
res = append(res, strings.TrimSpace(cur))
}
err = inner
}
return
}

// redact redacts a configuration string
func redact(src string) string {
secrets := []string{
"url", "uri", "host", "broker", "mac", // infrastructure
"sponsortoken", "plant", // global settings
"user", "password", "pin", // users
"token", "access", "refresh", // tokens
"ain", "id", "secret", "serial", "deviceid", "machineid", // devices
"vin"} // vehicles
return regexp.
MustCompile(fmt.Sprintf(`\b(%s)\b.*?:.*`, strings.Join(secrets, "|"))).
ReplaceAllString(src, "$1: *****")
}

func publishErrorInfo(cfgFile string, err error) {
if cfgFile != "" {
file, pathErr := filepath.Abs(cfgFile)
if pathErr != nil {
file = cfgFile
}
publish("file", file)

if src, fileErr := os.ReadFile(cfgFile); fileErr != nil {
log.ERROR.Println("could not open config file:", fileErr)
} else {
publish("config", redact(string(src)))

// find line number
if match := regexp.MustCompile(`yaml: line (\d+):`).FindStringSubmatch(err.Error()); len(match) == 2 {
if line, err := strconv.Atoi(match[1]); err == nil {
publish("line", line)
}
}
}
}

publish("fatal", unwrap(err))
}

// fatal logs a fatal error and runs shutdown functions before terminating
func fatal(err error) {
log.FATAL.Println(err)
<-shutdownDoneC()
os.Exit(1)
}

// shutdownDoneC returns a channel that closes when shutdown has completed
func shutdownDoneC() <-chan struct{} {
doneC := make(chan struct{})
go shutdown.Cleanup(doneC)
return doneC
}

// exitWhenDone waits for shutdown to complete with timeout
func exitWhenDone(timeout time.Duration) {
select {
case <-shutdownDoneC(): // wait for shutdown
case <-time.After(timeout):
}

os.Exit(1)
}
3 changes: 3 additions & 0 deletions cmd/meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,7 @@ func runMeter(cmd *cobra.Command, args []string) {
for name, v := range meters {
d.DumpWithHeader(name, v)
}

// wait for shutdown
<-shutdownDoneC()
}
115 changes: 16 additions & 99 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ import (
_ "net/http/pprof" // pprof handler
"os"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
"time"

"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/core"
"github.com/evcc-io/evcc/push"
"github.com/evcc-io/evcc/server"
Expand Down Expand Up @@ -101,59 +97,6 @@ func publish(key string, val any) {
valueChan <- util.Param{Key: key, Val: val}
}

func unwrap(err error) (res []string) {
for err != nil {
inner := errors.Unwrap(err)
if inner == nil {
res = append(res, err.Error())
} else {
cur := strings.TrimSuffix(err.Error(), ": "+inner.Error())
cur = strings.TrimSuffix(cur, inner.Error())
res = append(res, strings.TrimSpace(cur))
}
err = inner
}
return
}

func redact(src string) string {
secrets := []string{
"url", "uri", "host", "broker", "mac", // infrastructure
"sponsortoken", "plant", // global settings
"user", "password", "pin", // users
"token", "access", "refresh", // tokens
"ain", "id", "secret", "serial", "deviceid", "machineid", // devices
"vin"} // vehicles
return regexp.
MustCompile(fmt.Sprintf(`\b(%s)\b.*?:.*`, strings.Join(secrets, "|"))).
ReplaceAllString(src, "$1: *****")
}

func publishErrorInfo(cfgFile string, err error) {
if cfgFile != "" {
file, pathErr := filepath.Abs(cfgFile)
if pathErr != nil {
file = cfgFile
}
publish("file", file)

if src, fileErr := os.ReadFile(cfgFile); fileErr != nil {
log.ERROR.Println("could not open config file:", fileErr)
} else {
publish("config", redact(string(src)))

// find line number
if match := regexp.MustCompile(`yaml: line (\d+):`).FindStringSubmatch(err.Error()); len(match) == 2 {
if line, err := strconv.Atoi(match[1]); err == nil {
publish("line", line)
}
}
}
}

publish("fatal", unwrap(err))
}

func runRoot(cmd *cobra.Command, args []string) {
util.LogLevel(viper.GetString("log"), viper.GetStringMapString("levels"))
log.INFO.Printf("evcc %s", server.FormattedVersion())
Expand Down Expand Up @@ -209,11 +152,6 @@ func runRoot(cmd *cobra.Command, args []string) {
err = configureEnvironment(cmd, conf)
}

// setup session log
if err == nil && conf.Database.Dsn != "" {
err = configureDatabase(conf.Database)
}

// setup site and loadpoints
var site *core.Site
if err == nil {
Expand Down Expand Up @@ -248,10 +186,18 @@ func runRoot(cmd *cobra.Command, args []string) {
pushChan, err = configureMessengers(conf.Messaging, cache)
}

// run shutdown functions on stop
var once sync.Once
stopC := make(chan struct{})
go shutdown.Run(stopC)

siteC := make(chan struct{})
// catch signals
go func() {
signalC := make(chan os.Signal, 1)
signal.Notify(signalC, os.Interrupt, syscall.SIGTERM)

<-signalC // wait for signal
once.Do(func() { close(stopC) }) // signal loop to end
}()

// show main ui
if err == nil {
Expand All @@ -277,14 +223,12 @@ func runRoot(cmd *cobra.Command, args []string) {

go func() {
site.Run(stopC, conf.Interval)
close(siteC)
}()
} else {
var once sync.Once
httpd.RegisterShutdownHandler(func() {
once.Do(func() {
log.FATAL.Println("evcc was stopped. OS should restart the service. Or restart manually.")
close(siteC)
close(stopC) // signal loop to end
})
})

Expand All @@ -296,42 +240,15 @@ func runRoot(cmd *cobra.Command, args []string) {

publishErrorInfo(cfgFile, err)

go func() {
select {
case <-time.After(rebootDelay):
case <-siteC:
}
os.Exit(1)
}()
// wait for shutdown
go exitWhenDone(rebootDelay)
}

// uds health check listener
go server.HealthListener(site, siteC)
go server.HealthListener(site)

// catch signals
go func() {
signalC := make(chan os.Signal, 1)
signal.Notify(signalC, os.Interrupt, syscall.SIGTERM)

<-signalC // wait for signal
close(stopC) // signal loop to end

exitC := make(chan struct{})
wg := new(sync.WaitGroup)
wg.Add(2)

// wait for main loop and shutdown functions to finish
go func() { <-shutdown.Done(); wg.Done() }()
go func() { <-siteC; wg.Done() }()
go func() { wg.Wait(); close(exitC) }()

select {
case <-exitC: // wait for loop to end
case <-time.NewTimer(conf.Interval).C: // wait max 1 period
}

os.Exit(1)
}()
// wait for shutdown
go exitWhenDone(conf.Interval)

log.FATAL.Println(httpd.ListenAndServe())
}
5 changes: 5 additions & 0 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ func configureEnvironment(cmd *cobra.Command, conf config) (err error) {
err = sponsor.ConfigureSponsorship(conf.SponsorToken)
}

// setup persistence
if err == nil && conf.Database.Dsn != "" {
err = configureDatabase(conf.Database)
}

// setup telemetry
if err == nil && conf.Telemetry {
err = telemetry.Create(sponsor.Token, conf.Plant)
Expand Down
Loading

0 comments on commit 430c91c

Please sign in to comment.