Files
steampipe/db/stop.go

144 lines
3.6 KiB
Go

package db
import (
"fmt"
"log"
"os"
"syscall"
"time"
"github.com/turbot/steampipe/cmdconfig"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/utils"
)
// StopStatus :: pseudoEnum for service stop result
type StopStatus int
const (
// ServiceStopped :: StopStatus - service was stopped
ServiceStopped StopStatus = iota
// ServiceNotRunning :: StopStatus - service was not running
ServiceNotRunning
// ServiceStopFailed :: StopStatus - service could not be stopped
ServiceStopFailed
// ServiceStopTimedOut :: StopStatus - service stop attempt timed out
ServiceStopTimedOut
)
// StopDB :: search and stop the running instance. Does nothing if an instance was not found
func StopDB(force bool, invoker Invoker) (StopStatus, error) {
log.Println("[TRACE] StopDB", force)
if force {
// remove this file regardless of whether
// we could stop the service or not
// so that the next time it starts,
// all previous instances are nuked
defer os.Remove(runningInfoFilePath())
}
info, err := loadRunningInstanceInfo()
if err != nil {
return ServiceStopFailed, err
}
if force {
// check if we have a process from another install-dir
checkedPreviousInstances := make(chan bool, 1)
s := utils.StartSpinnerAfterDelay("Checking for running instances", constants.SpinnerShowTimeout, checkedPreviousInstances)
for {
previousProcess := findSteampipePostgresInstance()
if previousProcess != nil {
// we have an errant process
killProcessTree(previousProcess)
continue
}
checkedPreviousInstances <- true
break
}
utils.StopSpinner(s)
os.Remove(runningInfoFilePath())
return ServiceStopped, nil
}
if info == nil {
// we do not have a info file
return ServiceNotRunning, nil
}
doesPidExist, err := pidExists(info.Pid)
if err != nil {
return ServiceStopFailed, err
}
if !doesPidExist {
// nothing to do here
return ServiceNotRunning, os.Remove(runningInfoFilePath())
}
if info.Invoker != invoker {
return ServiceStopFailed, fmt.Errorf("You have a %s session open. The service will be stopped when the session ends.\nTo kill existing sessions, run %s", constants.Bold("steampipe query"), constants.Bold("steampipe service stop --force"))
}
process, err := os.FindProcess(info.Pid)
if err != nil {
return ServiceStopFailed, err
}
// we need to do this since the standard
// cmd.Process.Kill() sends a SIGKILL which
// makes PG terminate immediately without saving state.
// refer: https://www.postgresql.org/docs/12/server-shutdown.html
// refer: https://golang.org/src/os/exec_posix.go?h=kill#L65
killSignal := syscall.SIGTERM
if force {
killSignal = syscall.SIGINT
}
err = process.Signal(killSignal)
log.Println("[TRACE] signal sent", killSignal)
if err != nil {
return ServiceStopFailed, err
}
signalSentAt := time.Now()
spinnerShown := false
processKilledChannel := make(chan string, 1)
go func() {
for {
pEx, err := pidExists(info.Pid)
if err != nil {
utils.ShowError(err)
}
if err == nil && !pEx {
// no more process
processKilledChannel <- "killed"
break
}
if time.Since(signalSentAt) > constants.SpinnerShowTimeout && !spinnerShown {
if cmdconfig.Viper().GetBool(constants.ShowInteractiveOutputConfigKey) {
s := utils.ShowSpinner("Shutting down...")
defer utils.StopSpinner(s)
spinnerShown = true
}
}
time.Sleep(50 * time.Millisecond)
}
}()
timeoutAfter := time.After(10 * time.Second)
select {
case <-timeoutAfter:
return ServiceStopTimedOut, nil
case <-processKilledChannel:
os.Remove(runningInfoFilePath())
return ServiceStopped, nil
}
}