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
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 @@
          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 @@
             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 @@
                 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 @@
         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 @@
         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 @@
         # 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 @@
 
         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(p.exitcode, -signal.SIGTERM)
+            self.assertEqual(exitcode, -signal.SIGTERM)
+
+    def test_kill(self):
+        exitcode = self._kill_process(multiprocessing.Process.kill)
+        if os.name != 'nt':
+            self.assertEqual(exitcode, -signal.SIGKILL)
 
     def test_cpu_count(self):
         try:
@@ -462,7 +470,7 @@
         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.