diff -r fca745bc70be Lib/multiprocessing/heap.py --- a/Lib/multiprocessing/heap.py Sat Jun 25 16:31:06 2011 +0200 +++ b/Lib/multiprocessing/heap.py Sat Jun 25 18:54:15 2011 +0200 @@ -39,6 +39,7 @@ import sys import threading import itertools +import gc import _multiprocessing from multiprocessing.util import Finalize, info @@ -101,6 +102,7 @@ self._stop_to_block = {} self._allocated_blocks = set() self._arenas = [] + self._is_gc_enabled = False @staticmethod def _roundup(n, alignment): @@ -175,22 +177,41 @@ return start, stop + def _enter(self): + # Enter a critical section. + # In issue #12352, free() was called asynchronously by the garbage + # collector (see BufferWrapper's Finalizer) while the thread was inside + # malloc(): since _lock was already held, _lock_.acquire() deadlocked: + # to avoid that, the GC is disabled in the critical section, and + # re-enabled - if necessary - on exit. Of course, if another thread + # re-enables the GC while the current thread is in the middle of a + # critical section, things will break... + self._is_gc_enabled = gc.isenabled() + gc.disable() + self._lock.acquire() + + def _leave(self): + # Leave a critical section. + self._lock.release() + if self._is_gc_enabled: + gc.enable() + def free(self, block): # free a block returned by malloc() assert os.getpid() == self._lastpid - self._lock.acquire() + self._enter() try: self._allocated_blocks.remove(block) self._free(block) finally: - self._lock.release() + self._leave() def malloc(self, size): # return a block of right size (possibly rounded up) assert 0 <= size < sys.maxsize if os.getpid() != self._lastpid: self.__init__() # reinitialize after fork - self._lock.acquire() + self._enter() try: size = self._roundup(max(size,1), self._alignment) (arena, start, stop) = self._malloc(size) @@ -201,7 +222,7 @@ self._allocated_blocks.add(block) return block finally: - self._lock.release() + self._leave() # # Class representing a chunk of an mmap -- can be inherited diff -r fca745bc70be Lib/test/test_multiprocessing.py --- a/Lib/test/test_multiprocessing.py Sat Jun 25 16:31:06 2011 +0200 +++ b/Lib/test/test_multiprocessing.py Sat Jun 25 18:54:15 2011 +0200 @@ -1737,7 +1737,33 @@ (narena, nstart, nstop) = all[i+1][:3] self.assertTrue((arena != narena and nstart == 0) or (stop == nstart)) - + + def test_free_from_gc(self): + # Check that freeing of blocks by the garbage collector doesn't deadlock + # (issue #12352). + # Make sure the GC is enabled, and set lower collection thresholds to + # make collections more frequent (and increase the probability of + # deadlock). + enabled = gc.isenabled() + if enabled: + thresholds = gc.get_threshold() + self.addCleanup(gc.set_threshold, *thresholds) + else: + gc.enable() + self.addCleanup(gc.disable) + gc.set_threshold(10) + + # perform numerous block allocations, with cyclic references to make + # sure objects are collected synchronously by the gc + for i in range(5000): + a = multiprocessing.heap.BufferWrapper(1) + b = multiprocessing.heap.BufferWrapper(1) + # create a circular reference (we want GC and not refcount collection when + # the block goes out of scope) + a.buddy = b + b.buddy = a + + # # #