https://github.com/python/cpython/commit/77605fa3bb4e380d5448665f70d73c8d3fcc2e75
commit: 77605fa3bb4e380d5448665f70d73c8d3fcc2e75
branch: main
author: pulkin <gpul...@gmail.com>
committer: gpshead <g...@krypto.org>
date: 2025-04-22T23:55:24-07:00
summary:

gh-131913: multiprocessing: add interrupt for POSIX (GH-132453)

* multiprocessing: interrupt

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Bénédikt Tran <10796600+picn...@users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst
M Doc/library/multiprocessing.rst
M Doc/whatsnew/3.14.rst
M Lib/multiprocessing/popen_fork.py
M Lib/multiprocessing/process.py
M Lib/test/_test_multiprocessing.py

diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index 6ccc0d4aa59555..e44142a8ed3106 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -670,6 +670,25 @@ The :mod:`multiprocessing` package mostly replicates the 
API of the
 
       .. versionadded:: 3.3
 
+   .. method:: interrupt()
+
+      Terminate the process. Works on POSIX using the 
:py:const:`~signal.SIGINT` signal.
+      Behavior on Windows is undefined.
+
+      By default, this terminates the child process by raising 
:exc:`KeyboardInterrupt`.
+      This behavior can be altered by setting the respective signal handler in 
the child
+      process :func:`signal.signal` for :py:const:`~signal.SIGINT`.
+
+      Note: if the child process catches and discards 
:exc:`KeyboardInterrupt`, the
+      process will not be terminated.
+
+      Note: the default behavior will also set :attr:`exitcode` to ``1`` as if 
an
+      uncaught exception was raised in the child process. To have a different
+      :attr:`exitcode` you may simply catch :exc:`KeyboardInterrupt` and call
+      ``exit(your_code)``.
+
+      .. versionadded:: next
+
    .. method:: terminate()
 
       Terminate the process.  On POSIX this is done using the 
:py:const:`~signal.SIGTERM` signal;
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 904a3ce6d597e9..63a5bab7fe8fea 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -972,6 +972,10 @@ multiprocessing
   The :func:`set` in :func:`multiprocessing.Manager` method is now available.
   (Contributed by Mingyu Park in :gh:`129949`.)
 
+* Add :func:`multiprocessing.Process.interrupt` which terminates the child
+  process by sending :py:const:`~signal.SIGINT`. This enables "finally" clauses
+  and printing stack trace for the terminated process.
+  (Contributed by Artem Pulkin in :gh:`131913`.)
 
 operator
 --------
diff --git a/Lib/multiprocessing/popen_fork.py 
b/Lib/multiprocessing/popen_fork.py
index a57ef6bdad5ccc..7affa1b985f091 100644
--- a/Lib/multiprocessing/popen_fork.py
+++ b/Lib/multiprocessing/popen_fork.py
@@ -54,6 +54,9 @@ def _send_signal(self, sig):
                 if self.wait(timeout=0.1) is None:
                     raise
 
+    def interrupt(self):
+        self._send_signal(signal.SIGINT)
+
     def terminate(self):
         self._send_signal(signal.SIGTERM)
 
diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py
index b45f7df476f7d8..9db322be1aa6d6 100644
--- a/Lib/multiprocessing/process.py
+++ b/Lib/multiprocessing/process.py
@@ -125,6 +125,13 @@ def start(self):
         del self._target, self._args, self._kwargs
         _children.add(self)
 
+    def interrupt(self):
+        '''
+        Terminate process; sends SIGINT signal
+        '''
+        self._check_closed()
+        self._popen.interrupt()
+
     def terminate(self):
         '''
         Terminate process; sends SIGTERM signal or uses TerminateProcess()
diff --git a/Lib/test/_test_multiprocessing.py 
b/Lib/test/_test_multiprocessing.py
index 58d8a5eae8a2ce..54b942e76d78c5 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -512,15 +512,20 @@ def _test_process_mainthread_native_id(cls, q):
     def _sleep_some(cls):
         time.sleep(100)
 
+    @classmethod
+    def _sleep_no_int_handler(cls):
+        signal.signal(signal.SIGINT, signal.SIG_DFL)
+        cls._sleep_some()
+
     @classmethod
     def _test_sleep(cls, delay):
         time.sleep(delay)
 
-    def _kill_process(self, meth):
+    def _kill_process(self, meth, target=None):
         if self.TYPE == 'threads':
             self.skipTest('test not appropriate for {}'.format(self.TYPE))
 
-        p = self.Process(target=self._sleep_some)
+        p = self.Process(target=target or self._sleep_some)
         p.daemon = True
         p.start()
 
@@ -567,6 +572,19 @@ def handler(*args):
 
         return p.exitcode
 
+    @unittest.skipIf(os.name == 'nt', "POSIX only")
+    def test_interrupt(self):
+        exitcode = self._kill_process(multiprocessing.Process.interrupt)
+        self.assertEqual(exitcode, 1)
+        # exit code 1 is hard-coded for uncaught exceptions
+        # (KeyboardInterrupt in this case)
+        # in multiprocessing.BaseProcess._bootstrap
+
+    @unittest.skipIf(os.name == 'nt', "POSIX only")
+    def test_interrupt_no_handler(self):
+        exitcode = self._kill_process(multiprocessing.Process.interrupt, 
target=self._sleep_no_int_handler)
+        self.assertEqual(exitcode, -signal.SIGINT)
+
     def test_terminate(self):
         exitcode = self._kill_process(multiprocessing.Process.terminate)
         self.assertEqual(exitcode, -signal.SIGTERM)
diff --git 
a/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst 
b/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst
new file mode 100644
index 00000000000000..be036524bc3434
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst
@@ -0,0 +1 @@
+Add a shortcut function :func:`multiprocessing.Process.interrupt` alongside 
the existing :func:`multiprocessing.Process.terminate` and 
:func:`multiprocessing.Process.kill` for an improved control over child process 
termination.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to