Description
Assume you have this process tree:
- Process 1
- Process 2 (in job object)
Process 1 creates process 2 and adds it to a new Windows job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE such that process 2 is automatically killed when process 1 gets terminated.
Process 2 then creates process 3 which is not part of that job object.
In this scenario, calling Kill(entireProcessTree: true) on process 1 terminates process 1 and process 2 but NOT process 3.
This scenario occurs for example with the Windows Python Launcher py.exe.
py.exe starts python.exe in a job object, but subprocesses started by the Python script are not in that job object, by design:
the launcher will execute its command in a child process, remaining alive while the child process is executing
the Win32 Job API will be used to arrange so that the child process is automatically killed when the parent is terminated (although children of that child process will continue as is the case now.)
Reproduction Steps
- Create
DotnetKillTree.exe:
dotnet new console -n DotnetKillTree -o .
echo System.Diagnostics.Process.GetProcessById(int.Parse(args[0])).Kill(entireProcessTree: true); > Program.cs
dotnet publish -r win-x64 --sc -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false -o .
- Download and install Python for Windows from https://www.python.org/downloads/
- Download PsList from https://learn.microsoft.com/en-us/sysinternals/downloads/pslist
- Start the process tree in a new console:
py.exe -c "import subprocess;subprocess.run(['ping.exe', '-t', 'localhost'])"
- In another console show the process tree:
C:\Users\WDAGUtilityAccount\Desktop\Test>pslist -t
PsList v1.41 - Process information lister
Copyright (C) 2000-2023 Mark Russinovich
Sysinternals - www.sysinternals.com
Process information for A1ACB1A2-C520-4:
Name Pid Pri Thd Hnd VM WS Priv
Idle 0 0 16 0 8 8 60
...
explorer 4244 8 59 2497 4194303 164184 64388
cmd 1356 8 4 81 4194303 4800 5468
pslist 4596 13 4 223 63620 7752 2436
conhost 5788 8 7 201 4194303 18800 7236
cmd 4796 8 1 84 4194303 4860 2272
conhost 720 8 4 202 4194303 18880 7148
py 5416 8 4 144 67540 7012 1596
python 4724 8 4 90 4194303 10936 7216
PING 4268 8 6 94 4194303 4528 996
- Kill the
py process subtree:
C:\Users\WDAGUtilityAccount\Desktop\Test>DotnetKillTree.exe 5416
- Check the process tree:
C:\Users\WDAGUtilityAccount\Desktop\Test>pslist -t
Name Pid Pri Thd Hnd VM WS Priv
Idle 0 0 16 0 8 8 60
...
explorer 4244 8 55 2483 4194303 164800 64732
cmd 1356 8 2 84 4194303 4896 3892
pslist 3608 13 4 223 63620 7748 2428
conhost 5788 8 8 204 4194303 18880 7276
cmd 4796 8 1 83 4194303 4860 2532
conhost 720 8 4 200 4194303 18880 7148
PING 4268 8 6 94 4194303 4536 1004
Expected behavior
The PING process 4268 is also killed.
Actual behavior
The PING process 4268 remains running.
Regression?
I don't know. Probably not.
Known Workarounds
Avoid creating processes in job objects.
For example directly start python.exe instead of py.exe:
python.exe -c "import subprocess;subprocess.run(['ping.exe', '-t', 'localhost'])"
Configuration
.NET 8.0.202
Windows 10 Pro 22H2
x64
It's only an issue on Windows (because of job objects).
But it's probably not specific to this configuration.
Other information
Not sure, but the problem might be that the KillTree(SafeProcessHandle handle) method first kills a process (so that no further children can be created) and then lists its children and recursively kills them.
I suspect that in the above scenario killing process 1 closes the job object which already kills process 2 (but not process 3). And then process 2 is no longer a child of process 1 (and neither is process 3) and therefore the recursion ends and process 3 remains running.
To inspect the job object of each process (in a different test, hence the PIDs don't match) I used ProcessExplorer:



Description
Assume you have this process tree:
Process 1 creates process 2 and adds it to a new Windows job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE such that process 2 is automatically killed when process 1 gets terminated.
Process 2 then creates process 3 which is not part of that job object.
In this scenario, calling
Kill(entireProcessTree: true)on process 1 terminates process 1 and process 2 but NOT process 3.This scenario occurs for example with the Windows Python Launcher
py.exe.py.exestartspython.exein a job object, but subprocesses started by the Python script are not in that job object, by design:Reproduction Steps
DotnetKillTree.exe:pyprocess subtree:Expected behavior
The
PINGprocess4268is also killed.Actual behavior
The
PINGprocess4268remains running.Regression?
I don't know. Probably not.
Known Workarounds
Avoid creating processes in job objects.
For example directly start
python.exeinstead ofpy.exe:Configuration
.NET 8.0.202
Windows 10 Pro 22H2
x64
It's only an issue on Windows (because of job objects).
But it's probably not specific to this configuration.
Other information
Not sure, but the problem might be that the KillTree(SafeProcessHandle handle) method first kills a process (so that no further children can be created) and then lists its children and recursively kills them.
I suspect that in the above scenario killing process 1 closes the job object which already kills process 2 (but not process 3). And then process 2 is no longer a child of process 1 (and neither is process 3) and therefore the recursion ends and process 3 remains running.
To inspect the job object of each process (in a different test, hence the PIDs don't match) I used ProcessExplorer: