An example / demo project for managing a child process in the same way across Windows/Mac/Linux
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
5.3 KiB

3 years ago
package main
import (
"bufio"
"fmt"
"log"
"os"
"os/exec"
"strconv"
"time"
)
var deadPids []int
func main() {
deadPids = make([]int, 5)
if len(os.Args) > 1 && os.Args[1] == "child" {
go func() {
i := 0
for {
time.Sleep(time.Millisecond * 500)
fmt.Printf("I am the Child!! i=%d\n", i)
i++
}
}()
sigs := getSignalChannelOSIndependent()
done := make(chan bool, 1)
go func() {
sig := <-sigs
fmt.Printf("child process recieved signal: %s\n", sig)
waitSeconds := 10
if len(os.Args) > 2 {
waitSeconds64, err := strconv.ParseInt(os.Args[2], 10, 64)
if err != nil {
fmt.Printf("cant parse %s (fight_seconds) as integer, defaulting to 10 seconds\n", os.Args[2])
} else {
waitSeconds = int(waitSeconds64)
}
}
fmt.Printf("I AM NOT GOING DOWN WITHOUT A FIGHT! Sleeping for %d seconds before exiting...\n", waitSeconds)
time.Sleep(time.Second * time.Duration(waitSeconds))
done <- true
}()
fmt.Println("child process is running!")
<-done
fmt.Println("child process is exiting")
} else {
executableLocation, err := os.Executable()
if err != nil {
panic(err)
}
log.Println(executableLocation)
args := []string{"child"}
if len(os.Args) > 1 {
args = append(args, os.Args[1])
}
command := exec.Command(executableLocation, args...)
command.SysProcAttr = processAttributesOSIndependent
stdoutReader, err := command.StdoutPipe()
if err != nil {
panic(err)
}
stdoutScanner := bufio.NewScanner(stdoutReader)
stdoutScanner.Split(bufio.ScanLines)
go func() {
for stdoutScanner.Scan() {
log.Println(stdoutScanner.Text())
}
}()
stderrReader, err := command.StderrPipe()
if err != nil {
panic(err)
}
stderrScanner := bufio.NewScanner(stderrReader)
stderrScanner.Split(bufio.ScanLines)
go func() {
for stderrScanner.Scan() {
log.Println(stderrScanner.Text())
}
}()
err = command.Start()
if err != nil {
panic(err)
}
go (func() {
err := command.Wait()
if err != nil {
log.Printf(
"command.Wait() returned '%s' for child process '%s %s'\n",
err, "test", "child",
)
}
log.Printf(
"child process '%s %s' ended with exit code %d",
"test", "child", command.ProcessState.ExitCode(),
)
deadPids = append(deadPids[1:], getPidFromCommand(command))
})()
time.Sleep(time.Second)
log.Println()
debugStatus(command)
printStatus(command)
log.Println()
time.Sleep(time.Second)
err = terminateProcessOSIndependent(getPidFromCommand(command), os.Interrupt)
if err != nil {
panic(err)
}
log.Printf("parent waiting for child to actually die after calling terminateProcessOSIndependent...\n")
for i := 0; i < 5; i++ {
time.Sleep(time.Millisecond * 500)
if isProcessDead(command) {
log.Printf("the child process is dead from the parents POV\n")
break
}
log.Printf(".\n")
}
log.Println()
debugStatus(command)
printStatus(command)
log.Println()
if !isProcessDead(command) {
log.Printf("parent process is fed up, now calling killProcessOSIndependent\n")
err = killProcessOSIndependent(command.Process)
if err != nil {
panic(err)
}
}
log.Println()
debugStatus(command)
printStatus(command)
log.Println()
time.Sleep(time.Second)
log.Println()
debugStatus(command)
printStatus(command)
log.Println()
}
}
func printStatus(command *exec.Cmd) {
currentPid := getPidFromCommand(command)
if currentPid > 1 {
if isProcessDead(command) {
log.Printf("Child process (%d) is no longer running.\n", currentPid)
} else {
log.Printf("Child process (%d) is ALIVE!!!\n", currentPid)
}
} else {
log.Printf("Child process (%d) has a null process id, it must not be started yet.\n", currentPid)
}
}
func isProcessDead(command *exec.Cmd) bool {
if command == nil || command.Process == nil || command.Process.Pid == 0 || command.Process.Pid == -1 {
return true
}
// for _, deadPid := range deadPids {
// if deadPid == pid {
// return true
// }
// }
return command.ProcessState != nil && (command.ProcessState.Exited() || command.ProcessState.ExitCode() == -1)
}
func debugStatus(command *exec.Cmd) {
hasProcess := command.Process != nil
processPid := "nil"
hasProcessState := command.ProcessState != nil
processStateExited := "nil"
processStateExitCode := "nil"
processStatePid := "nil"
osProcessExists := false
if hasProcess {
processPid = fmt.Sprintf("%d", command.Process.Pid)
}
if hasProcessState {
processStateExited = fmt.Sprintf("%t", command.ProcessState.Exited())
processStateExitCode = fmt.Sprintf("%d", command.ProcessState.ExitCode())
processStatePid = fmt.Sprintf("%d", command.ProcessState.Pid())
}
currentPid := getPidFromCommand(command)
if currentPid > 1 {
osProcess, osProcessErr := os.FindProcess(currentPid)
osProcessExists = osProcessErr == nil && osProcess.Pid == currentPid
}
log.Printf(`golang sez: hasProcess: %t, processPid: %s, osProcessExists: %t,
hasProcessState: %t, processStatePid: %s, processStateExited: %s, processStateExitCode: %s`,
hasProcess, processPid, osProcessExists,
hasProcessState, processStatePid, processStateExited, processStateExitCode)
}
func getPidFromCommand(command *exec.Cmd) int {
pid := -1
if command.Process != nil {
pid = command.Process.Pid
}
if pid < 1 && command.ProcessState != nil {
pid = command.ProcessState.Pid()
}
return pid
}