diff options
author | Valery Piashchynski <[email protected]> | 2022-05-31 16:01:30 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2022-05-31 16:01:30 +0200 |
commit | 854c6b9a6d22c99984a8064e64e444c32209c9bd (patch) | |
tree | 4901ed62317c13d451b62c2f9d32f33854d03c91 | |
parent | f0179128570e6ed005086d9c9dbcd11964955c24 (diff) | |
parent | 2aba7854e5b916a9a72b3c87c1eba0d3071006d7 (diff) |
[#1163]: feat(CLI): `rr stop` command
-rw-r--r-- | .pid | 1 | ||||
-rw-r--r-- | internal/cli/root.go | 67 | ||||
-rw-r--r-- | internal/cli/serve/command.go | 6 | ||||
-rw-r--r-- | internal/cli/stop/command.go | 63 | ||||
-rw-r--r-- | internal/cli/stop/command_test.go | 27 |
5 files changed, 142 insertions, 22 deletions
@@ -0,0 +1 @@ +181292
\ No newline at end of file diff --git a/internal/cli/root.go b/internal/cli/root.go index 50f93795..1af7ef41 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -5,10 +5,12 @@ import ( "os" "path/filepath" "runtime" + "strconv" "github.com/roadrunner-server/errors" "github.com/roadrunner-server/roadrunner/v2/internal/cli/reset" "github.com/roadrunner-server/roadrunner/v2/internal/cli/serve" + "github.com/roadrunner-server/roadrunner/v2/internal/cli/stop" "github.com/roadrunner-server/roadrunner/v2/internal/cli/workers" dbg "github.com/roadrunner-server/roadrunner/v2/internal/debug" "github.com/roadrunner-server/roadrunner/v2/internal/meta" @@ -17,21 +19,31 @@ import ( "github.com/spf13/cobra" ) -// NewCommand creates root command. -func NewCommand(cmdName string) *cobra.Command { //nolint:funlen - const ( - envDotenv string = "DOTENV_PATH" // env var name: path to the .env file - ) +const ( + // env var name: path to the .env file + envDotenv string = "DOTENV_PATH" + pidFileName string = ".pid" +) - var ( // flag values - cfgFile = strPtr("") // path to the .rr.yaml - workDir string // working directory - dotenv string // path to the .env file - debug bool // debug mode - override = &[]string{} // override config values - // do not print startup message - silent = boolPtr(false) - ) +// NewCommand creates root command. +func NewCommand(cmdName string) *cobra.Command { //nolint:funlen,gocognit + // path to the .rr.yaml + cfgFile := toPtr("") + // pidfile path + pidFile := toPtr(false) + // force stop RR + forceStop := toPtr(false) + // override config values + override := &[]string{} + // do not print startup message + silent := toPtr(false) + + // working directory + var workDir string + // path to the .env file + var dotenv string + // debug mode + var debug bool cmd := &cobra.Command{ Use: cmdName, @@ -81,12 +93,30 @@ func NewCommand(cmdName string) *cobra.Command { //nolint:funlen go func() { _ = srv.Start(":6061") }() // TODO implement graceful server stopping } + // user wanted to write a .pid file + if *pidFile { + f, err := os.Create(pidFileName) + if err != nil { + return err + } + defer func() { + _ = f.Close() + }() + + _, err = f.WriteString(strconv.Itoa(os.Getpid())) + if err != nil { + return err + } + } + return nil }, } f := cmd.PersistentFlags() + f.BoolVarP(forceStop, "force", "f", false, "force stop") + f.BoolVarP(pidFile, "pid", "p", false, "create a .pid file") f.StringVarP(cfgFile, "config", "c", ".rr.yaml", "config file") f.StringVarP(&workDir, "WorkDir", "w", "", "working directory") f.StringVarP(&dotenv, "dotenv", "", "", fmt.Sprintf("dotenv file [$%s]", envDotenv)) @@ -98,15 +128,12 @@ func NewCommand(cmdName string) *cobra.Command { //nolint:funlen workers.NewCommand(cfgFile, override), reset.NewCommand(cfgFile, override, silent), serve.NewCommand(override, cfgFile, silent), + stop.NewCommand(silent, forceStop), ) return cmd } -func strPtr(s string) *string { - return &s -} - -func boolPtr(b bool) *bool { - return &b +func toPtr[T any](val T) *T { + return &val } diff --git a/internal/cli/serve/command.go b/internal/cli/serve/command.go index 3a1e945c..a85beb89 100644 --- a/internal/cli/serve/command.go +++ b/internal/cli/serve/command.go @@ -5,6 +5,7 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/roadrunner-server/roadrunner/v2/internal/container" "github.com/roadrunner-server/roadrunner/v2/internal/meta" @@ -73,7 +74,7 @@ func NewCommand(override *[]string, cfgFile *string, silent *bool) *cobra.Comman return errors.E(op, err) } - oss, stop := make(chan os.Signal, 2), make(chan struct{}, 1) //nolint:gomnd + oss, stop := make(chan os.Signal, 5), make(chan struct{}, 1) //nolint:gomnd signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) go func() { @@ -98,8 +99,9 @@ func NewCommand(override *[]string, cfgFile *string, silent *bool) *cobra.Comman case e := <-errCh: return fmt.Errorf("error: %w\nplugin: %s", e.Error, e.VertexID) case <-stop: // stop the container after first signal - fmt.Printf("stop signal received, grace timeout is: %f seconds\n", containerCfg.GracePeriod.Seconds()) + fmt.Printf("stop signal received, grace timeout is: %0.f seconds\n", containerCfg.GracePeriod.Seconds()) + time.Sleep(time.Second * 100) if err = endureContainer.Stop(); err != nil { return fmt.Errorf("error: %w", err) } diff --git a/internal/cli/stop/command.go b/internal/cli/stop/command.go new file mode 100644 index 00000000..533f11d6 --- /dev/null +++ b/internal/cli/stop/command.go @@ -0,0 +1,63 @@ +package stop + +import ( + "log" + "os" + "strconv" + "syscall" + "time" + + "github.com/roadrunner-server/errors" + "github.com/spf13/cobra" +) + +const ( + // sync with root.go + pidFileName string = ".pid" +) + +// NewCommand creates `serve` command. +func NewCommand(silent *bool, force *bool) *cobra.Command { + return &cobra.Command{ + Use: "stop", + Short: "Stop RoadRunner server", + RunE: func(*cobra.Command, []string) error { + const op = errors.Op("rr_stop") + + 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) + } + + pid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.E(op, err) + } + + process, err := os.FindProcess(pid) + if err != nil { + return errors.E(op, err) + } + + if !*silent { + log.Printf("stopping process with PID: %d", pid) + } + + err = process.Signal(syscall.SIGTERM) + if err != nil { + return errors.E(op, err) + } + + if *force { + // RR may lost the signal if we immediately send it + time.Sleep(time.Second) + err = process.Signal(syscall.SIGTERM) + if err != nil { + return errors.E(op, err) + } + } + + return nil + }, + } +} diff --git a/internal/cli/stop/command_test.go b/internal/cli/stop/command_test.go new file mode 100644 index 00000000..13f01bed --- /dev/null +++ b/internal/cli/stop/command_test.go @@ -0,0 +1,27 @@ +package stop_test + +import ( + "testing" + + "github.com/roadrunner-server/roadrunner/v2/internal/cli/stop" + + "github.com/stretchr/testify/assert" +) + +func TestCommandProperties(t *testing.T) { + cmd := stop.NewCommand(toPtr(false), toPtr(false)) + + assert.Equal(t, "stop", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func TestCommandTrue(t *testing.T) { + cmd := stop.NewCommand(toPtr(true), toPtr(true)) + + assert.Equal(t, "stop", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func toPtr[T any](val T) *T { + return &val +} |