You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix tests failing with The process cannot access the file because it is being used by another process by force-killing child processes #101601
GitHub Actions continue to sporadically fail with PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'D:\\a\\cpython\\cpython\\build\\test_python_5740�\\test_python_worker_5604�'.
The failure happens when a test creates a child process, shares a test directory with it, and the child outlives its parent.
I'd prefer for job and process group support to be integrated into subprocess and multiprocessing.
In test.libregrtest.runtest_mp, the way I envision this in practice is as follows. In run_test_in_subprocess(), the subprocess.Popen arguments would be extended to create a job on Windows, which would be implemented to also create a new process group.
ifsys.platform=='win32':
kw['createjob'] =True
Killing the process in TestWorkerProcess._kill() would be extended for both POSIX and Windows. On POSIX, use the common combination of SIGTERM and SIGKILL. On Windows, send SIGBREAK to the process group, and then terminate the job.
Regarding subprocess.Popen, the above depends on the addition of a new createjob=False parameter and a terminate_job() method, as well as a new cross-platform send_signal_pg() method.
Configuring the job with the limit flag JOB_OBJECT_LIMIT_BREAKAWAY_OK allows child processes to be created outside of the job by specifying the process creation flag CREATE_BREAKAWAY_FROM_JOB.
The implementation of _winapi.SetInformationJobObject() only has to support setting JobObjectExtendedLimitInformation. The only limit flag that needs to be supported is JOB_OBJECT_LIMIT_BREAKAWAY_OK, but it can also easily support the flags JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, and JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. For everything else it can raise NotImplementedError.
After spawning the process in Popen._execute_child(), add it to the job and resume the initial thread as follows:
The new pgid attribute (process group ID) would be used by the new send_signal_pg() method. This sends SIGINT or SIGBREAK more reliably and clearly by ensuring that the child process was created with a new process group and that the current process and child process are in the same console session. Ensuring the latter avoids longstanding bugs in WinAPI GenerateConsoleCtrlEvent().
defsend_signal_pg(self, sig):
"""Send a signal to the process group."""ifself.pgidisNone:
raiseSubprocessError('no process group assigned')
try:
processes=_winapi.GetConsoleProcessList()
exceptOSErrorase:
ife.winerror==_winapi.ERROR_INVALID_HANDLE:
raiseSubprocessError('a console session is required')
raiseifself.pidnotinprocesses:
raiseSubprocessError('the process must be in the current ''console session')
ifsig==signal.SIGINT:
ctrl_event=_winapi.CTRL_C_EVENTelifsig==signal.SIGBREAK:
ctrl_event=_winapi.CTRL_BREAK_EVENTelse:
raiseValueError("Unsupported signal: {}".format(sig))
_winapi.GenerateConsoleCtrlEvent(ctrl_event, self.pgid)
A method is needed to forcefully terminate all active processes in the job.
WinAPI TerminateJobObject() succeeds if the job has no active processes.
It would be nice to also implement a method that returns basic accounting information about the job, such as the number of active processes, how much user/kernel time the job has used, and info about the job's I/O operations. For example:
The implementation of _winapi.QueryInformationJobObject() only has to support querying JobObjectBasicAndIoAccountingInformation. For everything else it can raise NotImplementedError.
On POSIX, pgid would be set after creating the child process, depending on the process_group and start_new_session arguments.
send_signal_pg() should be relatively simple on POSIX.
defsend_signal_pg(self, sig):
"""Send a signal to the process group."""ifself.pgidisNone:
raiseSubprocessError('no process group assigned')
try:
os.killpg(self.pgid, sig)
exceptProcessLookupError:
pass
Activity
eryksun commentedon Feb 8, 2023
I'd prefer for job and process group support to be integrated into
subprocessandmultiprocessing.In
test.libregrtest.runtest_mp, the way I envision this in practice is as follows. Inrun_test_in_subprocess(), thesubprocess.Popenarguments would be extended to create a job on Windows, which would be implemented to also create a new process group.Killing the process in
TestWorkerProcess._kill()would be extended for both POSIX and Windows. On POSIX, use the common combination ofSIGTERMandSIGKILL. On Windows, sendSIGBREAKto the process group, and then terminate the job.Regarding
subprocess.Popen, the above depends on the addition of a newcreatejob=Falseparameter and aterminate_job()method, as well as a new cross-platformsend_signal_pg()method.Popen.__init__()can set up the job as follows:Configuring the job with the limit flag
JOB_OBJECT_LIMIT_BREAKAWAY_OKallows child processes to be created outside of the job by specifying the process creation flagCREATE_BREAKAWAY_FROM_JOB.The implementation of
_winapi.SetInformationJobObject()only has to support settingJobObjectExtendedLimitInformation. The only limit flag that needs to be supported isJOB_OBJECT_LIMIT_BREAKAWAY_OK, but it can also easily support the flagsJOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK,JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, andJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. For everything else it can raiseNotImplementedError.After spawning the process in
Popen._execute_child(), add it to the job and resume the initial thread as follows:The new
pgidattribute (process group ID) would be used by the newsend_signal_pg()method. This sendsSIGINTorSIGBREAKmore reliably and clearly by ensuring that the child process was created with a new process group and that the current process and child process are in the same console session. Ensuring the latter avoids longstanding bugs in WinAPIGenerateConsoleCtrlEvent().A method is needed to forcefully terminate all active processes in the job.
WinAPI
TerminateJobObject()succeeds if the job has no active processes.It would be nice to also implement a method that returns basic accounting information about the job, such as the number of active processes, how much user/kernel time the job has used, and info about the job's I/O operations. For example:
The above would return a dict containing
BasicInfoandIoInfodicts, based onJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION. For example:>>> p.get_job_info() {'BasicInfo': {'TotalUserTime': 1250000, 'TotalKernelTime': 625000, 'ThisPeriodTotalUserTime': 1250000, 'ThisPeriodTotalKernelTime': 625000, 'TotalPageFaultCount': 5163, 'TotalProcesses': 2, 'ActiveProcesses': 2, 'TotalTerminatedProcesses': 0}, 'IoInfo': {'ReadOperationCount': 64, 'WriteOperationCount': 2, 'OtherOperationCount': 995, 'ReadTransferCount': 768540, 'WriteTransferCount': 172, 'OtherTransferCount': 99152}}The implementation of
_winapi.QueryInformationJobObject()only has to support queryingJobObjectBasicAndIoAccountingInformation. For everything else it can raiseNotImplementedError.On POSIX,
pgidwould be set after creating the child process, depending on theprocess_groupandstart_new_sessionarguments.send_signal_pg()should be relatively simple on POSIX.