Skip to content

Commit 97bfe8d

Browse files
tzickelpitrou
authored andcommitted
bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450)
Fix a reference issue inside multiprocessing.Pool that caused the pool to remain alive if it was deleted without being closed or terminated explicitly.
1 parent 9012a0f commit 97bfe8d

File tree

3 files changed

+57
-24
lines changed

3 files changed

+57
-24
lines changed

Lib/multiprocessing/pool.py

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ class Pool(object):
149149
'''
150150
_wrap_exception = True
151151

152-
def Process(self, *args, **kwds):
153-
return self._ctx.Process(*args, **kwds)
152+
@staticmethod
153+
def Process(ctx, *args, **kwds):
154+
return ctx.Process(*args, **kwds)
154155

155156
def __init__(self, processes=None, initializer=None, initargs=(),
156157
maxtasksperchild=None, context=None):
@@ -177,13 +178,15 @@ def __init__(self, processes=None, initializer=None, initargs=(),
177178

178179
self._worker_handler = threading.Thread(
179180
target=Pool._handle_workers,
180-
args=(self, )
181+
args=(self._cache, self._taskqueue, self._ctx, self.Process,
182+
self._processes, self._pool, self._inqueue, self._outqueue,
183+
self._initializer, self._initargs, self._maxtasksperchild,
184+
self._wrap_exception)
181185
)
182186
self._worker_handler.daemon = True
183187
self._worker_handler._state = RUN
184188
self._worker_handler.start()
185189

186-
187190
self._task_handler = threading.Thread(
188191
target=Pool._handle_tasks,
189192
args=(self._taskqueue, self._quick_put, self._outqueue,
@@ -209,43 +212,62 @@ def __init__(self, processes=None, initializer=None, initargs=(),
209212
exitpriority=15
210213
)
211214

212-
def _join_exited_workers(self):
215+
@staticmethod
216+
def _join_exited_workers(pool):
213217
"""Cleanup after any worker processes which have exited due to reaching
214218
their specified lifetime. Returns True if any workers were cleaned up.
215219
"""
216220
cleaned = False
217-
for i in reversed(range(len(self._pool))):
218-
worker = self._pool[i]
221+
for i in reversed(range(len(pool))):
222+
worker = pool[i]
219223
if worker.exitcode is not None:
220224
# worker exited
221225
util.debug('cleaning up worker %d' % i)
222226
worker.join()
223227
cleaned = True
224-
del self._pool[i]
228+
del pool[i]
225229
return cleaned
226230

227231
def _repopulate_pool(self):
232+
return self._repopulate_pool_static(self._ctx, self.Process,
233+
self._processes,
234+
self._pool, self._inqueue,
235+
self._outqueue, self._initializer,
236+
self._initargs,
237+
self._maxtasksperchild,
238+
self._wrap_exception)
239+
240+
@staticmethod
241+
def _repopulate_pool_static(ctx, Process, processes, pool, inqueue,
242+
outqueue, initializer, initargs,
243+
maxtasksperchild, wrap_exception):
228244
"""Bring the number of pool processes up to the specified number,
229245
for use after reaping workers which have exited.
230246
"""
231-
for i in range(self._processes - len(self._pool)):
232-
w = self.Process(target=worker,
233-
args=(self._inqueue, self._outqueue,
234-
self._initializer,
235-
self._initargs, self._maxtasksperchild,
236-
self._wrap_exception)
237-
)
238-
self._pool.append(w)
247+
for i in range(processes - len(pool)):
248+
w = Process(ctx, target=worker,
249+
args=(inqueue, outqueue,
250+
initializer,
251+
initargs, maxtasksperchild,
252+
wrap_exception)
253+
)
254+
pool.append(w)
239255
w.name = w.name.replace('Process', 'PoolWorker')
240256
w.daemon = True
241257
w.start()
242258
util.debug('added worker')
243259

244-
def _maintain_pool(self):
260+
@staticmethod
261+
def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue,
262+
initializer, initargs, maxtasksperchild,
263+
wrap_exception):
245264
"""Clean up any exited workers and start replacements for them.
246265
"""
247-
if self._join_exited_workers():
248-
self._repopulate_pool()
266+
if Pool._join_exited_workers(pool):
267+
Pool._repopulate_pool_static(ctx, Process, processes, pool,
268+
inqueue, outqueue, initializer,
269+
initargs, maxtasksperchild,
270+
wrap_exception)
249271

250272
def _setup_queues(self):
251273
self._inqueue = self._ctx.SimpleQueue()
@@ -403,16 +425,20 @@ def _map_async(self, func, iterable, mapper, chunksize=None, callback=None,
403425
return result
404426

405427
@staticmethod
406-
def _handle_workers(pool):
428+
def _handle_workers(cache, taskqueue, ctx, Process, processes, pool,
429+
inqueue, outqueue, initializer, initargs,
430+
maxtasksperchild, wrap_exception):
407431
thread = threading.current_thread()
408432

409433
# Keep maintaining workers until the cache gets drained, unless the pool
410434
# is terminated.
411-
while thread._state == RUN or (pool._cache and thread._state != TERMINATE):
412-
pool._maintain_pool()
435+
while thread._state == RUN or (cache and thread._state != TERMINATE):
436+
Pool._maintain_pool(ctx, Process, processes, pool, inqueue,
437+
outqueue, initializer, initargs,
438+
maxtasksperchild, wrap_exception)
413439
time.sleep(0.1)
414440
# send sentinel to stop workers
415-
pool._taskqueue.put(None)
441+
taskqueue.put(None)
416442
util.debug('worker handler exiting')
417443

418444
@staticmethod
@@ -794,7 +820,7 @@ class ThreadPool(Pool):
794820
_wrap_exception = False
795821

796822
@staticmethod
797-
def Process(*args, **kwds):
823+
def Process(ctx, *args, **kwds):
798824
from .dummy import Process
799825
return Process(*args, **kwds)
800826

Lib/test/_test_multiprocessing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,6 +2549,12 @@ def test_release_task_refs(self):
25492549
# they were released too.
25502550
self.assertEqual(CountedObject.n_instances, 0)
25512551

2552+
def test_del_pool(self):
2553+
p = self.Pool(1)
2554+
wr = weakref.ref(p)
2555+
del p
2556+
gc.collect()
2557+
self.assertIsNone(wr())
25522558

25532559
def raising():
25542560
raise KeyError("key")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a reference issue inside multiprocessing.Pool that caused the pool to remain alive if it was deleted without being closed or terminated explicitly.

0 commit comments

Comments
 (0)