@@ -141,10 +141,11 @@ def __init__(self, future, fn, args, kwargs):
141
141
self .kwargs = kwargs
142
142
143
143
class _ResultItem (object ):
144
- def __init__ (self , work_id , exception = None , result = None ):
144
+ def __init__ (self , work_id , exception = None , result = None , pid = None ):
145
145
self .work_id = work_id
146
146
self .exception = exception
147
147
self .result = result
148
+ self .pid = pid
148
149
149
150
class _CallItem (object ):
150
151
def __init__ (self , work_id , fn , args , kwargs ):
@@ -201,14 +202,14 @@ def _process_chunk(fn, chunk):
201
202
return [fn (* args ) for args in chunk ]
202
203
203
204
204
- def _sendback_result (result_queue , work_id , result = None , exception = None ):
205
+ def _sendback_result (result_queue , work_id , result = None , exception = None , pid = None ):
205
206
"""Safely send back the given result or exception"""
206
207
try :
207
208
result_queue .put (_ResultItem (work_id , result = result ,
208
- exception = exception ))
209
+ exception = exception , pid = pid ))
209
210
except BaseException as e :
210
211
exc = _ExceptionWithTraceback (e , e .__traceback__ )
211
- result_queue .put (_ResultItem (work_id , exception = exc ))
212
+ result_queue .put (_ResultItem (work_id , exception = exc , pid = pid ))
212
213
213
214
214
215
def _process_worker (call_queue , result_queue , initializer , initargs , max_tasks = None ):
@@ -232,31 +233,35 @@ def _process_worker(call_queue, result_queue, initializer, initargs, max_tasks=N
232
233
# The parent will notice that the process stopped and
233
234
# mark the pool broken
234
235
return
235
- completed_count = 0
236
- while max_tasks is None or (max_tasks > completed_count ):
236
+ num_tasks = 0
237
+ exit_pid = None
238
+ while True :
237
239
call_item = call_queue .get (block = True )
238
240
if call_item is None :
239
241
# Wake up queue management thread
240
242
result_queue .put (os .getpid ())
241
243
return
244
+
245
+ if max_tasks is not None :
246
+ num_tasks += 1
247
+ if num_tasks >= max_tasks :
248
+ exit_pid = os .getpid ()
249
+
242
250
try :
243
251
r = call_item .fn (* call_item .args , ** call_item .kwargs )
244
252
except BaseException as e :
245
253
exc = _ExceptionWithTraceback (e , e .__traceback__ )
246
- _sendback_result (result_queue , call_item .work_id , exception = exc )
254
+ _sendback_result (result_queue , call_item .work_id , exception = exc , pid = exit_pid )
247
255
else :
248
- _sendback_result (result_queue , call_item .work_id , result = r )
256
+ _sendback_result (result_queue , call_item .work_id , result = r , pid = exit_pid )
249
257
del r
250
- finally :
251
- completed_count += 1
252
258
253
259
# Liberate the resource as soon as possible, to avoid holding onto
254
260
# open files or shared memory that is not needed anymore
255
261
del call_item
256
262
257
- # Reached the maximum number of tasks this process can work on
258
- # notify the manager that this process is exiting cleanly
259
- result_queue .put (os .getpid ())
263
+ if exit_pid is not None :
264
+ return
260
265
261
266
262
267
class _ExecutorManagerThread (threading .Thread ):
@@ -331,15 +336,21 @@ def run(self):
331
336
return
332
337
if result_item is not None :
333
338
self .process_result_item (result_item )
339
+
340
+ process_exited = result_item .pid is not None
341
+ if process_exited :
342
+ self .processes .pop (result_item .pid )
343
+
334
344
# Delete reference to result_item to avoid keeping references
335
345
# while waiting on new results.
336
346
del result_item
337
347
338
- # attempt to increment idle process count
339
- executor = self .executor_reference ()
340
- if executor is not None :
341
- executor ._idle_worker_semaphore .release ()
342
- del executor
348
+ if executor := self .executor_reference ():
349
+ if process_exited :
350
+ executor ._adjust_process_count ()
351
+ else :
352
+ executor ._idle_worker_semaphore .release ()
353
+ del executor
343
354
344
355
if self .is_shutting_down ():
345
356
self .flag_executor_shutting_down ()
@@ -411,14 +422,12 @@ def process_result_item(self, result_item):
411
422
if isinstance (result_item , int ):
412
423
# Clean shutdown of a worker using its PID
413
424
# (avoids marking the executor broken)
425
+ assert self .is_shutting_down ()
414
426
p = self .processes .pop (result_item )
415
427
p .join ()
416
- if self .is_shutting_down () and not self .pending_work_items :
417
- if not self .processes :
418
- self .join_executor_internals ()
419
- else :
420
- if executor := self .executor_reference ():
421
- executor ._add_process_to_pool ()
428
+ if not self .processes :
429
+ self .join_executor_internals ()
430
+ return
422
431
else :
423
432
# Received a _ResultItem so mark the future as completed.
424
433
work_item = self .pending_work_items .pop (result_item .work_id , None )
@@ -698,18 +707,15 @@ def _adjust_process_count(self):
698
707
699
708
process_count = len (self ._processes )
700
709
if process_count < self ._max_workers :
701
- self ._add_process_to_pool ()
702
-
703
- def _add_process_to_pool (self ):
704
- p = self ._mp_context .Process (
705
- target = _process_worker ,
706
- args = (self ._call_queue ,
707
- self ._result_queue ,
708
- self ._initializer ,
709
- self ._initargs ,
710
- self ._max_tasks_per_child ))
711
- p .start ()
712
- self ._processes [p .pid ] = p
710
+ p = self ._mp_context .Process (
711
+ target = _process_worker ,
712
+ args = (self ._call_queue ,
713
+ self ._result_queue ,
714
+ self ._initializer ,
715
+ self ._initargs ,
716
+ self ._max_tasks_per_child ))
717
+ p .start ()
718
+ self ._processes [p .pid ] = p
713
719
714
720
def submit (self , fn , / , * args , ** kwargs ):
715
721
with self ._shutdown_lock :
0 commit comments