Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renaming master/slave to parent/child #54

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -68,7 +68,7 @@ func prog(state overseer.State) {
* The child process is provided with these files which is converted into a `Listener/s` for the `Program` to consume.
* All child process pipes are connected back to the main process.
* All signals received on the main process are forwarded through to the child process.
* `Fetcher` runs in a goroutine and checks for updates at preconfigured interval. When `Fetcher` returns a valid binary stream (`io.Reader`), the master process saves it to a temporary location, verifies it, replaces the current binary and initiates a graceful restart.
* `Fetcher` runs in a goroutine and checks for updates at pre-configured interval. When `Fetcher` returns a valid binary stream (`io.Reader`), the parent process saves it to a temporary location, verifies it, replaces the current binary and initiates a graceful restart.
* The `fetcher.HTTP` accepts a `URL`, it polls this URL with HEAD requests and until it detects a change. On change, we `GET` the `URL` and stream it back out to `overseer`. See also `fetcher.S3`.
* Once a binary is received, it is run with a simple echo token to confirm it is a `overseer` binary.
* Except for scheduled restarts, the active child process exiting will cause the main process to exit with the same code. So, **`overseer` is not a process manager**.
Expand Down Expand Up @@ -150,7 +150,7 @@ func main() {

### Known issues

* The master process's `overseer.Config` cannot be changed via an upgrade, the master process must be restarted.
* The parent process's `overseer.Config` cannot be changed via an upgrade, the parent process must be restarted.
* Therefore, `Addresses` can only be changed by restarting the main process.
* Currently shells out to `mv` for moving files because `mv` handles cross-partition moves unlike `os.Rename`.
* Package `init()` functions will run twice on start, once in the main process and once in the child process.
Expand Down
27 changes: 8 additions & 19 deletions example/go.sum
@@ -1,21 +1,10 @@
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/aws/aws-sdk-go v1.29.34 h1:yrzwfDaZFe9oT4AmQeNNunSQA7c0m2chz0B43+bJ1ok=
github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v2.20.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=
github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/gunit v1.1.3 h1:32x+htJCu3aMswhPw3teoJ+PnWPONqdNgaGs6Qt8ZaU=
github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
14 changes: 7 additions & 7 deletions overseer.go
Expand Up @@ -14,8 +14,8 @@ import (
)

const (
envSlaveID = "OVERSEER_SLAVE_ID"
envIsSlave = "OVERSEER_IS_SLAVE"
envChildID = "OVERSEER_CHILD_ID"
envIsChild = "OVERSEER_IS_CHILD"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this change is the only one that will cause issues - this variable is used between both processes, so if a user updates their app with the latest overseer, the newly created child process will have the old variables set (by the old parent process), breaking the app

i think the least risky thing to do is just keep these 2 strings... the alternative is to come up with a transition version, which supports both old/new variable names

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense... But if you make that argument you could never change it as there could always be someone with an old version.
I assume you don't want to do a v2 release just for this?
I'd change it with a transition approach if you decide that to be the right way.

envNumFDs = "OVERSEER_NUM_FDS"
envBinID = "OVERSEER_BIN_ID"
envBinPath = "OVERSEER_BIN_PATH"
Expand Down Expand Up @@ -136,7 +136,7 @@ func SanityCheck() {
}
}

//abstraction over master/slave
//abstraction over parent/child
var currentProcess interface {
triggerRestart()
run() error
Expand All @@ -153,11 +153,11 @@ func runErr(c *Config) error {
if sanityCheck() {
return nil
}
//run either in master or slave mode
if os.Getenv(envIsSlave) == "1" {
currentProcess = &slave{Config: c}
//run either in parent or child mode
if os.Getenv(envIsChild) == "1" {
currentProcess = &child{Config: c}
} else {
currentProcess = &master{Config: c}
currentProcess = &parent{Config: c}
}
return currentProcess.run()
}
Expand Down
38 changes: 19 additions & 19 deletions proc_slave.go → proc_child.go
Expand Up @@ -29,7 +29,7 @@ type State struct {
StartedAt time.Time
//Listener is the first net.Listener in Listeners
Listener net.Listener
//Listeners are the set of acquired sockets by the master
//Listeners are the set of acquired sockets by the parent
//process. These are all passed into this program in the
//same order they are specified in Config.Addresses.
Listeners []net.Listener
Expand All @@ -44,19 +44,19 @@ type State struct {
BinPath string
}

//a overseer slave process
//a overseer child process

type slave struct {
type child struct {
*Config
id string
listeners []*overseerListener
masterPid int
masterProc *os.Process
parentPid int
parentProc *os.Process
state State
}

func (sp *slave) run() error {
sp.id = os.Getenv(envSlaveID)
func (sp *child) run() error {
sp.id = os.Getenv(envChildID)
sp.debugf("run")
sp.state.Enabled = true
sp.state.ID = os.Getenv(envBinID)
Expand All @@ -78,7 +78,7 @@ func (sp *slave) run() error {
return nil
}

func (sp *slave) initFileDescriptors() error {
func (sp *child) initFileDescriptors() error {
//inspect file descriptors
numFDs, err := strconv.Atoi(os.Getenv(envNumFDs))
if err != nil {
Expand All @@ -102,26 +102,26 @@ func (sp *slave) initFileDescriptors() error {
return nil
}

func (sp *slave) watchSignal() {
func (sp *child) watchSignal() {
signals := make(chan os.Signal)
signal.Notify(signals, sp.Config.RestartSignal)
go func() {
<-signals
signal.Stop(signals)
sp.debugf("graceful shutdown requested")
//master wants to restart,
//parent wants to restart,
close(sp.state.GracefulShutdown)
//release any sockets and notify master
//release any sockets and notify parent
if len(sp.listeners) > 0 {
//perform graceful shutdown
for _, l := range sp.listeners {
l.release(sp.Config.TerminateTimeout)
}
//signal release of held sockets, allows master to start
//signal release of held sockets, allows parent to start
//a new process before this child has actually exited.
//early restarts not supported with restarts disabled.
if !sp.NoRestart {
sp.masterProc.Signal(SIGUSR1)
sp.parentProc.Signal(SIGUSR1)
}
//listeners should be waiting on connections to close...
}
Expand All @@ -134,20 +134,20 @@ func (sp *slave) watchSignal() {
}()
}

func (sp *slave) triggerRestart() {
if err := sp.masterProc.Signal(sp.Config.RestartSignal); err != nil {
func (sp *child) triggerRestart() {
if err := sp.parentProc.Signal(sp.Config.RestartSignal); err != nil {
os.Exit(1)
}
}

func (sp *slave) debugf(f string, args ...interface{}) {
func (sp *child) debugf(f string, args ...interface{}) {
if sp.Config.Debug {
log.Printf("[overseer slave#"+sp.id+"] "+f, args...)
log.Printf("[overseer child#"+sp.id+"] "+f, args...)
}
}

func (sp *slave) warnf(f string, args ...interface{}) {
func (sp *child) warnf(f string, args ...interface{}) {
if sp.Config.Debug || !sp.Config.NoWarn {
log.Printf("[overseer slave#"+sp.id+"] "+f, args...)
log.Printf("[overseer child#"+sp.id+"] "+f, args...)
}
}
30 changes: 30 additions & 0 deletions proc_child_others.go
@@ -0,0 +1,30 @@
// +build !windows

package overseer

import (
"fmt"
"os"
"syscall"
"time"
)

func (sp *child) watchParent() error {
sp.parentPid = os.Getppid()
proc, err := os.FindProcess(sp.parentPid)
if err != nil {
return fmt.Errorf("parent process: %s", err)
}
sp.parentProc = proc
go func() {
//send signal 0 to parent process forever
for {
//should not error as long as the process is alive
if err := sp.parentProc.Signal(syscall.Signal(0)); err != nil {
os.Exit(1)
}
time.Sleep(2 * time.Second)
}
}()
return nil
}
17 changes: 9 additions & 8 deletions proc_slave_windows.go → proc_child_windows.go
Expand Up @@ -5,9 +5,10 @@ package overseer
import (
"context"
"fmt"
"github.com/StackExchange/wmi"
"os"
"time"

"github.com/StackExchange/wmi"
)

var (
Expand Down Expand Up @@ -53,18 +54,18 @@ type Win32_Process struct {
WorkingSetSize uint64
}

func (sp *slave) watchParent() error {
sp.masterPid = os.Getppid()
proc, err := os.FindProcess(sp.masterPid)
func (cp *child) watchParent() error {
cp.parentPid = os.Getppid()
proc, err := os.FindProcess(cp.parentPid)
if err != nil {
return fmt.Errorf("master process: %s", err)
return fmt.Errorf("parent process: %s", err)
}
sp.masterProc = proc
cp.parentProc = proc
go func() {
//send signal 0 to master process forever
//send signal 0 to parent process forever
for {
//should not error as long as the process is alive
if _, err := GetWin32Proc(int32(sp.masterPid)); err != nil {
if _, err := GetWin32Proc(int32(cp.parentPid)); err != nil {
os.Exit(1)
}
time.Sleep(2 * time.Second)
Expand Down