diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index ffd1735848c455..ee5f76780bd0ca 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -39,6 +39,25 @@ def __init__(self): self._lock = threading.Lock() self._preload_modules = ['__main__'] + def _stop(self): + # Method used by unit tests to stop the server + with self._lock: + self._stop_unlocked() + + def _stop_unlocked(self): + if self._forkserver_pid is None: + return + + # close the "alive" file descriptor asks the server to stop + os.close(self._forkserver_alive_fd) + self._forkserver_alive_fd = None + + os.waitpid(self._forkserver_pid, 0) + self._forkserver_pid = None + + os.unlink(self._forkserver_address) + self._forkserver_address = None + def set_forkserver_preload(self, modules_names): '''Set list of module names to try to load in forkserver process.''' if not all(type(mod) is str for mod in self._preload_modules): diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index f5331abe821fb1..327fe42d6efdb3 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -456,3 +456,24 @@ def spawnv_passfds(path, args, passfds): finally: os.close(errpipe_read) os.close(errpipe_write) + + +def _cleanup_tests(): + """Cleanup multiprocessing resources when multiprocessing tests + completed.""" + + from test import support + + # cleanup multiprocessing + process._cleanup() + + # Stop the ForkServer process if it's running + from multiprocessing import forkserver + forkserver._forkserver._stop() + + # bpo-37421: Explicitly call _run_finalizers() to remove immediately + # temporary directories created by multiprocessing.util.get_temp_dir(). + _run_finalizers() + support.gc_collect() + + support.reap_children() diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 30ea23da694991..eb03da9bd4487c 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5049,8 +5049,8 @@ def tearDownModule(): # Sleep 500 ms to give time to child processes to complete. if need_sleep: time.sleep(0.5) - multiprocessing.process._cleanup() - test.support.gc_collect() + + multiprocessing.util._cleanup_tests() remote_globs['setUpModule'] = setUpModule remote_globs['tearDownModule'] = tearDownModule diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py index b42670e16a6c99..a261bfeb4d3734 100644 --- a/Lib/test/test_concurrent_futures.py +++ b/Lib/test/test_concurrent_futures.py @@ -26,6 +26,7 @@ BrokenExecutor) from concurrent.futures.process import BrokenProcessPool from multiprocessing import get_context +import multiprocessing.util def create_future(state=PENDING, exception=None, result=None): @@ -1253,6 +1254,7 @@ def test_main(): test.support.run_unittest(__name__) finally: test.support.reap_children() + multiprocessing.util._cleanup_tests() if __name__ == "__main__": test_main() diff --git a/Misc/NEWS.d/next/Tests/2019-07-01-19-57-26.bpo-37421.NFH1f0.rst b/Misc/NEWS.d/next/Tests/2019-07-01-19-57-26.bpo-37421.NFH1f0.rst new file mode 100644 index 00000000000000..c379b504ba8a41 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2019-07-01-19-57-26.bpo-37421.NFH1f0.rst @@ -0,0 +1,2 @@ +multiprocessing tests now explicitly call ``_run_finalizers()`` to +immediately remove temporary directories created by tests. diff --git a/Misc/NEWS.d/next/Tests/2019-07-05-14-47-55.bpo-37421.n8o2to.rst b/Misc/NEWS.d/next/Tests/2019-07-05-14-47-55.bpo-37421.n8o2to.rst new file mode 100644 index 00000000000000..136faa22d47a6e --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2019-07-05-14-47-55.bpo-37421.n8o2to.rst @@ -0,0 +1,3 @@ +multiprocessing tests now stop the ForkServer instance if it's running: close +the "alive" file descriptor to ask the server to stop and then remove its UNIX +address.