@@ -42,7 +42,8 @@ def mapstar(args):
4242# Code run by worker processes
4343#
4444
45- def worker (inqueue , outqueue , initializer = None , initargs = ()):
45+ def worker (inqueue , outqueue , initializer = None , initargs = (), maxtasks = None ):
46+ assert maxtasks is None or (type (maxtasks ) == int and maxtasks > 0 )
4647 put = outqueue .put
4748 get = inqueue .get
4849 if hasattr (inqueue , '_writer' ):
@@ -52,7 +53,8 @@ def worker(inqueue, outqueue, initializer=None, initargs=()):
5253 if initializer is not None :
5354 initializer (* initargs )
5455
55- while 1 :
56+ completed = 0
57+ while maxtasks is None or (maxtasks and completed < maxtasks ):
5658 try :
5759 task = get ()
5860 except (EOFError , IOError ):
@@ -69,6 +71,8 @@ def worker(inqueue, outqueue, initializer=None, initargs=()):
6971 except Exception as e :
7072 result = (False , e )
7173 put ((job , i , result ))
74+ completed += 1
75+ debug ('worker exiting after %d tasks' % completed )
7276
7377#
7478# Class representing a process pool
@@ -80,11 +84,15 @@ class Pool(object):
8084 '''
8185 Process = Process
8286
83- def __init__ (self , processes = None , initializer = None , initargs = ()):
87+ def __init__ (self , processes = None , initializer = None , initargs = (),
88+ maxtasksperchild = None ):
8489 self ._setup_queues ()
8590 self ._taskqueue = queue .Queue ()
8691 self ._cache = {}
8792 self ._state = RUN
93+ self ._maxtasksperchild = maxtasksperchild
94+ self ._initializer = initializer
95+ self ._initargs = initargs
8896
8997 if processes is None :
9098 try :
@@ -95,16 +103,18 @@ def __init__(self, processes=None, initializer=None, initargs=()):
95103 if initializer is not None and not hasattr (initializer , '__call__' ):
96104 raise TypeError ('initializer must be a callable' )
97105
106+ self ._processes = processes
98107 self ._pool = []
99- for i in range (processes ):
100- w = self .Process (
101- target = worker ,
102- args = (self ._inqueue , self ._outqueue , initializer , initargs )
103- )
104- self ._pool .append (w )
105- w .name = w .name .replace ('Process' , 'PoolWorker' )
106- w .daemon = True
107- w .start ()
108+ self ._repopulate_pool ()
109+
110+ self ._worker_handler = threading .Thread (
111+ target = Pool ._handle_workers ,
112+ args = (self , )
113+ )
114+ self ._worker_handler .daemon = True
115+ self ._worker_handler ._state = RUN
116+ self ._worker_handler .start ()
117+
108118
109119 self ._task_handler = threading .Thread (
110120 target = Pool ._handle_tasks ,
@@ -125,10 +135,48 @@ def __init__(self, processes=None, initializer=None, initargs=()):
125135 self ._terminate = Finalize (
126136 self , self ._terminate_pool ,
127137 args = (self ._taskqueue , self ._inqueue , self ._outqueue , self ._pool ,
128- self ._task_handler , self ._result_handler , self ._cache ),
138+ self ._worker_handler , self ._task_handler ,
139+ self ._result_handler , self ._cache ),
129140 exitpriority = 15
130141 )
131142
143+ def _join_exited_workers (self ):
144+ """Cleanup after any worker processes which have exited due to reaching
145+ their specified lifetime. Returns True if any workers were cleaned up.
146+ """
147+ cleaned = False
148+ for i in reversed (range (len (self ._pool ))):
149+ worker = self ._pool [i ]
150+ if worker .exitcode is not None :
151+ # worker exited
152+ debug ('cleaning up worker %d' % i )
153+ worker .join ()
154+ cleaned = True
155+ del self ._pool [i ]
156+ return cleaned
157+
158+ def _repopulate_pool (self ):
159+ """Bring the number of pool processes up to the specified number,
160+ for use after reaping workers which have exited.
161+ """
162+ for i in range (self ._processes - len (self ._pool )):
163+ w = self .Process (target = worker ,
164+ args = (self ._inqueue , self ._outqueue ,
165+ self ._initializer ,
166+ self ._initargs , self ._maxtasksperchild )
167+ )
168+ self ._pool .append (w )
169+ w .name = w .name .replace ('Process' , 'PoolWorker' )
170+ w .daemon = True
171+ w .start ()
172+ debug ('added worker' )
173+
174+ def _maintain_pool (self ):
175+ """Clean up any exited workers and start replacements for them.
176+ """
177+ if self ._join_exited_workers ():
178+ self ._repopulate_pool ()
179+
132180 def _setup_queues (self ):
133181 from .queues import SimpleQueue
134182 self ._inqueue = SimpleQueue ()
@@ -217,6 +265,13 @@ def map_async(self, func, iterable, chunksize=None, callback=None):
217265 for i , x in enumerate (task_batches )), None ))
218266 return result
219267
268+ @staticmethod
269+ def _handle_workers (pool ):
270+ while pool ._worker_handler ._state == RUN and pool ._state == RUN :
271+ pool ._maintain_pool ()
272+ time .sleep (0.1 )
273+ debug ('worker handler exiting' )
274+
220275 @staticmethod
221276 def _handle_tasks (taskqueue , put , outqueue , pool ):
222277 thread = threading .current_thread ()
@@ -332,16 +387,19 @@ def close(self):
332387 debug ('closing pool' )
333388 if self ._state == RUN :
334389 self ._state = CLOSE
390+ self ._worker_handler ._state = CLOSE
335391 self ._taskqueue .put (None )
336392
337393 def terminate (self ):
338394 debug ('terminating pool' )
339395 self ._state = TERMINATE
396+ self ._worker_handler ._state = TERMINATE
340397 self ._terminate ()
341398
342399 def join (self ):
343400 debug ('joining pool' )
344401 assert self ._state in (CLOSE , TERMINATE )
402+ self ._worker_handler .join ()
345403 self ._task_handler .join ()
346404 self ._result_handler .join ()
347405 for p in self ._pool :
@@ -358,10 +416,11 @@ def _help_stuff_finish(inqueue, task_handler, size):
358416
359417 @classmethod
360418 def _terminate_pool (cls , taskqueue , inqueue , outqueue , pool ,
361- task_handler , result_handler , cache ):
419+ worker_handler , task_handler , result_handler , cache ):
362420 # this is guaranteed to only be called once
363421 debug ('finalizing pool' )
364422
423+ worker_handler ._state = TERMINATE
365424 task_handler ._state = TERMINATE
366425 taskqueue .put (None ) # sentinel
367426
@@ -373,10 +432,12 @@ def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool,
373432 result_handler ._state = TERMINATE
374433 outqueue .put (None ) # sentinel
375434
435+ # Terminate workers which haven't already finished.
376436 if pool and hasattr (pool [0 ], 'terminate' ):
377437 debug ('terminating workers' )
378438 for p in pool :
379- p .terminate ()
439+ if p .exitcode is None :
440+ p .terminate ()
380441
381442 debug ('joining task handler' )
382443 task_handler .join (1e100 )
@@ -388,6 +449,11 @@ def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool,
388449 debug ('joining pool workers' )
389450 for p in pool :
390451 p .join ()
452+ for w in pool :
453+ if w .exitcode is None :
454+ # worker has not yet exited
455+ debug ('cleaning up worker %d' % w .pid )
456+ w .join ()
391457
392458#
393459# Class whose instances are returned by `Pool.apply_async()`
0 commit comments