From ba75af713078966cc594fc7f0809ed53c532c58f Mon Sep 17 00:00:00 2001 From: Vitor Pereira Date: Tue, 18 Jul 2017 16:34:23 +0100 Subject: bpo-30794: added kill() method to multiprocessing.Process (#2528) * bpo-30794: added kill() method to multiprocessing.Process * Added entries to documentation and NEWS * Refactored test_terminate and test_kill * Fix SIGTERM and SIGKILL being used on Windows for the tests * Added "versionadded" marker to the documentation * Fix trailing whitespace in doc --- Doc/library/multiprocessing.rst | 6 ++++++ Lib/multiprocessing/popen_fork.py | 10 ++++++++-- Lib/multiprocessing/popen_spawn_win32.py | 2 ++ Lib/multiprocessing/process.py | 7 +++++++ Lib/test/_test_multiprocessing.py | 22 +++++++++++++++------- .../2017-07-04-22-00-20.bpo-30794.qFwozm.rst | 2 ++ 6 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-07-04-22-00-20.bpo-30794.qFwozm.rst diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 5265639..9318a75 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -598,6 +598,12 @@ The :mod:`multiprocessing` package mostly replicates the API of the acquired a lock or semaphore etc. then terminating it is liable to cause other processes to deadlock. + .. method:: kill() + + Same as :meth:`terminate()` but using the ``SIGKILL`` signal on Unix. + + .. versionadded:: 3.7 + .. method:: close() Close the :class:`Process` object, releasing all resources associated diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 5af9d91..44ce9a9 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -49,16 +49,22 @@ class Popen(object): return self.poll(os.WNOHANG if timeout == 0.0 else 0) return self.returncode - def terminate(self): + def _send_signal(self, sig): if self.returncode is None: try: - os.kill(self.pid, signal.SIGTERM) + os.kill(self.pid, sig) except ProcessLookupError: pass except OSError: if self.wait(timeout=0.1) is None: raise + def terminate(self): + self._send_signal(signal.SIGTERM) + + def kill(self): + self._send_signal(signal.SIGKILL) + def _launch(self, process_obj): code = 1 parent_r, child_w = os.pipe() diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index ecb86e9..3e42e9c 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -97,5 +97,7 @@ class Popen(object): if self.wait(timeout=1.0) is None: raise + kill = terminate + def close(self): self.finalizer() diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index fde97b7..ce4ce43 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -122,6 +122,13 @@ class BaseProcess(object): self._check_closed() self._popen.terminate() + def kill(self): + ''' + Terminate process; sends SIGKILL signal or uses TerminateProcess() + ''' + self._check_closed() + self._popen.kill() + def join(self, timeout=None): ''' Wait until child process terminates diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index d83b5a7..0515730 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -277,18 +277,18 @@ class _TestProcess(BaseTestCase): self.assertNotIn(p, self.active_children()) @classmethod - def _test_terminate(cls): + def _sleep_some(cls): time.sleep(100) @classmethod def _test_sleep(cls, delay): time.sleep(delay) - def test_terminate(self): + def _kill_process(self, meth): if self.TYPE == 'threads': self.skipTest('test not appropriate for {}'.format(self.TYPE)) - p = self.Process(target=self._test_terminate) + p = self.Process(target=self._sleep_some) p.daemon = True p.start() @@ -309,7 +309,7 @@ class _TestProcess(BaseTestCase): # XXX maybe terminating too soon causes the problems on Gentoo... time.sleep(1) - p.terminate() + meth(p) if hasattr(signal, 'alarm'): # On the Gentoo buildbot waitpid() often seems to block forever. @@ -333,9 +333,17 @@ class _TestProcess(BaseTestCase): p.join() - # sometimes get p.exitcode == 0 on Windows ... + return p.exitcode + + def test_terminate(self): + exitcode = self._kill_process(multiprocessing.Process.terminate) + if os.name != 'nt': + self.assertEqual(exitcode, -signal.SIGTERM) + + def test_kill(self): + exitcode = self._kill_process(multiprocessing.Process.kill) if os.name != 'nt': - self.assertEqual(p.exitcode, -signal.SIGTERM) + self.assertEqual(exitcode, -signal.SIGKILL) def test_cpu_count(self): try: @@ -462,7 +470,7 @@ class _TestProcess(BaseTestCase): for p in procs: self.assertEqual(p.exitcode, 0) - procs = [self.Process(target=self._test_terminate) + procs = [self.Process(target=self._sleep_some) for i in range(N)] for p in procs: p.start() diff --git a/Misc/NEWS.d/next/Library/2017-07-04-22-00-20.bpo-30794.qFwozm.rst b/Misc/NEWS.d/next/Library/2017-07-04-22-00-20.bpo-30794.qFwozm.rst new file mode 100644 index 0000000..d960bd3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-07-04-22-00-20.bpo-30794.qFwozm.rst @@ -0,0 +1,2 @@ +Added multiprocessing.Process.kill method to terminate using the SIGKILL +signal on Unix. -- cgit v0.12