Skip to content
This repository has been archived by the owner on Dec 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #286 from vania-pooh/master
Browse files Browse the repository at this point in the history
Graceful shutdown support
  • Loading branch information
vania-pooh authored Dec 2, 2020
2 parents 414b5fd + a491e7c commit 103d482
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 39 deletions.
34 changes: 23 additions & 11 deletions cmd/selenoid.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"
"os"
"runtime"
"time"
)

var (
Expand All @@ -24,6 +25,8 @@ var (
skipDownload bool
vnc bool
force bool
graceful bool
gracefulTimeout time.Duration
args string
env string
browserEnv string
Expand Down Expand Up @@ -155,6 +158,13 @@ func initFlags() {
} {
c.Flags().BoolVarP(&force, "force", "f", false, "force action")
}
for _, c := range []*cobra.Command{
selenoidStopCmd,
selenoidStopUICmd,
} {
c.Flags().BoolVarP(&graceful, "graceful", "", false, "do action gracefully (e.g. gracefully stop Selenoid)")
c.Flags().DurationVarP(&gracefulTimeout, "graceful-timeout", "", 30*time.Second, "graceful timeout value (how much time to wait for graceful action execution)")
}
for _, c := range []*cobra.Command{
selenoidStartCmd,
selenoidUpdateCmd,
Expand All @@ -170,17 +180,19 @@ func initFlags() {

func createLifecycle(configDir string, port uint16) (*selenoid.Lifecycle, error) {
config := selenoid.LifecycleConfig{
Quiet: quiet,
Force: force,
ConfigDir: configDir,
UseDrivers: useDrivers,
Browsers: browsers,
BrowserEnv: browserEnv,
Download: !skipDownload,
Args: args,
Env: env,
Port: int(port),
DisableLogs: disableLogs,
Quiet: quiet,
Force: force,
Graceful: graceful,
GracefulTimeout: gracefulTimeout,
ConfigDir: configDir,
UseDrivers: useDrivers,
Browsers: browsers,
BrowserEnv: browserEnv,
Download: !skipDownload,
Args: args,
Env: env,
Port: int(port),
DisableLogs: disableLogs,

LastVersions: lastVersions,
RegistryUrl: registry,
Expand Down
9 changes: 5 additions & 4 deletions docs/selenoid-commands.adoc
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
== Quick Start Guide

This guide will show how to start Selenoid in a fastest way with help of Configuration Manager.
First, you need to download latest release binary from https://github.com/aerokube/cm/releases/latest[GitHub releases] for your platform (linux/darwin/windows).
To start Selenoid:

Having a binary launch one command:
. Download the latest release binary from https://github.com/aerokube/cm/releases/latest[GitHub releases] for your platform (linux/darwin/windows).

. Having the binary launch one command:
+
.On Linux and Mac OS
[source,bash]
----
$ ./cm selenoid start --vnc
----

+
.On Windows
[source,powershell]
----
Expand Down
6 changes: 6 additions & 0 deletions selenoid/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/user"
"path/filepath"
"time"

"github.com/fatih/color"
"github.com/mattn/go-colorable"
Expand Down Expand Up @@ -90,6 +91,11 @@ type Forceable struct {
Force bool
}

type GracefulAware struct {
Graceful bool
GracefulTimeout time.Duration
}

type VersionAware struct {
Version string
}
Expand Down
12 changes: 11 additions & 1 deletion selenoid/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type DockerConfigurator struct {
PortAware
UserNSAware
LogsAware
GracefulAware
LastVersions int
Pull bool
RegistryUrl string
Expand All @@ -102,6 +103,7 @@ func NewDockerConfigurator(config *LifecycleConfig) (*DockerConfigurator, error)
PortAware: PortAware{Port: config.Port},
UserNSAware: UserNSAware{UserNS: config.UserNS},
LogsAware: LogsAware{DisableLogs: config.DisableLogs},
GracefulAware: GracefulAware{Graceful: config.Graceful, GracefulTimeout: config.GracefulTimeout},
RegistryUrl: config.RegistryUrl,
BrowsersJson: config.BrowsersJson,
LastVersions: config.LastVersions,
Expand Down Expand Up @@ -981,7 +983,15 @@ func (c *DockerConfigurator) createNetworkIfNeeded(networkName string) error {
}

func (c *DockerConfigurator) removeContainer(id string) error {
return c.docker.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
ctx := context.Background()
if c.Graceful {
err := c.docker.ContainerStop(ctx, id, &c.GracefulTimeout)
if err == nil {
return c.docker.ContainerRemove(ctx, id, types.ContainerRemoveOptions{RemoveVolumes: true})
}
return err
}
return c.docker.ContainerRemove(ctx, id, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
}

func (c *DockerConfigurator) Stop() error {
Expand Down
46 changes: 35 additions & 11 deletions selenoid/drivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"regexp"
"runtime"
"strings"
"syscall"
"time"
)

const (
Expand Down Expand Up @@ -67,6 +69,7 @@ type DriversConfigurator struct {
PortAware
RequestedBrowsersAware
LogsAware
GracefulAware
DriversInfoUrl string

GithubBaseUrl string
Expand All @@ -86,6 +89,7 @@ func NewDriversConfigurator(config *LifecycleConfig) *DriversConfigurator {
DownloadAware: DownloadAware{DownloadNeeded: config.Download},
RequestedBrowsersAware: RequestedBrowsersAware{Browsers: config.Browsers},
LogsAware: LogsAware{DisableLogs: config.DisableLogs},
GracefulAware: GracefulAware{Graceful: config.Graceful, GracefulTimeout: config.GracefulTimeout},
DriversInfoUrl: config.DriversInfoUrl,
GithubBaseUrl: config.GithubBaseUrl,
OS: config.OS,
Expand Down Expand Up @@ -615,21 +619,41 @@ func (d *DriversConfigurator) StartUI() error {
return runCommand(d.getSelenoidUIBinaryPath(), args, env)
}

var killFunc = func(p os.Process) error {
return p.Kill()
var killFunc = func(p *os.Process, graceful bool, gracefulTimeout time.Duration) error {
if isWindows() || !graceful {
return p.Kill()
}
err := p.Signal(syscall.SIGTERM)
if err != nil {
return fmt.Errorf("failed to send signal: %v", err)
}
exitCode := make(chan int)
go func() {
ps, _ := p.Wait()
exitCode <- ps.ExitCode()
}()
select {
case <-time.After(gracefulTimeout):
return p.Kill()
case code := <-exitCode:
if code != 0 {
return fmt.Errorf("process exited with code %d", code)
}
return nil
}
}

func (d *DriversConfigurator) Stop() error {
return killAllProcesses(findSelenoidProcesses())
return d.killAllProcesses(findSelenoidProcesses())
}

func (d *DriversConfigurator) StopUI() error {
return killAllProcesses(findSelenoidUIProcesses())
return d.killAllProcesses(findSelenoidUIProcesses())
}

func killAllProcesses(processes []os.Process) error {
func (d *DriversConfigurator) killAllProcesses(processes []*os.Process) error {
for _, p := range processes {
err := killFunc(p)
err := killFunc(p, d.Graceful, d.GracefulTimeout)
if err != nil {
return err
}
Expand All @@ -642,23 +666,23 @@ func (d *DriversConfigurator) Close() error {
return nil
}

func findSelenoidProcesses() []os.Process {
func findSelenoidProcesses() []*os.Process {
return findProcesses("selenoid")
}

func findSelenoidUIProcesses() []os.Process {
func findSelenoidUIProcesses() []*os.Process {
return findProcesses("selenoid-ui")
}

func findProcesses(regex string) []os.Process {
ret := []os.Process{}
func findProcesses(regex string) []*os.Process {
var ret []*os.Process
processes, _ := ps.Processes()
for _, process := range processes {
matched, _ := regexp.MatchString(regex, process.Executable())
if matched {
p, err := os.FindProcess(process.Pid())
if err == nil {
ret = append(ret, *p)
ret = append(ret, p)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion selenoid/drivers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"reflect"
"runtime"
"testing"
"time"
)

const (
Expand All @@ -32,7 +33,7 @@ var (

func init() {
mockDriverServer = httptest.NewServer(driversMux())
killFunc = func(_ os.Process) error { return nil }
killFunc = func(_ *os.Process, _ bool, _ time.Duration) error { return nil }
}

func driversMux() http.Handler {
Expand Down
25 changes: 14 additions & 11 deletions selenoid/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import (
"github.com/docker/docker/client"
"github.com/fatih/color"
"io"
"time"
)

type LifecycleConfig struct {
Quiet bool
Force bool
ConfigDir string
Browsers string
BrowserEnv string
Download bool
Args string
Env string
Version string
Port int
DisableLogs bool
Quiet bool
Force bool
Graceful bool
GracefulTimeout time.Duration
ConfigDir string
Browsers string
BrowserEnv string
Download bool
Args string
Env string
Version string
Port int
DisableLogs bool

// Docker specific
LastVersions int
Expand Down

0 comments on commit 103d482

Please sign in to comment.