Skip to content

Commit

Permalink
[#1671]: feature: sd_notify support
Browse files Browse the repository at this point in the history
  • Loading branch information
rustatian authored Aug 15, 2023
2 parents 67875c3 + 80f2569 commit 1aad8be
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 56 deletions.
4 changes: 0 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ linters-settings:
check-shadowing: true
golint:
min-confidence: 0.1
gocyclo:
min-complexity: 15
godot:
scope: declarations
capital: true
Expand Down Expand Up @@ -54,10 +52,8 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
- exhaustive # check exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- gochecknoglobals # Checks that no globals are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
- revive
Expand Down
55 changes: 19 additions & 36 deletions container/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import (
"golang.org/x/exp/slog"
)

// Config defines endure container configuration.
type Config struct {
GracePeriod time.Duration
PrintGraph bool
LogLevel slog.Leveler
GracePeriod time.Duration `mapstructure:"grace_period"`
LogLevel string `mapstructure:"log_level"`
WatchdogSec int `mapstructure:"watchdog_sec"`
PrintGraph bool `mapstructure:"print_graph"`
}

const (
endureKey = "endure"
// endure config key
endureKey = "endure"
// overall grace period, after which container will be stopped forcefully
defaultGracePeriod = time.Second * 30
)

Expand All @@ -29,46 +33,25 @@ func NewConfig(cfgFile string) (*Config, error) {
return nil, err
}

if !v.IsSet(endureKey) {
return &Config{ // return config with defaults
GracePeriod: defaultGracePeriod,
PrintGraph: false,
LogLevel: slog.LevelError,
}, nil
}

rrCfgEndure := struct {
GracePeriod time.Duration `mapstructure:"grace_period"`
PrintGraph bool `mapstructure:"print_graph"`
LogLevel string `mapstructure:"log_level"`
}{}

err = v.UnmarshalKey(endureKey, &rrCfgEndure)
if err != nil {
return nil, err
}

if rrCfgEndure.GracePeriod == 0 {
rrCfgEndure.GracePeriod = defaultGracePeriod
cfg := &Config{
GracePeriod: defaultGracePeriod,
LogLevel: "error",
PrintGraph: false,
}

if rrCfgEndure.LogLevel == "" {
rrCfgEndure.LogLevel = "error"
if !v.IsSet(endureKey) {
return cfg, nil
}

logLevel, err := parseLogLevel(rrCfgEndure.LogLevel)
err = v.UnmarshalKey(endureKey, cfg)
if err != nil {
return nil, err
}

return &Config{
GracePeriod: rrCfgEndure.GracePeriod,
PrintGraph: rrCfgEndure.PrintGraph,
LogLevel: logLevel,
}, nil
return cfg, nil
}

func parseLogLevel(s string) (slog.Leveler, error) {
func ParseLogLevel(s string) (slog.Leveler, error) {
switch s {
case "debug":
return slog.LevelDebug, nil
Expand All @@ -78,7 +61,7 @@ func parseLogLevel(s string) (slog.Leveler, error) {
return slog.LevelWarn, nil
case "error":
return slog.LevelError, nil
default:
return slog.LevelError, fmt.Errorf(`unknown log level "%s" (allowed: debug, info, warn, error)`, s)
}

return slog.LevelInfo, fmt.Errorf(`unknown log level "%s" (allowed: debug, info, warn, error)`, s)
}
21 changes: 14 additions & 7 deletions container/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ func TestNewConfig_SuccessfulReading(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, c)

ll, err := container.ParseLogLevel(c.LogLevel)
assert.NoError(t, err)

assert.Equal(t, time.Second*10, c.GracePeriod)
assert.True(t, c.PrintGraph)
assert.Equal(t, slog.LevelWarn, c.LogLevel)
assert.Equal(t, slog.LevelWarn, ll.Level())
}

func TestNewConfig_WithoutEndureKey(t *testing.T) {
Expand All @@ -28,9 +31,12 @@ func TestNewConfig_WithoutEndureKey(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, c)

ll, err := container.ParseLogLevel(c.LogLevel)
assert.NoError(t, err)

assert.Equal(t, time.Second*30, c.GracePeriod)
assert.False(t, c.PrintGraph)
assert.Equal(t, slog.LevelError, c.LogLevel)
assert.Equal(t, slog.LevelError, ll.Level())
}

func TestNewConfig_LoggingLevels(t *testing.T) {
Expand All @@ -53,15 +59,16 @@ func TestNewConfig_LoggingLevels(t *testing.T) {
assert.NoError(t, cfgPlugin.Init())

c, err := container.NewConfig(tt.path)
assert.NotNil(t, c)
ll, err2 := container.ParseLogLevel(c.LogLevel)

if tt.wantError {
assert.Nil(t, c)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown log level")
assert.Error(t, err2)
assert.Contains(t, err2.Error(), "unknown log level")
} else {
assert.NoError(t, err)
assert.NotNil(t, c)
assert.Equal(t, tt.wantLevel, c.LogLevel)
assert.NoError(t, err2)
assert.Equal(t, tt.wantLevel, ll.Level())
}
})
}
Expand Down
7 changes: 6 additions & 1 deletion internal/cli/reset/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"

internalRpc "github.com/roadrunner-server/roadrunner/v2023/internal/rpc"
"github.com/roadrunner-server/roadrunner/v2023/internal/sdnotify"

"github.com/roadrunner-server/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -33,13 +34,15 @@ func NewCommand(cfgFile *string, override *[]string, silent *bool) *cobra.Comman

defer func() { _ = client.Close() }()

plugins := args // by default we expect services list from user
plugins := args // by default, we expect services list from user
if len(plugins) == 0 { // but if nothing was passed - request all services list
if err = client.Call(resetterList, true, &plugins); err != nil {
return err
}
}

_, _ = sdnotify.SdNotify(sdnotify.Reloading)

var wg sync.WaitGroup
wg.Add(len(plugins))

Expand Down Expand Up @@ -68,6 +71,8 @@ func NewCommand(cfgFile *string, override *[]string, silent *bool) *cobra.Comman

wg.Wait()

_, _ = sdnotify.SdNotify(sdnotify.Ready)

return nil
},
}
Expand Down
5 changes: 2 additions & 3 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"runtime"
"strconv"

"github.com/joho/godotenv"
"github.com/roadrunner-server/errors"
"github.com/roadrunner-server/roadrunner/v2023/internal/cli/jobs"
"github.com/roadrunner-server/roadrunner/v2023/internal/cli/reset"
Expand All @@ -15,8 +16,6 @@ import (
"github.com/roadrunner-server/roadrunner/v2023/internal/cli/workers"
dbg "github.com/roadrunner-server/roadrunner/v2023/internal/debug"
"github.com/roadrunner-server/roadrunner/v2023/internal/meta"

"github.com/joho/godotenv"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -48,7 +47,7 @@ func NewCommand(cmdName string) *cobra.Command { //nolint:funlen,gocognit

cmd := &cobra.Command{
Use: cmdName,
Short: "High-performance PHP application server, load-balancer and process manager",
Short: "High-performance PHP application server, process manager written in Golang and powered with ❤️ (by SpiralScout)",
SilenceErrors: true,
SilenceUsage: true,
Version: fmt.Sprintf("%s (build time: %s, %s), OS: %s, arch: %s", meta.Version(), meta.BuildTime(), runtime.Version(), runtime.GOOS, runtime.GOARCH),
Expand Down
43 changes: 39 additions & 4 deletions internal/cli/serve/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/roadrunner-server/endure/v2"
"github.com/roadrunner-server/roadrunner/v2023/container"
"github.com/roadrunner-server/roadrunner/v2023/internal/meta"
"github.com/roadrunner-server/roadrunner/v2023/internal/sdnotify"

configImpl "github.com/roadrunner-server/config/v4"
"github.com/roadrunner-server/errors"
Expand Down Expand Up @@ -54,7 +55,13 @@ func NewCommand(override *[]string, cfgFile *string, silent *bool) *cobra.Comman
}

// create endure container
cont := endure.New(containerCfg.LogLevel, endureOptions...)
ll, err := container.ParseLogLevel(containerCfg.LogLevel)
if err != nil {
if !*silent {
fmt.Printf("[WARN] Failed to parse log level, using default (error): %s\n", err)
}
}
cont := endure.New(ll, endureOptions...)

// register plugins
err = cont.RegisterAll(append(container.Plugins(), cfg)...)
Expand All @@ -75,16 +82,18 @@ func NewCommand(override *[]string, cfgFile *string, silent *bool) *cobra.Comman
}

oss, stop := make(chan os.Signal, 5), make(chan struct{}, 1) //nolint:gomnd
signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT)

go func() {
// first catch - stop the container
<-oss
// send signal to stop execution
stop <- struct{}{}

// after first hit we are waiting for the second
// second catch - exit from the process
// notify about stopping
_, _ = sdnotify.SdNotify(sdnotify.Stopping)

// after first hit we are waiting for the second catch - exit from the process
<-oss
fmt.Println("exit forced")
os.Exit(1)
Expand All @@ -94,6 +103,32 @@ func NewCommand(override *[]string, cfgFile *string, silent *bool) *cobra.Comman
fmt.Printf("[INFO] RoadRunner server started; version: %s, buildtime: %s\n", meta.Version(), meta.BuildTime())
}

// at this moment, we're almost sure that the container is running (almost- because we don't know if the plugins won't report an error on the next step)
notified, err := sdnotify.SdNotify(sdnotify.Ready)
if err != nil {
if !*silent {
fmt.Printf("[WARN] sdnotify: %s\n", err)
}
}

if !*silent {
if notified {
fmt.Println("[INFO] sdnotify: notified")
stopCh := make(chan struct{}, 1)
if containerCfg.WatchdogSec > 0 {
fmt.Printf("[INFO] sdnotify: watchdog enabled, timeout: %d seconds\n", containerCfg.WatchdogSec)
sdnotify.StartWatchdog(containerCfg.WatchdogSec, stopCh)
}

// if notified -> notify about stop
defer func() {
stopCh <- struct{}{}
}()
} else {
fmt.Println("[INFO] sdnotify: not notified")
}
}

for {
select {
case e := <-errCh:
Expand Down
2 changes: 2 additions & 0 deletions internal/cli/stop/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/roadrunner-server/errors"
"github.com/roadrunner-server/roadrunner/v2023/internal/sdnotify"
"github.com/spf13/cobra"
)

Expand All @@ -24,6 +25,7 @@ func NewCommand(silent *bool, force *bool) *cobra.Command {
RunE: func(*cobra.Command, []string) error {
const op = errors.Op("rr_stop")

_, _ = sdnotify.SdNotify(sdnotify.Stopping)
data, err := os.ReadFile(pidFileName)
if err != nil {
return errors.Errorf("%v, to create a .pid file, you must run RR with the following options: './rr serve -p'", err)
Expand Down
Loading

0 comments on commit 1aad8be

Please sign in to comment.