No documented Win32 contract guarantees bounded or unconditional termination progress from this state; the supported sequence is to request termination before continuing the initial event, reconcile the debugging events through EXIT_PROCESS_DEBUG_EVENT, continue that exit event, and then observe completion on an independently retained process handle.
Supported ordering
- Retain an independent process handle with:
-
PROCESS_TERMINATEforTerminateProcess. -
SYNCHRONIZEfor waiting.
hProcesssupplied inCREATE_PROCESS_DEBUG_INFO, because the debugging subsystem closes its process/thread handles whenEXIT_PROCESS_DEBUG_EVENTis continued. Microsoft specifically recommends duplicating that process handle before continuing the exit event when it will be used to observe final shutdown.^1^ -
- Close
CREATE_PROCESS_DEBUG_INFO.hFileif non-NULL after completing the admission check. The debugger owns that image-file handle and must close it.^2^ - While
CREATE_PROCESS_DEBUG_EVENTremains outstanding, call:if (!TerminateProcess(retainedProcess, rejectedExitCode)) { DWORD error = GetLastError(); // Termination was not successfully requested; handle as failure. }TerminateProcessrequiresPROCESS_TERMINATE. For an external process it is asynchronous: success means termination was initiated, not completed. Completion can be delayed while pending I/O is completed or cancelled.^3^ - Only after
TerminateProcesssucceeds, continue the outstanding create event:
The documentation does not state verbatim that termination cannot progress while this particular create event remains outstanding. Continuing it is the conservative ordering derived from the documented debug-event handshake: a reported event suspends the process, event processing advances throughif (!ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE)) { DWORD error = GetLastError(); // Debug protocol could not be advanced; report/handle failure. }ContinueDebugEvent, and the debugger normally alternates waiting and continuing.^1^^4^ - Continue pumping events. For each event delivered by
WaitForDebugEvent:- Perform required per-event cleanup, such as closing a non-NULL
LOAD_DLL_DEBUG_EVENTfile handle. - Continue the event.
- Do not assume that only
EXIT_PROCESS_DEBUG_EVENTcan appear. - Do not treat a timeout or API failure as successful shutdown.
- Perform required per-event cleanup, such as closing a non-NULL
- On
EXIT_PROCESS_DEBUG_EVENT:- Record its exit code.
- Release debugger bookkeeping.
- Call
ContinueDebugEventfor that event. - Only then wait on the independently retained process handle.
EXIT_PROCESS_DEBUG_EVENTremains outstanding. Continuing it releases the debugger-owned process/thread handles and allows final shutdown to proceed.^1^^5^ - Observe completion:
A process object becomes signaled when the process terminates. Waiting requiresDWORD result = WaitForSingleObject(retainedProcess, timeoutMs); if (result == WAIT_OBJECT_0) { // Completed process shutdown has been observed. } else if (result == WAIT_TIMEOUT) { // Termination remains incomplete or unobserved. } else { // WAIT_FAILED DWORD error = GetLastError(); }SYNCHRONIZE;WAIT_OBJECT_0is the completion observation, whileWAIT_TIMEOUTis not.^6^^7^
Execution exclusion
The initial CREATE_PROCESS_DEBUG_EVENT is generated before the process begins executing in user mode and before any other debug event for it. While that event is outstanding, all affected threads remain suspended.^1^
After a successful TerminateProcess, the documented semantics are that all process threads are terminated with no opportunity to execute additional code; termination handlers and DLL detach notifications do not run.^7^ Therefore, terminating first and continuing second is the supported ordering that avoids permitting target user-mode loader activity, DLL initialization, TLS callbacks, or application instructions.
An extra SuspendThread is neither required by this ordering nor documented as providing a stronger process-wide guarantee. It also operates per thread rather than as an atomic process-wide barrier.
Contract boundaries
-
TerminateProcesssuccess is a successful request, not proof of progress or completion. - Receipt of
EXIT_PROCESS_DEBUG_EVENTestablishes that the last thread exited and the exit code was updated, but kernel teardown still awaits continuation of that event. -
WAIT_OBJECT_0on the retained process handle after continuing the exit event is the independent completion observation. - The documentation does not guarantee that
EXIT_PROCESS_DEBUG_EVENTor a signaled process handle will be reached within a bounded time. -
WaitForDebugEventandContinueDebugEventmust be called by the thread that created the debugged process.^2^^5^ - These debugging APIs list desktop-app support beginning with Windows XP and Windows Server 2003. No separate architecture-specific guarantee is documented for this combined sequence.
References