mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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+picnixz@users.noreply.github.com>
This commit is contained in:
parent
862fd89036
commit
77605fa3bb
6 changed files with 54 additions and 2 deletions
|
@ -670,6 +670,25 @@ The :mod:`multiprocessing` package mostly replicates the API of the
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. 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()
|
.. method:: terminate()
|
||||||
|
|
||||||
Terminate the process. On POSIX this is done using the :py:const:`~signal.SIGTERM` signal;
|
Terminate the process. On POSIX this is done using the :py:const:`~signal.SIGTERM` signal;
|
||||||
|
|
|
@ -972,6 +972,10 @@ multiprocessing
|
||||||
The :func:`set` in :func:`multiprocessing.Manager` method is now available.
|
The :func:`set` in :func:`multiprocessing.Manager` method is now available.
|
||||||
(Contributed by Mingyu Park in :gh:`129949`.)
|
(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
|
operator
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -54,6 +54,9 @@ class Popen(object):
|
||||||
if self.wait(timeout=0.1) is None:
|
if self.wait(timeout=0.1) is None:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def interrupt(self):
|
||||||
|
self._send_signal(signal.SIGINT)
|
||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
self._send_signal(signal.SIGTERM)
|
self._send_signal(signal.SIGTERM)
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,13 @@ class BaseProcess(object):
|
||||||
del self._target, self._args, self._kwargs
|
del self._target, self._args, self._kwargs
|
||||||
_children.add(self)
|
_children.add(self)
|
||||||
|
|
||||||
|
def interrupt(self):
|
||||||
|
'''
|
||||||
|
Terminate process; sends SIGINT signal
|
||||||
|
'''
|
||||||
|
self._check_closed()
|
||||||
|
self._popen.interrupt()
|
||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
'''
|
'''
|
||||||
Terminate process; sends SIGTERM signal or uses TerminateProcess()
|
Terminate process; sends SIGTERM signal or uses TerminateProcess()
|
||||||
|
|
|
@ -512,15 +512,20 @@ class _TestProcess(BaseTestCase):
|
||||||
def _sleep_some(cls):
|
def _sleep_some(cls):
|
||||||
time.sleep(100)
|
time.sleep(100)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _sleep_no_int_handler(cls):
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
cls._sleep_some()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _test_sleep(cls, delay):
|
def _test_sleep(cls, delay):
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
def _kill_process(self, meth):
|
def _kill_process(self, meth, target=None):
|
||||||
if self.TYPE == 'threads':
|
if self.TYPE == 'threads':
|
||||||
self.skipTest('test not appropriate for {}'.format(self.TYPE))
|
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.daemon = True
|
||||||
p.start()
|
p.start()
|
||||||
|
|
||||||
|
@ -567,6 +572,19 @@ class _TestProcess(BaseTestCase):
|
||||||
|
|
||||||
return p.exitcode
|
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):
|
def test_terminate(self):
|
||||||
exitcode = self._kill_process(multiprocessing.Process.terminate)
|
exitcode = self._kill_process(multiprocessing.Process.terminate)
|
||||||
self.assertEqual(exitcode, -signal.SIGTERM)
|
self.assertEqual(exitcode, -signal.SIGTERM)
|
||||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue