Hi @Branislav Lazic ,
Thank you for the detailed question. Some parts can be answered from Microsoft documentation. Others, mainly what happens when the caller terminates while CreateProcessW is still running, are not covered by any public API contract. I've marked each point clearly below.
1. Can the new process run without being assigned to the Job Object if the caller terminates during CreateProcessW?
The UpdateProcThreadAttribute reference describes PROC_THREAD_ATTRIBUTE_JOB_LIST as a list of job handles that are assigned to the child process in the order specified. It does not describe the outcome if the caller terminates before the call returns, and neither does the CreateProcessW reference. I could not find documentation that guarantees an outcome for that specific interruption. The lack of documentation does not mean an unassigned child can actually occur. It only means the public API contract does not cover this case.
2. Is Job assignment guaranteed to complete before the initial thread runs?
The API reference does not state this ordering. However, Microsoft's published guidance does. Raymond Chen's article A more direct and mistake-free way of creating a process in a job object (The Old New Thing, February 2023) explains that the process is assigned to each listed job in turn, and that this happens before the process's initial thread is allowed to run. As a result, the process cannot execute outside the job. The article also explains why the attribute was added: the older pattern of creating the process suspended and then calling AssignProcessToJobObject can leave the child outside the job if the creator crashes between the two steps. Please note that this is a DevBlogs explanation, not API reference text.
3. What happens if the caller terminates after process creation but before handling the returned handles?
- When a process terminates, all of its kernel object handles are closed. Child processes are not terminated just because their parent terminated (Terminating a Process).
Once a process is associated with a job, that association cannot be broken (Job Objects).
If the caller's handle was the last handle to the job, closing it triggers JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, and the system terminates all processes associated with the job (JOBOBJECT_BASIC_LIMIT_INFORMATION).
This termination should not be treated as instantaneous. TerminateProcess is asynchronous when it targets another process, and the target cannot exit until its pending I/O has completed or been canceled.
4. Does closing the last Job Object handle guarantee termination of all associated processes?
Yes. This is the part with a clear documented guarantee. The Job Objects page states that when JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE is set, closing the last job handle terminates all associated processes and then destroys the job. For nested jobs, processes in child jobs are terminated as well. The guarantee depends on these conditions:
No other handle keeps the job alive. Kernel objects remain alive as long as any handle exists, including handles opened by name or duplicated with DuplicateHandle (Kernel Objects). An unnamed job with a non-inheritable handle avoids most of these cases. Raymond Chen also notes that an inheritable job handle can be inherited by accident if other code in the process calls CreateProcess with bInheritHandles = TRUE (Destroying all child processes (and grandchildren) when the parent exits, Additional notes on controlling which handles are inherited by CreateProcess).
Breakaway is not allowed. If the job sets JOB_OBJECT_LIMIT_BREAKAWAY_OK or JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, descendants can be created outside the job. Child processes created through Win32_Process.Create are also not associated with the job (Job Objects).
5. Is there a documented transactional or rollback guarantee?
No. Neither the CreateProcessW reference nor the UpdateProcThreadAttribute reference describes transactional semantics or rollback when the caller terminates during the call. CreateProcessW is also documented to return before the new process has finished initializing.
6. Does terminating only the calling thread differ from terminating the whole process?
Yes, in a way that matters for your design:
Thread only. Handles belong to the process, not the thread (Kernel Objects). If other threads are still running, the process stays alive and its job handle stays open, so KILL_ON_JOB_CLOSE is not triggered. The process terminates only if that thread was its last active thread (Terminating a Thread).
Thread killed during the call. TerminateThread warns that terminating a thread while it is running certain Kernel32 calls can leave the process's Kernel32 state inconsistent. The documentation does not describe what happens to a child being created at that moment.
Whole process. All handles are closed, so the behavior in point 3 applies.
Recommended design based on the documented behavior
Create an unnamed job with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE and no breakaway limits.
Keep the job handle non-inheritable. If the child must inherit other handles, list them explicitly with PROC_THREAD_ATTRIBUTE_HANDLE_LIST (CreateProcessW).
Use PROC_THREAD_ATTRIBUTE_JOB_LIST instead of creating the process suspended and then calling AssignProcessToJobObject, for the reason explained in point 2.
If you want an explicit check before the child runs, also pass CREATE_SUSPENDED. The primary thread does not run until ResumeThread is called (Process Creation Flags). Confirm membership with IsProcessInJob (Job Objects) before resuming the thread.
This covers what can be established from public documentation. If your design needs a formal guarantee for the interrupted-call scenario (points 1 and 5), you can request clarification through Windows Developer Support. Please note that this is paid, incident-based support.
I hope this helps. If you found it useful, you're welcome to follow this guidance so others with a similar question can find it more easily.
Thank you.