Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-128041: Add a terminate_workers method to ProcessPoolExecutor#128043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
47b162a6ef883361c9b143bf54644b285b8b4939fdba6a4c05d58e500db381b7ae1685f7ad96c2c0b578a878221794ee2564693a7926dff16d77c104429b2fb8d6e5ff9a77147cfa42e2b31fabad15ee50f57912c16fde51bedb28f1b0cf6cc5f359b3cc8a2dbf9d321e16da652a53267f09586d5f75780e42eca3f55347File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,17 @@ | ||
| import os | ||
| import queue | ||
| import signal | ||
| import sys | ||
| import threading | ||
| import time | ||
| import unittest | ||
| import unittest.mock | ||
| from concurrent import futures | ||
| from concurrent.futures.process import BrokenProcessPool | ||
| from test import support | ||
| from test.support import hashlib_helper | ||
| from test.test_importlib.metadata.fixtures import parameterize | ||
csm10495 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| from .executor import ExecutorTest, mul | ||
| from .util import ( | ||
| @@ -22,6 +26,19 @@ def __init__(self, mgr): | ||
| def __del__(self): | ||
| self.event.set() | ||
| TERMINATE_WORKERS = futures.ProcessPoolExecutor.terminate_workers.__name__ | ||
| KILL_WORKERS = futures.ProcessPoolExecutor.kill_workers.__name__ | ||
| FORCE_SHUTDOWN_PARAMS = [ | ||
| dict(function_name=TERMINATE_WORKERS), | ||
| dict(function_name=KILL_WORKERS), | ||
| ] | ||
| def _put_sleep_put(queue): | ||
| """ Used as part of test_terminate_workers """ | ||
| queue.put('started') | ||
| time.sleep(2) | ||
| queue.put('finished') | ||
| class ProcessPoolExecutorTest(ExecutorTest): | ||
| @@ -218,6 +235,86 @@ def mock_start_new_thread(func, *args, **kwargs): | ||
| list(executor.map(mul, [(2, 3)] * 10)) | ||
| executor.shutdown() | ||
| def test_terminate_workers(self): | ||
| mock_fn = unittest.mock.Mock() | ||
| with self.executor_type(max_workers=1) as executor: | ||
| executor._force_shutdown = mock_fn | ||
| executor.terminate_workers() | ||
| mock_fn.assert_called_once_with(operation=futures.process._TERMINATE) | ||
| def test_kill_workers(self): | ||
| mock_fn = unittest.mock.Mock() | ||
| with self.executor_type(max_workers=1) as executor: | ||
| executor._force_shutdown = mock_fn | ||
| executor.kill_workers() | ||
| mock_fn.assert_called_once_with(operation=futures.process._KILL) | ||
| def test_force_shutdown_workers_invalid_op(self): | ||
| with self.executor_type(max_workers=1) as executor: | ||
| self.assertRaises(ValueError, | ||
| executor._force_shutdown, | ||
| operation='invalid operation'), | ||
| @parameterize(*FORCE_SHUTDOWN_PARAMS) | ||
| def test_force_shutdown_workers(self, function_name): | ||
| manager = self.get_context().Manager() | ||
| q = manager.Queue() | ||
| with self.executor_type(max_workers=1) as executor: | ||
| executor.submit(_put_sleep_put, q) | ||
| # We should get started, but not finished since we'll terminate the | ||
| # workers just after | ||
| self.assertEqual(q.get(timeout=5), 'started') | ||
| worker_process = list(executor._processes.values())[0] | ||
| getattr(executor, function_name)() | ||
| worker_process.join() | ||
| if function_name == TERMINATE_WORKERS or \ | ||
| sys.platform == 'win32': | ||
| # On windows, kill and terminate both send SIGTERM | ||
| self.assertEqual(worker_process.exitcode, -signal.SIGTERM) | ||
| elif function_name == KILL_WORKERS: | ||
| self.assertEqual(worker_process.exitcode, -signal.SIGKILL) | ||
| else: | ||
| self.fail(f"Unknown operation: {function_name}") | ||
| self.assertRaises(queue.Empty, q.get, timeout=1) | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do suspect we may see this come up as occasionally flaky in buildbot or CI systems as it is depend on the timing of the sleeps and kills which really can't be guaranteed on a loaded system. if so, _put_sleep_put can have its sleep increased. lets see how it goes first. | ||
| @parameterize(*FORCE_SHUTDOWN_PARAMS) | ||
| def test_force_shutdown_workers_dead_workers(self, function_name): | ||
| with self.executor_type(max_workers=1) as executor: | ||
| future = executor.submit(os._exit, 1) | ||
| self.assertRaises(BrokenProcessPool, future.result) | ||
| # even though the pool is broken, this shouldn't raise | ||
| getattr(executor, function_name)() | ||
| @parameterize(*FORCE_SHUTDOWN_PARAMS) | ||
| def test_force_shutdown_workers_not_started_yet(self, function_name): | ||
| ctx = self.get_context() | ||
| with unittest.mock.patch.object(ctx, 'Process') as mock_process: | ||
| with self.executor_type(max_workers=1, mp_context=ctx) as executor: | ||
| # The worker has not been started yet, terminate/kill_workers | ||
| # should basically no-op | ||
| getattr(executor, function_name)() | ||
| mock_process.return_value.kill.assert_not_called() | ||
| mock_process.return_value.terminate.assert_not_called() | ||
| @parameterize(*FORCE_SHUTDOWN_PARAMS) | ||
| def test_force_shutdown_workers_stops_pool(self, function_name): | ||
| with self.executor_type(max_workers=1) as executor: | ||
| task = executor.submit(time.sleep, 0) | ||
| self.assertIsNone(task.result()) | ||
| getattr(executor, function_name)() | ||
| self.assertRaises(RuntimeError, executor.submit, time.sleep, 0) | ||
| create_executor_tests(globals(), ProcessPoolExecutorTest, | ||
| executor_mixins=(ProcessPoolForkMixin, | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Add :meth:`concurrent.futures.ProcessPoolExecutor.terminate_workers` and | ||
| :meth:`concurrent.futures.ProcessPoolExecutor.kill_workers` as | ||
| ways to terminate or kill all living worker processes in the given pool. | ||
| (Contributed by Charles Machalow in :gh:`128043`.) |
Uh oh!
There was an error while loading. Please reload this page.