Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 63ff413

Browse files
authored
bpo-21423: Add an initializer argument to {Process,Thread}PoolExecutor (#4241)
* bpo-21423: Add an initializer argument to {Process,Thread}PoolExecutor * Fix docstring
1 parent b838cc3 commit 63ff413

7 files changed

Lines changed: 246 additions & 81 deletions

File tree

Doc/library/concurrent.futures.rst

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,17 @@ And::
124124
executor.submit(wait_on_future)
125125

126126

127-
.. class:: ThreadPoolExecutor(max_workers=None, thread_name_prefix='')
127+
.. class:: ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
128128

129129
An :class:`Executor` subclass that uses a pool of at most *max_workers*
130130
threads to execute calls asynchronously.
131131

132+
*initializer* is an optional callable that is called at the start of
133+
each worker thread; *initargs* is a tuple of arguments passed to the
134+
initializer. Should *initializer* raise an exception, all currently
135+
pending jobs will raise a :exc:`~concurrent.futures.thread.BrokenThreadPool`,
136+
as well any attempt to submit more jobs to the pool.
137+
132138
.. versionchanged:: 3.5
133139
If *max_workers* is ``None`` or
134140
not given, it will default to the number of processors on the machine,
@@ -142,6 +148,10 @@ And::
142148
control the threading.Thread names for worker threads created by
143149
the pool for easier debugging.
144150

151+
.. versionchanged:: 3.7
152+
Added the *initializer* and *initargs* arguments.
153+
154+
145155
.. _threadpoolexecutor-example:
146156

147157
ThreadPoolExecutor Example
@@ -191,7 +201,7 @@ that :class:`ProcessPoolExecutor` will not work in the interactive interpreter.
191201
Calling :class:`Executor` or :class:`Future` methods from a callable submitted
192202
to a :class:`ProcessPoolExecutor` will result in deadlock.
193203

194-
.. class:: ProcessPoolExecutor(max_workers=None, mp_context=None)
204+
.. class:: ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=())
195205

196206
An :class:`Executor` subclass that executes calls asynchronously using a pool
197207
of at most *max_workers* processes. If *max_workers* is ``None`` or not
@@ -202,6 +212,12 @@ to a :class:`ProcessPoolExecutor` will result in deadlock.
202212
launch the workers. If *mp_context* is ``None`` or not given, the default
203213
multiprocessing context is used.
204214

215+
*initializer* is an optional callable that is called at the start of
216+
each worker process; *initargs* is a tuple of arguments passed to the
217+
initializer. Should *initializer* raise an exception, all currently
218+
pending jobs will raise a :exc:`~concurrent.futures.thread.BrokenThreadPool`,
219+
as well any attempt to submit more jobs to the pool.
220+
205221
.. versionchanged:: 3.3
206222
When one of the worker processes terminates abruptly, a
207223
:exc:`BrokenProcessPool` error is now raised. Previously, behaviour
@@ -212,6 +228,8 @@ to a :class:`ProcessPoolExecutor` will result in deadlock.
212228
The *mp_context* argument was added to allow users to control the
213229
start_method for worker processes created by the pool.
214230

231+
Added the *initializer* and *initargs* arguments.
232+
215233

216234
.. _processpoolexecutor-example:
217235

@@ -432,13 +450,31 @@ Exception classes
432450

433451
Raised when a future operation exceeds the given timeout.
434452

453+
.. exception:: BrokenExecutor
454+
455+
Derived from :exc:`RuntimeError`, this exception class is raised
456+
when an executor is broken for some reason, and cannot be used
457+
to submit or execute new tasks.
458+
459+
.. versionadded:: 3.7
460+
461+
.. currentmodule:: concurrent.futures.thread
462+
463+
.. exception:: BrokenThreadPool
464+
465+
Derived from :exc:`~concurrent.futures.BrokenExecutor`, this exception
466+
class is raised when one of the workers of a :class:`ThreadPoolExecutor`
467+
has failed initializing.
468+
469+
.. versionadded:: 3.7
470+
435471
.. currentmodule:: concurrent.futures.process
436472

437473
.. exception:: BrokenProcessPool
438474

439-
Derived from :exc:`RuntimeError`, this exception class is raised when
440-
one of the workers of a :class:`ProcessPoolExecutor` has terminated
441-
in a non-clean fashion (for example, if it was killed from the outside).
475+
Derived from :exc:`~concurrent.futures.BrokenExecutor` (formerly
476+
:exc:`RuntimeError`), this exception class is raised when one of the
477+
workers of a :class:`ProcessPoolExecutor` has terminated in a non-clean
478+
fashion (for example, if it was killed from the outside).
442479

443480
.. versionadded:: 3.3
444-

Lib/concurrent/futures/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ALL_COMPLETED,
1111
CancelledError,
1212
TimeoutError,
13+
BrokenExecutor,
1314
Future,
1415
Executor,
1516
wait,

Lib/concurrent/futures/_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,9 @@ def __enter__(self):
610610
def __exit__(self, exc_type, exc_val, exc_tb):
611611
self.shutdown(wait=True)
612612
return False
613+
614+
615+
class BrokenExecutor(RuntimeError):
616+
"""
617+
Raised when a executor has become non-functional after a severe failure.
618+
"""

Lib/concurrent/futures/process.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def __init__(self, work_id, fn, args, kwargs):
131131
self.args = args
132132
self.kwargs = kwargs
133133

134+
134135
def _get_chunks(*iterables, chunksize):
135136
""" Iterates over zip()ed iterables in chunks. """
136137
it = zip(*iterables)
@@ -151,7 +152,7 @@ def _process_chunk(fn, chunk):
151152
"""
152153
return [fn(*args) for args in chunk]
153154

154-
def _process_worker(call_queue, result_queue):
155+
def _process_worker(call_queue, result_queue, initializer, initargs):
155156
"""Evaluates calls from call_queue and places the results in result_queue.
156157
157158
This worker is run in a separate process.
@@ -161,7 +162,17 @@ def _process_worker(call_queue, result_queue):
161162
evaluated by the worker.
162163
result_queue: A ctx.Queue of _ResultItems that will written
163164
to by the worker.
165+
initializer: A callable initializer, or None
166+
initargs: A tuple of args for the initializer
164167
"""
168+
if initializer is not None:
169+
try:
170+
initializer(*initargs)
171+
except BaseException:
172+
_base.LOGGER.critical('Exception in initializer:', exc_info=True)
173+
# The parent will notice that the process stopped and
174+
# mark the pool broken
175+
return
165176
while True:
166177
call_item = call_queue.get(block=True)
167178
if call_item is None:
@@ -277,7 +288,9 @@ def shutdown_worker():
277288
# Mark the process pool broken so that submits fail right now.
278289
executor = executor_reference()
279290
if executor is not None:
280-
executor._broken = True
291+
executor._broken = ('A child process terminated '
292+
'abruptly, the process pool is not '
293+
'usable anymore')
281294
executor._shutdown_thread = True
282295
executor = None
283296
# All futures in flight must be marked failed
@@ -372,15 +385,16 @@ def _chain_from_iterable_of_lists(iterable):
372385
yield element.pop()
373386

374387

375-
class BrokenProcessPool(RuntimeError):
388+
class BrokenProcessPool(_base.BrokenExecutor):
376389
"""
377390
Raised when a process in a ProcessPoolExecutor terminated abruptly
378391
while a future was in the running state.
379392
"""
380393

381394

382395
class ProcessPoolExecutor(_base.Executor):
383-
def __init__(self, max_workers=None, mp_context=None):
396+
def __init__(self, max_workers=None, mp_context=None,
397+
initializer=None, initargs=()):
384398
"""Initializes a new ProcessPoolExecutor instance.
385399
386400
Args:
@@ -389,6 +403,8 @@ def __init__(self, max_workers=None, mp_context=None):
389403
worker processes will be created as the machine has processors.
390404
mp_context: A multiprocessing context to launch the workers. This
391405
object should provide SimpleQueue, Queue and Process.
406+
initializer: An callable used to initialize worker processes.
407+
initargs: A tuple of arguments to pass to the initializer.
392408
"""
393409
_check_system_limits()
394410

@@ -403,6 +419,11 @@ def __init__(self, max_workers=None, mp_context=None):
403419
mp_context = mp.get_context()
404420
self._mp_context = mp_context
405421

422+
if initializer is not None and not callable(initializer):
423+
raise TypeError("initializer must be a callable")
424+
self._initializer = initializer
425+
self._initargs = initargs
426+
406427
# Make the call queue slightly larger than the number of processes to
407428
# prevent the worker processes from idling. But don't make it too big
408429
# because futures in the call queue cannot be cancelled.
@@ -450,15 +471,16 @@ def _adjust_process_count(self):
450471
p = self._mp_context.Process(
451472
target=_process_worker,
452473
args=(self._call_queue,
453-
self._result_queue))
474+
self._result_queue,
475+
self._initializer,
476+
self._initargs))
454477
p.start()
455478
self._processes[p.pid] = p
456479

457480
def submit(self, fn, *args, **kwargs):
458481
with self._shutdown_lock:
459482
if self._broken:
460-
raise BrokenProcessPool('A child process terminated '
461-
'abruptly, the process pool is not usable anymore')
483+
raise BrokenProcessPool(self._broken)
462484
if self._shutdown_thread:
463485
raise RuntimeError('cannot schedule new futures after shutdown')
464486

Lib/concurrent/futures/thread.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def _python_exit():
4141

4242
atexit.register(_python_exit)
4343

44+
4445
class _WorkItem(object):
4546
def __init__(self, future, fn, args, kwargs):
4647
self.future = future
@@ -61,7 +62,17 @@ def run(self):
6162
else:
6263
self.future.set_result(result)
6364

64-
def _worker(executor_reference, work_queue):
65+
66+
def _worker(executor_reference, work_queue, initializer, initargs):
67+
if initializer is not None:
68+
try:
69+
initializer(*initargs)
70+
except BaseException:
71+
_base.LOGGER.critical('Exception in initializer:', exc_info=True)
72+
executor = executor_reference()
73+
if executor is not None:
74+
executor._initializer_failed()
75+
return
6576
try:
6677
while True:
6778
work_item = work_queue.get(block=True)
@@ -83,18 +94,28 @@ def _worker(executor_reference, work_queue):
8394
except BaseException:
8495
_base.LOGGER.critical('Exception in worker', exc_info=True)
8596

97+
98+
class BrokenThreadPool(_base.BrokenExecutor):
99+
"""
100+
Raised when a worker thread in a ThreadPoolExecutor failed initializing.
101+
"""
102+
103+
86104
class ThreadPoolExecutor(_base.Executor):
87105

88106
# Used to assign unique thread names when thread_name_prefix is not supplied.
89107
_counter = itertools.count().__next__
90108

91-
def __init__(self, max_workers=None, thread_name_prefix=''):
109+
def __init__(self, max_workers=None, thread_name_prefix='',
110+
initializer=None, initargs=()):
92111
"""Initializes a new ThreadPoolExecutor instance.
93112
94113
Args:
95114
max_workers: The maximum number of threads that can be used to
96115
execute the given calls.
97116
thread_name_prefix: An optional name prefix to give our threads.
117+
initializer: An callable used to initialize worker threads.
118+
initargs: A tuple of arguments to pass to the initializer.
98119
"""
99120
if max_workers is None:
100121
# Use this number because ThreadPoolExecutor is often
@@ -103,16 +124,25 @@ def __init__(self, max_workers=None, thread_name_prefix=''):
103124
if max_workers <= 0:
104125
raise ValueError("max_workers must be greater than 0")
105126

127+
if initializer is not None and not callable(initializer):
128+
raise TypeError("initializer must be a callable")
129+
106130
self._max_workers = max_workers
107131
self._work_queue = queue.Queue()
108132
self._threads = set()
133+
self._broken = False
109134
self._shutdown = False
110135
self._shutdown_lock = threading.Lock()
111136
self._thread_name_prefix = (thread_name_prefix or
112137
("ThreadPoolExecutor-%d" % self._counter()))
138+
self._initializer = initializer
139+
self._initargs = initargs
113140

114141
def submit(self, fn, *args, **kwargs):
115142
with self._shutdown_lock:
143+
if self._broken:
144+
raise BrokenThreadPool(self._broken)
145+
116146
if self._shutdown:
117147
raise RuntimeError('cannot schedule new futures after shutdown')
118148

@@ -137,12 +167,27 @@ def weakref_cb(_, q=self._work_queue):
137167
num_threads)
138168
t = threading.Thread(name=thread_name, target=_worker,
139169
args=(weakref.ref(self, weakref_cb),
140-
self._work_queue))
170+
self._work_queue,
171+
self._initializer,
172+
self._initargs))
141173
t.daemon = True
142174
t.start()
143175
self._threads.add(t)
144176
_threads_queues[t] = self._work_queue
145177

178+
def _initializer_failed(self):
179+
with self._shutdown_lock:
180+
self._broken = ('A thread initializer failed, the thread pool '
181+
'is not usable anymore')
182+
# Drain work queue and mark pending futures failed
183+
while True:
184+
try:
185+
work_item = self._work_queue.get_nowait()
186+
except queue.Empty:
187+
break
188+
if work_item is not None:
189+
work_item.future.set_exception(BrokenThreadPool(self._broken))
190+
146191
def shutdown(self, wait=True):
147192
with self._shutdown_lock:
148193
self._shutdown = True

0 commit comments

Comments
 (0)