Fix infinite hang on Windows compilation actions in WaitableProcess.

Occasionally on Windows, Bazel compilation actions (such as MSVC cl.exe) get stuck in an infinite wait in the Bazel server even after the compiler process has finished. When cl.exe runs, it can launch background child processes such as Microsoft's telemetry uploader (vctip.exe), which can enter an unkillable zombie state inside the Windows Job Object. Because Bazel previously waited with an INFINITE timeout for JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO after calling TerminateJobObject, any stuck child process caused the entire build server to hang forever.

Replace INFINITE with a finite 5-second timeout (kJobCompletionTimeoutMsec) when calling GetQueuedCompletionStatus in WaitableProcess::WaitFor after the primary process has terminated and TerminateJobObject has been invoked. This allows Bazel to proceed after a short grace period even if background subprocesses like vctip.exe refuse to terminate. Fixes https://github.com/bazelbuild/bazel/issues/15094.

PiperOrigin-RevId: 971912626
Change-Id: I8e289d376ee7ac4bf61de7abd7a32f64893eb85d
diff --git a/src/main/native/windows/process.cc b/src/main/native/windows/process.cc
index 252a3e3..7a68aa8 100644
--- a/src/main/native/windows/process.cc
+++ b/src/main/native/windows/process.cc
@@ -276,11 +276,16 @@
   if (job_.IsValid()) {
     // Wait for the job object to complete, signalling that all subprocesses
     // have exited.
+    // Use a finite timeout (5 seconds) instead of INFINITE to prevent Bazel
+    // from hanging forever if a child process (such as MSVC's vctip.exe
+    // telemetry uploader) becomes a zombie or refuses to terminate after
+    // TerminateJobObject. See https://github.com/bazelbuild/bazel/issues/15094.
+    static constexpr DWORD kJobCompletionTimeoutMsec = 5000;
     DWORD CompletionCode;
     ULONG_PTR CompletionKey;
     LPOVERLAPPED Overlapped;
-    while (GetQueuedCompletionStatus(ioport_, &CompletionCode,
-                                     &CompletionKey, &Overlapped, INFINITE) &&
+    while (GetQueuedCompletionStatus(ioport_, &CompletionCode, &CompletionKey,
+                                     &Overlapped, kJobCompletionTimeoutMsec) &&
            !((HANDLE)CompletionKey == (HANDLE)job_ &&
              CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) {
       // Still waiting...