go-reaper: seeing error "waitid: no child processes" from run-to-completion processes while reaping

Similar to #2 but a little different.

After doing go reaper.Reap() in my program, I’m doing simple run-to-completion executions, such as:

cmd := exec.Command("some-cmd")
b, err := cmd.CombinedOutput()

this is returning error

waitid: no child processes

When I do not do go reaper.Reap(), things seem to work fine.

For example, more weirdly, this happens only while running:

{/bin/sh [-c npm install --only=production]}

but not while running:

{/bin/sh [-c go build .]}

I have a suspicion that these two commands are different somehow.

How to debug this?

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@jadolg Cool. Glad that worked for you. As re: your questions … so the reaper config takes a Pid argument which is just passed down to wait4 (value is similar to the pid argument to waitpid). That value can indicate whether to wait for all processes or anything matching the current process group or a specific process group or then a specific pid. Of course for multiple pids, you would need to invoke the reaper with different configs (lil’ kludgy but it works).

But that said, I think from a reaper perspective, making this fix somewhat generic might be a better option. Let me mull on that. For now, I will probably just document the above mentioned usage in the README. Thx

@jadolg , ok looks like that is setting the process group id/creating a new session.

So one solution here is to have the pid 1 do the reaping and have your code run inside a forked process ala:

import "os"
import "syscall"

func main() {
        // Note 1: Am using a environment variable REAPER to indicate that its a worker/parent.
	if _, hasReaper := os.LookupEnv("REAPER"); !hasReaper {
		go reaper.Reap()

		// Note 2: This is really if you want to distinguish the worker/parent ala in ps.
                //   Am not sure how and if it could affect your plugin's as its one extra
                //  argument added at the end. If it does, you can just use os.Args here instead.
                args := append(os.Args, "#worker")

		pwd, err := os.Getwd()
		if err != nil {
                        // Note 3: Better if you can handle it with a default directory ala "/tmp". 
			panic(err)
		}

		workerEnv := []string{fmt.Sprintf("REAPER=%d", os.Getpid())}

		var wstatus syscall.WaitStatus
		pattrs := &syscall.ProcAttr{
			Dir:   pwd,
			Env:   append(os.Environ(), workerEnv...),
			Sys:   &syscall.SysProcAttr{Setsid: true},
			Files: []uintptr{0, 1, 2},
		}
		workerPid, _ := syscall.ForkExec(args[0], args, pattrs)

		// fmt.Printf("worker-pid = %d\n", workerPid)
		syscall.Wait4(workerPid, &wstatus, 0, nil)
		return
	}

        // your code goes here
        your_plugin_code_main()
}

And there’s 3 different notes for you to adjust your code accordingly.

Aside: hmm, maybe something can do from an config option perspective. Let me mull on that a bit as we do require an entry point to invoke as well.