@@ -123,15 +123,55 @@ def start_suite(
123123 )
124124
125125
126+ def windows_descendants (pid : int , timeout : int ) -> bool :
127+ """True if any live process still claims `pid` as its parent.
128+
129+ Used only when the suite leader has already exited: `taskkill /T` cannot
130+ walk a tree from a dead PID, so cleanup is proven by asking whether anything
131+ is still parented to it. One level deep on purpose -- Windows does not
132+ reparent orphans, so a grandchild keeps pointing at its own (dead) parent
133+ and would not be found here. That is a weaker proof than taskkill /T, which
134+ is why it is reserved for the case where the strong proof is impossible.
135+ """
136+ try :
137+ completed = subprocess .run (
138+ [
139+ "powershell.exe" ,
140+ "-NoProfile" ,
141+ "-NonInteractive" ,
142+ "-Command" ,
143+ "@(Get-CimInstance Win32_Process -Filter "
144+ f"'ParentProcessId={ pid } ').Count" ,
145+ ],
146+ check = False ,
147+ stdin = subprocess .DEVNULL ,
148+ capture_output = True ,
149+ text = True ,
150+ timeout = timeout ,
151+ )
152+ except (OSError , subprocess .TimeoutExpired ):
153+ return True # cannot prove absence -> assume the worst
154+ if completed .returncode != 0 :
155+ return True
156+ return (completed .stdout or "" ).strip () not in ("0" , "" )
157+
158+
126159def terminate_process_tree (active : ActiveSuite , kill_grace : int ) -> None :
127160 process = active .process
128161 leader_exited = process .poll () is not None
129162 if os .name == "nt" :
130163 if leader_exited :
131- raise RuntimeError (
132- f"suite { active .name !r} leader exited before Windows tree cleanup "
133- "could be proven"
134- )
164+ # The leader can exit on its own between the timeout decision and
165+ # this call. Refusing outright made the harness itself lose a race:
166+ # a natural exit at the wrong moment failed the whole wave, which is
167+ # how a deliberately-hanging fixture suite reddened a release run.
168+ # taskkill /T cannot walk a tree from a dead PID, so prove cleanup
169+ # the only way still available -- nothing is parented to it.
170+ if windows_descendants (process .pid , kill_grace ):
171+ raise RuntimeError (
172+ f"suite { active .name !r} leader exited leaving live descendants"
173+ )
174+ return
135175 try :
136176 completed = subprocess .run (
137177 [
0 commit comments