| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 862fd89 commit 77605fa
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -670,6 +670,25 @@ The :mod:`multiprocessing` package mostly replicates the API of the | |||
| 670 | 670 | ||
| 671 | 671 | .. versionadded:: 3.3 | |
| 672 | 672 | ||
| 673 | + .. method:: interrupt() | ||
| 674 | + | ||
| 675 | + Terminate the process. Works on POSIX using the :py:const:`~signal.SIGINT` signal. | ||
| 676 | + Behavior on Windows is undefined. | ||
| 677 | + | ||
| 678 | + By default, this terminates the child process by raising :exc:`KeyboardInterrupt`. | ||
| 679 | + This behavior can be altered by setting the respective signal handler in the child | ||
| 680 | + process :func:`signal.signal` for :py:const:`~signal.SIGINT`. | ||
| 681 | + | ||
| 682 | + Note: if the child process catches and discards :exc:`KeyboardInterrupt`, the | ||
| 683 | + process will not be terminated. | ||
| 684 | + | ||
| 685 | + Note: the default behavior will also set :attr:`exitcode` to ``1`` as if an | ||
| 686 | + uncaught exception was raised in the child process. To have a different | ||
| 687 | + :attr:`exitcode` you may simply catch :exc:`KeyboardInterrupt` and call | ||
| 688 | + ``exit(your_code)``. | ||
| 689 | + | ||
| 690 | + .. versionadded:: next | ||
| 691 | + | ||
| 673 | 692 | .. method:: terminate() | |
| 674 | 693 | ||
| 675 | 694 | Terminate the process. On POSIX this is done using the :py:const:`~signal.SIGTERM` signal; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -972,6 +972,10 @@ multiprocessing | |||
| 972 | 972 | The :func:`set` in :func:`multiprocessing.Manager` method is now available. | |
| 973 | 973 | (Contributed by Mingyu Park in :gh:`129949`.) | |
| 974 | 974 | ||
| 975 | + * Add :func:`multiprocessing.Process.interrupt` which terminates the child | ||
| 976 | + process by sending :py:const:`~signal.SIGINT`. This enables "finally" clauses | ||
| 977 | + and printing stack trace for the terminated process. | ||
| 978 | + (Contributed by Artem Pulkin in :gh:`131913`.) | ||
| 975 | 979 | ||
| 976 | 980 | operator | |
| 977 | 981 | -------- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -54,6 +54,9 @@ def _send_signal(self, sig): | |||
| 54 | 54 | if self.wait(timeout=0.1) is None: | |
| 55 | 55 | raise | |
| 56 | 56 | ||
| 57 | + def interrupt(self): | ||
| 58 | + self._send_signal(signal.SIGINT) | ||
| 59 | + | ||
| 57 | 60 | def terminate(self): | |
| 58 | 61 | self._send_signal(signal.SIGTERM) | |
| 59 | 62 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,6 +125,13 @@ def start(self): | |||
| 125 | 125 | del self._target, self._args, self._kwargs | |
| 126 | 126 | _children.add(self) | |
| 127 | 127 | ||
| 128 | + def interrupt(self): | ||
| 129 | + ''' | ||
| 130 | + Terminate process; sends SIGINT signal | ||
| 131 | + ''' | ||
| 132 | + self._check_closed() | ||
| 133 | + self._popen.interrupt() | ||
| 134 | + | ||
| 128 | 135 | def terminate(self): | |
| 129 | 136 | ''' | |
| 130 | 137 | Terminate process; sends SIGTERM signal or uses TerminateProcess() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -512,15 +512,20 @@ def _test_process_mainthread_native_id(cls, q): | |||
| 512 | 512 | def _sleep_some(cls): | |
| 513 | 513 | time.sleep(100) | |
| 514 | 514 | ||
| 515 | + @classmethod | ||
| 516 | + def _sleep_no_int_handler(cls): | ||
| 517 | + signal.signal(signal.SIGINT, signal.SIG_DFL) | ||
| 518 | + cls._sleep_some() | ||
| 519 | + | ||
| 515 | 520 | @classmethod | |
| 516 | 521 | def _test_sleep(cls, delay): | |
| 517 | 522 | time.sleep(delay) | |
| 518 | 523 | ||
| 519 | - def _kill_process(self, meth): | ||
| 524 | + def _kill_process(self, meth, target=None): | ||
| 520 | 525 | if self.TYPE == 'threads': | |
| 521 | 526 | self.skipTest('test not appropriate for {}'.format(self.TYPE)) | |
| 522 | 527 | ||
| 523 | - p = self.Process(target=self._sleep_some) | ||
| 528 | + p = self.Process(target=target or self._sleep_some) | ||
| 524 | 529 | p.daemon = True | |
| 525 | 530 | p.start() | |
| 526 | 531 | ||
@@ -567,6 +572,19 @@ def handler(*args): | |||
| 567 | 572 | ||
| 568 | 573 | return p.exitcode | |
| 569 | 574 | ||
| 575 | + @unittest.skipIf(os.name == 'nt', "POSIX only") | ||
| 576 | + def test_interrupt(self): | ||
| 577 | + exitcode = self._kill_process(multiprocessing.Process.interrupt) | ||
| 578 | + self.assertEqual(exitcode, 1) | ||
| 579 | + # exit code 1 is hard-coded for uncaught exceptions | ||
| 580 | + # (KeyboardInterrupt in this case) | ||
| 581 | + # in multiprocessing.BaseProcess._bootstrap | ||
| 582 | + | ||
| 583 | + @unittest.skipIf(os.name == 'nt', "POSIX only") | ||
| 584 | + def test_interrupt_no_handler(self): | ||
| 585 | + exitcode = self._kill_process(multiprocessing.Process.interrupt, target=self._sleep_no_int_handler) | ||
| 586 | + self.assertEqual(exitcode, -signal.SIGINT) | ||
| 587 | + | ||
| 570 | 588 | def test_terminate(self): | |
| 571 | 589 | exitcode = self._kill_process(multiprocessing.Process.terminate) | |
| 572 | 590 | self.assertEqual(exitcode, -signal.SIGTERM) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 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. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments