Browse Source

first commit

main
forest 3 months ago
commit
9eadd999a2
6 changed files with 528 additions and 0 deletions
  1. +173
    -0
      README.md
  2. +5
    -0
      go.mod
  3. +2
    -0
      go.sum
  4. +238
    -0
      main.go
  5. +43
    -0
      process_posix.go
  6. +67
    -0
      process_windows.go

+ 173
- 0
README.md View File

@ -0,0 +1,173 @@
# compiling
```
GOOS=windows GOARCH=amd64 go build -tags 'osusergo netgo' -ldflags='-extldflags=-static' -o proctest.exe
```
## output on linux
```
forest@thingpad:~/Desktop/proctest$ go run . 1
2021/10/08 13:49:38 /tmp/go-build1719441506/b001/exe/proctest
2021/10/08 13:49:38 child process is running!
2021/10/08 13:49:39 I am the Child!! i=0
2021/10/08 13:49:39
2021/10/08 13:49:39 golang sez: hasProcess: true, processPid: 139005, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 13:49:39 Child process (139005) is ALIVE!!!
2021/10/08 13:49:39
2021/10/08 13:49:39 I am the Child!! i=1
2021/10/08 13:49:40 I am the Child!! i=2
2021/10/08 13:49:40 parent waiting for child to actually die after calling terminateProcessOSIndependent...
2021/10/08 13:49:40 2021/10/08 13:49:40 child process recieved signal: interrupt
2021/10/08 13:49:40 2021/10/08 13:49:40 I AM NOT GOING DOWN WITHOUT A FIGHT! Sleeping for 10 seconds before exiting...
2021/10/08 13:49:40 I am the Child!! i=3
2021/10/08 13:49:41 .
2021/10/08 13:49:41 I am the Child!! i=4
2021/10/08 13:49:41 child process is exiting
2021/10/08 13:49:41 .
2021/10/08 13:49:41 child process 'test child' ended with exit code 0
2021/10/08 13:49:42 the child process is dead from the parents POV
2021/10/08 13:49:42
2021/10/08 13:49:42 golang sez: hasProcess: true, processPid: 139005, osProcessExists: true,
hasProcessState: true, processStatePid: 139005, processStateExited: true, processStateExitCode: 0
2021/10/08 13:49:42 Child process (139005) is no longer running.
2021/10/08 13:49:42
2021/10/08 13:49:42
2021/10/08 13:49:42 golang sez: hasProcess: true, processPid: 139005, osProcessExists: true,
hasProcessState: true, processStatePid: 139005, processStateExited: true, processStateExitCode: 0
2021/10/08 13:49:42 Child process (139005) is no longer running.
2021/10/08 13:49:42
2021/10/08 13:49:43
2021/10/08 13:49:43 golang sez: hasProcess: true, processPid: 139005, osProcessExists: true,
hasProcessState: true, processStatePid: 139005, processStateExited: true, processStateExitCode: 0
2021/10/08 13:49:43 Child process (139005) is no longer running.
2021/10/08 13:49:43
forest@thingpad:~/Desktop/proctest$ go run . 10
2021/10/08 13:49:50 /tmp/go-build278945089/b001/exe/proctest
2021/10/08 13:49:50 child process is running!
2021/10/08 13:49:50 I am the Child!! i=0
2021/10/08 13:49:51
2021/10/08 13:49:51 golang sez: hasProcess: true, processPid: 139116, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 13:49:51 Child process (139116) is ALIVE!!!
2021/10/08 13:49:51
2021/10/08 13:49:51 I am the Child!! i=1
2021/10/08 13:49:51 I am the Child!! i=2
2021/10/08 13:49:52 parent waiting for child to actually die after calling terminateProcessOSIndependent...
2021/10/08 13:49:52 2021/10/08 13:49:52 child process recieved signal: interrupt
2021/10/08 13:49:52 2021/10/08 13:49:52 I AM NOT GOING DOWN WITHOUT A FIGHT! Sleeping for 10 seconds before exiting...
2021/10/08 13:49:52 I am the Child!! i=3
2021/10/08 13:49:52 .
2021/10/08 13:49:52 I am the Child!! i=4
2021/10/08 13:49:53 .
2021/10/08 13:49:53 I am the Child!! i=5
2021/10/08 13:49:53 .
2021/10/08 13:49:53 I am the Child!! i=6
2021/10/08 13:49:54 .
2021/10/08 13:49:54 I am the Child!! i=7
2021/10/08 13:49:54 .
2021/10/08 13:49:54
2021/10/08 13:49:54 golang sez: hasProcess: true, processPid: 139116, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 13:49:54 Child process (139116) is ALIVE!!!
2021/10/08 13:49:54
2021/10/08 13:49:54 parent process is fed up, now calling killProcessOSIndependent
2021/10/08 13:49:54
2021/10/08 13:49:54 golang sez: hasProcess: true, processPid: 139116, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 13:49:54 Child process (139116) is ALIVE!!!
2021/10/08 13:49:54
2021/10/08 13:49:54 command.Wait() returned 'signal: killed' for child process 'test child'
2021/10/08 13:49:54 child process 'test child' ended with exit code -1
2021/10/08 13:49:55
2021/10/08 13:49:55 golang sez: hasProcess: true, processPid: 139116, osProcessExists: true,
hasProcessState: true, processStatePid: 139116, processStateExited: false, processStateExitCode: -1
2021/10/08 13:49:55 Child process (139116) is no longer running.
2021/10/08 13:49:55
```
## output on windows
```
PS C:\Program Files (x86)\greenhouse-desktop\background-service> ./test4.exe 1
2021/10/08 11:10:11 C:\Program Files (x86)\greenhouse-desktop\background-service\test4.exe
2021/10/08 11:10:11 child process is running!
2021/10/08 11:10:12 I am the Child!! i=0
2021/10/08 11:10:12
2021/10/08 11:10:12 golang sez: hasProcess: true, processPid: 3940, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 11:10:12 Child process (3940) is ALIVE!!!
2021/10/08 11:10:12
2021/10/08 11:10:12 I am the Child!! i=1
2021/10/08 11:10:13 I am the Child!! i=2
2021/10/08 11:10:13 parent waiting for child to actually die after calling terminateProcessOSIndependent...
2021/10/08 11:10:13 child process recieved signal: interrupt
2021/10/08 11:10:13 I AM NOT GOING DOWN WITHOUT A FIGHT! Sleeping for 1 seconds before exiting...
2021/10/08 11:10:13 I am the Child!! i=3
2021/10/08 11:10:14 .
2021/10/08 11:10:14 I am the Child!! i=4
2021/10/08 11:10:14 child process is exiting
2021/10/08 11:10:14 child process 'test child' ended with exit code 0
2021/10/08 11:10:14 the child process is dead from the parents POV
2021/10/08 11:10:14
2021/10/08 11:10:14 golang sez: hasProcess: true, processPid: 3940, osProcessExists: true,
hasProcessState: true, processStatePid: 3940, processStateExited: true, processStateExitCode: 0
2021/10/08 11:10:14 Child process (3940) is no longer running.
2021/10/08 11:10:14
2021/10/08 11:10:14
2021/10/08 11:10:14 golang sez: hasProcess: true, processPid: 3940, osProcessExists: true,
hasProcessState: true, processStatePid: 3940, processStateExited: true, processStateExitCode: 0
2021/10/08 11:10:14 Child process (3940) is no longer running.
2021/10/08 11:10:14
2021/10/08 11:10:15
2021/10/08 11:10:15 golang sez: hasProcess: true, processPid: 3940, osProcessExists: true,
hasProcessState: true, processStatePid: 3940, processStateExited: true, processStateExitCode: 0
2021/10/08 11:10:15 Child process (3940) is no longer running.
2021/10/08 11:10:15
PS C:\Program Files (x86)\greenhouse-desktop\background-service> ./test4.exe 10
2021/10/08 11:10:30 C:\Program Files (x86)\greenhouse-desktop\background-service\test4.exe
2021/10/08 11:10:30 child process is running!
2021/10/08 11:10:31 I am the Child!! i=0
2021/10/08 11:10:31
2021/10/08 11:10:31 golang sez: hasProcess: true, processPid: 8404, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 11:10:31 Child process (8404) is ALIVE!!!
2021/10/08 11:10:31
2021/10/08 11:10:31 I am the Child!! i=1
2021/10/08 11:10:32 I am the Child!! i=2
2021/10/08 11:10:32 parent waiting for child to actually die after calling terminateProcessOSIndependent...
2021/10/08 11:10:32 child process recieved signal: interrupt
2021/10/08 11:10:32 I AM NOT GOING DOWN WITHOUT A FIGHT! Sleeping for 10 seconds before exiting...
2021/10/08 11:10:32 I am the Child!! i=3
2021/10/08 11:10:33 .
2021/10/08 11:10:33 I am the Child!! i=4
2021/10/08 11:10:33 .
2021/10/08 11:10:33 I am the Child!! i=5
2021/10/08 11:10:34 .
2021/10/08 11:10:34 I am the Child!! i=6
2021/10/08 11:10:34 .
2021/10/08 11:10:34 I am the Child!! i=7
2021/10/08 11:10:35 .
2021/10/08 11:10:35
2021/10/08 11:10:35 golang sez: hasProcess: true, processPid: 8404, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 11:10:35 Child process (8404) is ALIVE!!!
2021/10/08 11:10:35
2021/10/08 11:10:35 I am the Child!! i=8
2021/10/08 11:10:35 parent process is fed up, now calling killProcessOSIndependent
2021/10/08 11:10:35
2021/10/08 11:10:35 golang sez: hasProcess: true, processPid: 8404, osProcessExists: true,
hasProcessState: false, processStatePid: nil, processStateExited: nil, processStateExitCode: nil
2021/10/08 11:10:35 Child process (8404) is ALIVE!!!
2021/10/08 11:10:35
2021/10/08 11:10:35 command.Wait() returned 'exit status 1' for child process 'test child'
2021/10/08 11:10:35 child process 'test child' ended with exit code 1
2021/10/08 11:10:36
2021/10/08 11:10:36 golang sez: hasProcess: true, processPid: 8404, osProcessExists: true,
hasProcessState: true, processStatePid: 8404, processStateExited: true, processStateExitCode: 1
2021/10/08 11:10:36 Child process (8404) is no longer running.
2021/10/08 11:10:36
```

+ 5
- 0
go.mod View File

@ -0,0 +1,5 @@
module git.sequentialread.com/forest/proctest
go 1.16
require golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect

+ 2
- 0
go.sum View File

@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

+ 238
- 0
main.go View File

@ -0,0 +1,238 @@
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
}

+ 43
- 0
process_posix.go View File

@ -0,0 +1,43 @@
// +build !windows
package main
import (
"os"
"os/signal"
"golang.org/x/sys/unix"
)
var processAttributesOSIndependent = &unix.SysProcAttr{Setpgid: true}
func terminateProcessOSIndependent(pid int, signal os.Signal) error {
if pid == 0 || pid == -1 {
return nil
}
pgid, err := unix.Getpgid(pid)
if err != nil {
return err
}
if pgid == pid {
pid = -1 * pid
}
target, err := os.FindProcess(pid)
if err != nil {
return err
}
return target.Signal(signal)
}
// kills the process with pid pid, as well as its children.
func killProcessOSIndependent(process *os.Process) error {
return unix.Kill(-1*process.Pid, unix.SIGKILL)
}
func getSignalChannelOSIndependent() <-chan os.Signal {
sc := make(chan os.Signal, 10)
signal.Notify(sc, unix.SIGTERM, unix.SIGINT, unix.SIGHUP)
return sc
}

+ 67
- 0
process_windows.go View File

@ -0,0 +1,67 @@
// +build windows
package main
import (
"os"
"os/signal"
"syscall"
"golang.org/x/sys/windows"
)
var processAttributesOSIndependent = &windows.SysProcAttr{
CreationFlags: windows.CREATE_UNICODE_ENVIRONMENT | windows.CREATE_NEW_PROCESS_GROUP,
}
func terminateProcessOSIndependent(pid int, _ os.Signal) error {
if pid == 0 || pid == -1 {
return nil
}
dll, err := windows.LoadDLL("kernel32.dll")
if err != nil {
return err
}
defer dll.Release()
f, err := dll.FindProc("AttachConsole")
if err != nil {
return err
}
r1, _, err := f.Call(uintptr(pid))
if r1 == 0 && err != syscall.ERROR_ACCESS_DENIED {
return err
}
f, err = dll.FindProc("SetConsoleCtrlHandler")
if err != nil {
return err
}
r1, _, err = f.Call(0, 1)
if r1 == 0 {
return err
}
f, err = dll.FindProc("GenerateConsoleCtrlEvent")
if err != nil {
return err
}
r1, _, err = f.Call(windows.CTRL_BREAK_EVENT, uintptr(pid))
if r1 == 0 {
return err
}
r1, _, err = f.Call(windows.CTRL_C_EVENT, uintptr(pid))
if r1 == 0 {
return err
}
return nil
}
func killProcessOSIndependent(process *os.Process) error {
return process.Kill()
}
func getSignalChannelOSIndependent() <-chan os.Signal {
sc := make(chan os.Signal, 10)
signal.Notify(sc, os.Interrupt)
return sc
}

Loading…
Cancel
Save