c++windowswinapiterminatewaitforsingleobject

How to make sure the process signalled to terminate using TerminateProcess WinAPI is actually terminated?


I am writing the following code to terminate a process with the given PID:

int TerminateProcessInstance(DWORD dwPID)
{
 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
 if (!TerminateProcess(hProcess, 0))
 {
  cout << "Failed to initiate terminate process" << endl;
  return -1;
 }
 else
 {
  // Now that the termination of process is initiated,
  // how do I confirm the process is terminated before proceeding here
 }
 CloseHandle(hProcess);
 return 0;
}

The TerminateProcess API documentation mentions to use WaitForSingleObject to make sure the process has terminated:

TerminateProcess is asynchronous; it initiates termination and returns immediately. If you need to be sure the process has terminated, call the WaitForSingleObject function with a handle to the process.


Should I update the code like this?

int TerminateProcessInstance(DWORD dwPID)
{
 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
 if (!TerminateProcess(hProcess, 0))
 {
  cout << "Failed to initiate terminate process" << endl;
  return -1;
 }
 else
 {
  bool bStatus = WaitForSingleObject(hProcess, 60000);
  if(bStatus == WAIT_TIMEOUT)
  {
   cout << "Failed to terminate process" << endl;
   return -1;
  }
 }
 CloseHandle(hProcess);
 return 0;
}

Another question that I have is, if TerminateProcess did succeed in terminating the process before I call WaitForSingleObject, what will be the state of the handle hProcess?

Will WaitForSingleObject fail in that case?


Solution

  • Calling one of the (object) wait functions on a process handle is a well-defined operation that can determine whether a process has terminated.

    This is easy-to-follow advice. To understand why this is true, we need to cover several concepts and introduce supporting terminology along the way.


    At a fundamental level, a process is a container that holds resources. The object representing a process is data. Data doesn't execute.

    The unit of execution is a thread. Every thread is owned by a process. When we colloquially say that "a process executes," what we mean instead is that "one or more threads owned by a process are eligible to run."

    This may not appear to be a useful distinction to make until we look at process termination. A call to TerminateProcess walks across all threads stored in a process object and terminates each one in turn. The process object itself remains alive.


    With the core primitives covered in broad strokes, the previous paragraph left off with a question: If TerminateProcess doesn't destroy the process object, who does, and when?

    (Kernel) object lifetimes are the responsibility of the object manager. It maintains a reference count for each object, incrementing it as it hands out HANDLEs to clients, and decrementing it whenever clients relinquish their (shared) ownership with a call to CloseHandle.

    The referenced object is destroyed only after its final HANDLE has been released. A successful call to OpenProcess thus keeps the process object alive at least until that HANDLE is passed into CloseHandle.


    The final part revolves around the synchronization API. Wait functions (such as WaitForSingleObject) return whenever the object is signaled or the timeout expired, whichever happened first. Process objects transition to (and remain in) the signaled state when the process terminates, out of free will or by force.


    In summary:

    The proposed code is still wrong for several reasons:

    A better implementation would read like this:

    int TerminateProcessInstance(DWORD dwPID) {
        HANDLE hProcess = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, dwPID);
        if (hProcess == NULL) return -1;
    
        if (!TerminateProcess(hProcess, 0)) {
            cout << "Failed to initiate terminate process" << endl;
            CloseHandle(hProcess);
            return -1;
        }
    
        DWORD res = WaitForSingleObject(hProcess, 60000);
        if(res != WAIT_OBJECT_0) {
            cout << "Process didn't terminate within timeout" << endl;
            CloseHandle(hProcess);
            return -1;
        }
    
        CloseHandle(hProcess);
        return 0;
    }
    

    Use of the Windows Implementation Library would make this code shorter, far more readable, maintainable, and robust.