// Copyright (c) 2020 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License-AGPL.txt in the project root for license information. package cmd import ( "os" "os/exec" "os/signal" "strings" "sync" "syscall" "github.com/gitpod-io/gitpod/common-go/log" "github.com/spf13/cobra" "golang.org/x/sys/unix" ) var initCmd = &cobra.Command{ Use: "init", Short: "init the supervisor", Run: func(cmd *cobra.Command, args []string) { log.Init(ServiceName, Version, true, false) // Because we're reaping with PID -1, we'll catch the child process for // which we've missed the notification anyways. sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGCHLD) // The reaper can be turned into a terminating reaper by writing true to this channel. // When in terminating mode, the reaper will send SIGTERM to each child that gets reparented // to us and is still running. We use this mechanism to send SIGTERM to a shell child processes // that get reparented once their parent shell terminates during shutdown. var terminating bool reaper := func() { for range sigs { for { // wait on the process, hence remove it from the process table pid, err := unix.Wait4(-1, nil, 0, nil) // if we've been interrupted, try again until we're done for err == syscall.EINTR { pid, err = unix.Wait4(-1, nil, 0, nil) } // The calling process does not have any unwaited-for children. Let's wait for a SIGCHLD notification. if err == unix.ECHILD { break } if err != nil { log.WithField("pid", pid).WithError(err).Debug("cannot call waitpid() for re-parented child") } if !terminating { continue } proc, err := os.FindProcess(pid) if err != nil { log.WithField("pid", pid).WithError(err).Debug("cannot find re-parented process") continue } err = proc.Signal(syscall.SIGTERM) if err != nil { if !strings.Contains(err.Error(), "os: process already finished") { log.WithField("pid", pid).WithError(err).Debug("cannot send SIGTERM to re-parented process") } continue } log.WithField("pid", pid).Debug("SIGTERM'ed reparented child process") } } } go reaper() supervisorPath, err := os.Executable() if err != nil { supervisorPath = "/.supervisor/supervisor" } runCommand := exec.Command(supervisorPath, "run") runCommand.Args[0] = "supervisor" runCommand.Stdin = os.Stdin runCommand.Stdout = os.Stdout runCommand.Stderr = os.Stderr runCommand.Env = os.Environ() err = runCommand.Start() if err != nil { log.WithError(err).Error("supervisor run start error") return } wg := sync.WaitGroup{} wg.Add(1) go func() { err := runCommand.Wait() if err != nil && !(strings.Contains(err.Error(), "signal: interrupt") || strings.Contains(err.Error(), "no child processes")) { log.WithError(err).Error("supervisor run error") return } wg.Done() }() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) s := <-sigChan terminating = true _ = runCommand.Process.Signal(s) wg.Wait() }, } func init() { rootCmd.AddCommand(initCmd) }