summaryrefslogtreecommitdiffstats
path: root/Lib/test/_test_multiprocessing.py
diff options
context:
space:
mode:
authorAntoine Pitrou <pitrou@free.fr>2018-04-09 15:37:55 (GMT)
committerGitHub <noreply@github.com>2018-04-09 15:37:55 (GMT)
commite4679cd644aa19f9d9df9beb1326625cf2b02c15 (patch)
tree4b6934e851dd7077d294d209ad90802f0695de63 /Lib/test/_test_multiprocessing.py
parent2ef65f346a5e829a886c075f519e3a49a2ebbde7 (diff)
downloadcpython-e4679cd644aa19f9d9df9beb1326625cf2b02c15.zip
cpython-e4679cd644aa19f9d9df9beb1326625cf2b02c15.tar.gz
cpython-e4679cd644aa19f9d9df9beb1326625cf2b02c15.tar.bz2
bpo-32759: Free unused arenas in multiprocessing.heap (GH-5827)
Large shared arrays allocated using multiprocessing would remain allocated until the process ends.
Diffstat (limited to 'Lib/test/_test_multiprocessing.py')
-rw-r--r--Lib/test/_test_multiprocessing.py81
1 files changed, 58 insertions, 23 deletions
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index c6a1f5c..20185a9 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -3372,11 +3372,25 @@ class _TestHeap(BaseTestCase):
ALLOWED_TYPES = ('processes',)
+ def setUp(self):
+ super().setUp()
+ # Make pristine heap for these tests
+ self.old_heap = multiprocessing.heap.BufferWrapper._heap
+ multiprocessing.heap.BufferWrapper._heap = multiprocessing.heap.Heap()
+
+ def tearDown(self):
+ multiprocessing.heap.BufferWrapper._heap = self.old_heap
+ super().tearDown()
+
def test_heap(self):
iterations = 5000
maxblocks = 50
blocks = []
+ # get the heap object
+ heap = multiprocessing.heap.BufferWrapper._heap
+ heap._DISCARD_FREE_SPACE_LARGER_THAN = 0
+
# create and destroy lots of blocks of different sizes
for i in range(iterations):
size = int(random.lognormvariate(0, 1) * 1000)
@@ -3385,31 +3399,52 @@ class _TestHeap(BaseTestCase):
if len(blocks) > maxblocks:
i = random.randrange(maxblocks)
del blocks[i]
-
- # get the heap object
- heap = multiprocessing.heap.BufferWrapper._heap
+ del b
# verify the state of the heap
- all = []
- occupied = 0
- heap._lock.acquire()
- self.addCleanup(heap._lock.release)
- for L in list(heap._len_to_seq.values()):
- for arena, start, stop in L:
- all.append((heap._arenas.index(arena), start, stop,
- stop-start, 'free'))
- for arena, start, stop in heap._allocated_blocks:
- all.append((heap._arenas.index(arena), start, stop,
- stop-start, 'occupied'))
- occupied += (stop-start)
-
- all.sort()
-
- for i in range(len(all)-1):
- (arena, start, stop) = all[i][:3]
- (narena, nstart, nstop) = all[i+1][:3]
- self.assertTrue((arena != narena and nstart == 0) or
- (stop == nstart))
+ with heap._lock:
+ all = []
+ free = 0
+ occupied = 0
+ for L in list(heap._len_to_seq.values()):
+ # count all free blocks in arenas
+ for arena, start, stop in L:
+ all.append((heap._arenas.index(arena), start, stop,
+ stop-start, 'free'))
+ free += (stop-start)
+ for arena, arena_blocks in heap._allocated_blocks.items():
+ # count all allocated blocks in arenas
+ for start, stop in arena_blocks:
+ all.append((heap._arenas.index(arena), start, stop,
+ stop-start, 'occupied'))
+ occupied += (stop-start)
+
+ self.assertEqual(free + occupied,
+ sum(arena.size for arena in heap._arenas))
+
+ all.sort()
+
+ for i in range(len(all)-1):
+ (arena, start, stop) = all[i][:3]
+ (narena, nstart, nstop) = all[i+1][:3]
+ if arena != narena:
+ # Two different arenas
+ self.assertEqual(stop, heap._arenas[arena].size) # last block
+ self.assertEqual(nstart, 0) # first block
+ else:
+ # Same arena: two adjacent blocks
+ self.assertEqual(stop, nstart)
+
+ # test free'ing all blocks
+ random.shuffle(blocks)
+ while blocks:
+ blocks.pop()
+
+ self.assertEqual(heap._n_frees, heap._n_mallocs)
+ self.assertEqual(len(heap._pending_free_blocks), 0)
+ self.assertEqual(len(heap._arenas), 0)
+ self.assertEqual(len(heap._allocated_blocks), 0, heap._allocated_blocks)
+ self.assertEqual(len(heap._len_to_seq), 0)
def test_free_from_gc(self):
# Check that freeing of blocks by the garbage collector doesn't deadlock