Issues with os.FindProcess on Windows

1,691 views
Skip to first unread message

mahe...@thoughtworks.com

unread,
Feb 10, 2016, 1:31:14 AM2/10/16
to golang-nuts
Hi All,

One of the requirements we have is to periodically check if a process with a given pid is still running. We wrote a function IsProcessRunning, which works fine on Mac and Linux. But on Windows, this function returns true even if there is no process with the given pid.

We dug a little deeper to figure out what's going on. We wrote the below function, based on this func in golang.

func main() {
    pid
:= 1567  // This is the process id of one of the chrome tabs on my machine. I got it from Task Manager.
   
const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
    _
, err := syscall.OpenProcess(da, false, uint32(pid))
    fmt
.Printf("Error: %v\n", err)
}


On running this program, we get a nil error, which is expected. Now, if we increment the pid by 1, we are still getting a nil error! There is no process with pid 1568 on my machine (verified through task manager as well as through tasklist). We are getting a nil error for around 3 or 4 increments (by 1) of pid. Beyond that, we get a non nil error, which is the correct behavior.

So in conclusion what we found is that syscall.OpenProcess is not returning an error for a range of pids starting from the actual pid. The length of this range is generally 3 or 4 or 5, depending on the process. We didn't find any particular pattern in the range length.

Is this the expected behavior or it's a bug? If yes, does anyone have a workaround?

Environment Details:

OS: Windows 7 64 bit
Golang version: go1.5.2 windows/amd64




andrey mirtchovski

unread,
Feb 10, 2016, 1:40:12 AM2/10/16
to mahe...@thoughtworks.com, golang-nuts
what i do is check if the process has a valid executable (which i know
the full path of). this may help (untested). i think i encountered
this code in gosigar (https://github.com/cloudfoundry/gosigar)
although it's been too long to remember.

func Check(pid int) string, error {
snapshot, err :=
syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return "", err
}
defer syscall.CloseHandle(snapshot)

var procEntry syscall.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
return "", err
}

for {
if procEntry.ProcessID == uint32(pid) {
return syscall.UTF16ToString(procEntry.ExeFile[:]), nil
}
err = syscall.Process32Next(snapshot, &procEntry)
if err != nil {
return "", err
}
}
return "", fmt.Errorf("pid not found")
}

yannick....@gmail.com

unread,
Feb 12, 2016, 12:40:15 PM2/12/16
to golang-nuts

Mahendra Kariya

unread,
Feb 15, 2016, 1:14:58 AM2/15/16
to yannick....@gmail.com, golang-nuts
Thanks a lot Yannick! This really helped.

For others who are facing the same issue, we ended up using os.ProcessState to check if the process is still running. The exact code can be found here.

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/p-H5ODtTXIw/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

mahe...@thoughtworks.com

unread,
Apr 1, 2016, 5:52:58 AM4/1/16
to golang-nuts
I finally got some time to write a blog post about this. It's still an early draft. But it would be great if some of you could review it and provide your feedback.

Dave Cheney

unread,
Apr 1, 2016, 6:10:36 AM4/1/16
to golang-nuts
What about monitoring the file descriptors passed down from the parent to the child. If you use an os.Pipe and pass that down in the the SysExtra (note, guess, I don't recall its name) argument passed to os.Exec, or whatever the parent does then you have a file descriptor shared by the parent and the child. Spin up a goroutine in the child to watch for the fd closing, if it does, shut down.

mahe...@thoughtworks.com

unread,
Apr 1, 2016, 6:39:23 AM4/1/16
to golang-nuts
What you are saying seems to be a better way to do this. But the parent process will be either in Java or C# (as of now. This list of languages could increase.)
I am not sure how easy it is to pass fds from parent to child in these languages. 

For instance, we are using ProcessBuilder in Java to launch the golang child process. And it seems there is no API to pass fds. :(

Dave Cheney

unread,
Apr 1, 2016, 6:40:34 AM4/1/16
to golang-nuts
Does your child process use stdin ? If it's free then that should be supported by anything that supports fork(2)

Mahendra Kariya

unread,
Apr 1, 2016, 7:00:46 AM4/1/16
to Dave Cheney, golang-nuts
Child process is using stdin. It's not free.



--

brainman

unread,
Apr 1, 2016, 8:34:16 AM4/1/16
to golang-nuts
I don't know if you know, but you can use Job Objects to monitor / manage group of processes on Windows. See https://github.com/golang/benchmarks/blob/master/driver/driver_windows.go#L65 for an example.

Alex

yannick....@gmail.com

unread,
Apr 1, 2016, 1:03:20 PM4/1/16
to golang-nuts
The following line seems to wait infinitely on my machine (Windows 8.1 x64). Im not quite sure if this is intended for a check like "is this process still running", because you wont get an answer if it is.

processState, err := process.Wait()

Also you dont handle all errors (no feedback for the user), like access denied for func FindProcess(pid int) (*Process, error) or the following:

func (p *Process) Wait() (*ProcessState, error)

Wait waits for the Process to exit, and then returns a ProcessState describing its status and an error, if any. Wait releases any resources associated with the Process. On most operating systems, the Process must be a child of the current process or an error will be returned.

Maybe you could use a socket + keep-alive pings. Simple to implement and plattform-/language-independent. Another way would be plattform-specific syscalls like CreateToolhelp32Snapshot on Windows or reading /proc on Linux systems.

Mahendra Kariya

unread,
Apr 2, 2016, 8:55:12 AM4/2/16
to brainman, golang-nuts

On Fri, Apr 1, 2016 at 6:04 PM, brainman <alex.b...@gmail.com> wrote:
I don't know if you know, but you can use Job Objects to monitor / manage group of processes on Windows. See https://github.com/golang/benchmarks/blob/master/driver/driver_windows.go#L65 for an example


I wasn't aware of this. Thanks a lot for pointing it out. I will take a look at it.

Mahendra Kariya

unread,
Apr 2, 2016, 9:14:44 AM4/2/16
to Yannick Häßler, golang-nuts
Hi Yannick,

My responses are inline below.

The following line seems to wait infinitely on my machine (Windows 8.1 x64). Im not quite sure if this is intended for a check like "is this process still running", because you wont get an answer if it is.
You are correct. It is certainly not intended to check if a process is still running. Calling wait() on a process is a blocking call and will only return (at least on Windows) when the process is signalled [1]. But this is what we want for our use case (more on this below).
Also you dont handle all errors (no feedback for the user), like access denied for func FindProcess(pid int) (*Process, error) or the following:
In our particular use case, FindProcess is never going to return access denied. Our application can only be launched by a particular group of users who have adequate access.

But I do agree with you that a programmer should ideally handle all these errors.
On most operating systems, the Process must be a child
of the current process or an error will be returned.

I had read this part of documentation before implementing it. But I thought of giving it a try and it seems to be working. I don't know how. And this is one of the reasons why I want the community to review the solution, although it is working and our users are happy.

Maybe you could use a socket + keep-alive pings. Simple to implement and plattform-/language-independent. 

I think I should elaborate more on the use case that we have. We are developing an automation testing framework called Gauge which is written in Golang. We have IDE plugins for IntelliJ, Visual Studio and Eclipse. When a user opens a Gauge project in any of these IDEs, Gauge process is launched in the background. So the golang process is a child process, and the parent is the IDE plugin (written in Java / C#). When user closes IDE gracefully, the child process is also getting killed. No issues here. But when user SIGKILLs the IDE (or it crashes for some reason), the child process still remains.

We have also considered asking the kernel to deliver SIGHUP signal when parent dies. But this doesn't work on Windows. :(


Mahendra Kariya

unread,
Apr 2, 2016, 10:06:31 AM4/2/16
to golang-nuts, Yannick Häßler, brainman, Dave Cheney
I looked at the job objects approach pointed out by Alex. It sounds interesting.

So we have got multiple suggestions in this thread and all of them seems to be correct (of course based on the situation).
  • Dave's approach is to monitor file descriptors
  • Alex suggested job objects
  • Yannick suggested socket + keep alive pings
  • When I had met Hana (@belbaoverhill) at Gophercon, I had discussed this problem with her and she seemed fine with our approach
So now the question is which of these is the best solution. I am leaning towards Yannick's, but I'd love to hear your opinions on this.


Chris Hines

unread,
Apr 2, 2016, 4:47:14 PM4/2/16
to golang-nuts, yannick....@gmail.com, alex.b...@gmail.com, da...@cheney.net
I had a similar problem for a cluster job manager that I worked on. A portion of the code needed to launch a child process and wait for it to complete. The child process could be anything, so it might launch additional child processes that our code would be unaware of. To cancel a running job we needed a way to kill the entire process tree only knowing the top process.

The Windows implementation used Job Objects. It took advantage of a particular feature of job objects:

To close a job object handle, use the CloseHandle function. The job is destroyed when its last handle has been closed and all associated processes have exited. However, if the job has the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag specified, closing the last job object handle terminates all associated processes and then destroys the job object itself.

Windows will automatically close all handles a process owns when the process dies (for any reason). If your parent process (the IDE) holds the only copy of the job object handle then using the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag will let Windows do the cleanup for you when the IDE exits.


Hope that helps,
Chris

brainman

unread,
Apr 2, 2016, 6:02:13 PM4/2/16
to golang-nuts, alex.b...@gmail.com
Here https://github.com/alexbrainman/ps I have some code to get you started.

Alex

Mahendra Kariya

unread,
Apr 3, 2016, 11:56:11 AM4/3/16
to brainman, golang-nuts
@Chris, Thanks for the response! Looks like job objects is the way to go.

@Alex, Thanks a lot for sharing the code! We will be implementing job objects soon. :)



On Sun, Apr 3, 2016 at 3:32 AM, brainman <alex.b...@gmail.com> wrote:
Here https://github.com/alexbrainman/ps I have some code to get you started.

Alex

--
Reply all
Reply to author
Forward
0 new messages