@@ -122,15 +122,11 @@ func (pg *processGroup) Wait() error {
122122 for {
123123 err := syscall .GetQueuedCompletionStatus (pg .ioPort , & completionCode , & completionKey , & overlapped , syscall .INFINITE )
124124 if err != nil {
125- pg .cmd .Wait ()
126125 waitDone <- err
127126 return
128127 }
129128
130129 if completionKey == magicCompletionKey && completionCode == syscallex .JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO {
131- // Call cmd.Wait() to properly release the process handle.
132- // Go 1.24+ panics if the process is GC'd without Wait().
133- pg .cmd .Wait ()
134130 waitDone <- nil
135131 return
136132 }
@@ -143,7 +139,9 @@ func (pg *processGroup) Wait() error {
143139 if pg .jobObject == syscall .InvalidHandle {
144140 pid := uint32 (pg .cmd .Process .Pid )
145141 pg .consumer .Infof ("Killing single process %d" , pid )
146- pg .cmd .Process .Kill ()
142+ // Use terminateProcess instead of cmd.Process.Kill() because
143+ // the os.Process handle from os.FindProcess lacks PROCESS_TERMINATE.
144+ terminateProcess (pid , 1 )
147145 } else {
148146 pg .consumer .Infof ("Attempting to kill entire job object..." )
149147 var processIdList syscallex.JobObjectBasicProcessIdList
@@ -184,16 +182,17 @@ func (pg *processGroup) Wait() error {
184182 }
185183 }
186184 }
187- // Wait for the goroutine to finish, which calls cmd.Wait()
188- // to properly release the process handle (required by Go 1.24+).
189- <- waitDone
190185 case err := <- waitDone :
191186 pg .consumer .Infof ("Wait done" )
192187 if err != nil {
193188 return fmt .Errorf ("%w" , err )
194189 }
195190 }
196191
192+ // Close the raw process handle stored in SysProcAttr. This is a separate
193+ // handle from the one managed by os.Process and must be closed explicitly.
194+ syscall .CloseHandle (pg .cmd .SysProcAttr .ProcessHandle )
195+
197196 return nil
198197}
199198
0 commit comments