Skip to content

Commit b1dfcad

Browse files
pierreglaserpitrou
authored andcommitted
bpo-36867: Create the resource_tracker before launching SharedMemoryManagers (GH-13276)
1 parent be6939f commit b1dfcad

File tree

3 files changed

+54
-14
lines changed

3 files changed

+54
-14
lines changed

Lib/multiprocessing/managers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import array
2222
import queue
2323
import time
24+
import os
2425
from os import getpid
2526

2627
from traceback import format_exc
@@ -1349,6 +1350,14 @@ class SharedMemoryManager(BaseManager):
13491350
_Server = SharedMemoryServer
13501351

13511352
def __init__(self, *args, **kwargs):
1353+
if os.name == "posix":
1354+
# bpo-36867: Ensure the resource_tracker is running before
1355+
# launching the manager process, so that concurrent
1356+
# shared_memory manipulation both in the manager and in the
1357+
# current process does not create two resource_tracker
1358+
# processes.
1359+
from . import resource_tracker
1360+
resource_tracker.ensure_running()
13521361
BaseManager.__init__(self, *args, **kwargs)
13531362
util.debug(f"{self.__class__.__name__} created by pid {getpid()}")
13541363

Lib/test/_test_multiprocessing.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import socket
1818
import random
1919
import logging
20+
import subprocess
2021
import struct
2122
import operator
2223
import pickle
@@ -3765,6 +3766,27 @@ def test_shared_memory_SharedMemoryServer_ignores_sigint(self):
37653766

37663767
smm.shutdown()
37673768

3769+
@unittest.skipIf(os.name != "posix", "resource_tracker is posix only")
3770+
def test_shared_memory_SharedMemoryManager_reuses_resource_tracker(self):
3771+
# bpo-36867: test that a SharedMemoryManager uses the
3772+
# same resource_tracker process as its parent.
3773+
cmd = '''if 1:
3774+
from multiprocessing.managers import SharedMemoryManager
3775+
3776+
3777+
smm = SharedMemoryManager()
3778+
smm.start()
3779+
sl = smm.ShareableList(range(10))
3780+
smm.shutdown()
3781+
'''
3782+
rc, out, err = test.support.script_helper.assert_python_ok('-c', cmd)
3783+
3784+
# Before bpo-36867 was fixed, a SharedMemoryManager not using the same
3785+
# resource_tracker process as its parent would make the parent's
3786+
# tracker complain about sl being leaked even though smm.shutdown()
3787+
# properly released sl.
3788+
self.assertFalse(err)
3789+
37683790
def test_shared_memory_SharedMemoryManager_basics(self):
37693791
smm1 = multiprocessing.managers.SharedMemoryManager()
37703792
with self.assertRaises(ValueError):
@@ -3904,8 +3926,6 @@ def test_shared_memory_ShareableList_pickling(self):
39043926
sl.shm.close()
39053927

39063928
def test_shared_memory_cleaned_after_process_termination(self):
3907-
import subprocess
3908-
from multiprocessing import shared_memory
39093929
cmd = '''if 1:
39103930
import os, time, sys
39113931
from multiprocessing import shared_memory
@@ -3916,18 +3936,29 @@ def test_shared_memory_cleaned_after_process_termination(self):
39163936
sys.stdout.flush()
39173937
time.sleep(100)
39183938
'''
3919-
p = subprocess.Popen([sys.executable, '-E', '-c', cmd],
3920-
stdout=subprocess.PIPE)
3921-
name = p.stdout.readline().strip().decode()
3939+
with subprocess.Popen([sys.executable, '-E', '-c', cmd],
3940+
stdout=subprocess.PIPE,
3941+
stderr=subprocess.PIPE) as p:
3942+
name = p.stdout.readline().strip().decode()
39223943

3923-
# killing abruptly processes holding reference to a shared memory
3924-
# segment should not leak the given memory segment.
3925-
p.terminate()
3926-
p.wait()
3927-
time.sleep(1.0) # wait for the OS to collect the segment
3944+
# killing abruptly processes holding reference to a shared memory
3945+
# segment should not leak the given memory segment.
3946+
p.terminate()
3947+
p.wait()
3948+
time.sleep(1.0) # wait for the OS to collect the segment
39283949

3929-
with self.assertRaises(FileNotFoundError):
3930-
smm = shared_memory.SharedMemory(name, create=False)
3950+
# The shared memory file was deleted.
3951+
with self.assertRaises(FileNotFoundError):
3952+
smm = shared_memory.SharedMemory(name, create=False)
3953+
3954+
if os.name == 'posix':
3955+
# A warning was emitted by the subprocess' own
3956+
# resource_tracker (on Windows, shared memory segments
3957+
# are released automatically by the OS).
3958+
err = p.stderr.read().decode()
3959+
self.assertIn(
3960+
"resource_tracker: There appear to be 1 leaked "
3961+
"shared_memory objects to clean up at shutdown", err)
39313962

39323963
#
39333964
#
@@ -4560,7 +4591,7 @@ def run_in_child(cls):
45604591
print(json.dumps(flags))
45614592

45624593
def test_flags(self):
4563-
import json, subprocess
4594+
import json
45644595
# start child process using unusual flags
45654596
prog = ('from test._test_multiprocessing import TestFlags; ' +
45664597
'TestFlags.run_in_child()')
@@ -4866,7 +4897,6 @@ def test_resource_tracker(self):
48664897
#
48674898
# Check that killing process does not leak named semaphores
48684899
#
4869-
import subprocess
48704900
cmd = '''if 1:
48714901
import time, os, tempfile
48724902
import multiprocessing as mp
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug making a SharedMemoryManager instance and its parent process use two separate resource_tracker processes.

0 commit comments

Comments
 (0)