Skip to content

Commit 81d7d22

Browse files
authored
FIX adapt resource_tracker teardown for newer cpython (#450)
1 parent d8bb877 commit 81d7d22

File tree

5 files changed

+53
-46
lines changed

5 files changed

+53
-46
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### 3.6.0 - in development
22

3+
- Fix resource_tracker teardown to accomodate with newer version of
4+
Python (3.12.10+, 3.13.3+, 3.14+). (#450)
35

46
### 3.5.1 - 2025-03-18
57

loky/backend/popen_loky_posix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def _launch(self, process_obj):
120120
reduction._mk_inheritable(tracker_fd)
121121
self._fds += [child_r, child_w, tracker_fd]
122122
if sys.version_info >= (3, 8) and os.name == "posix":
123-
mp_tracker_fd = prep_data["mp_tracker_args"]["fd"]
123+
mp_tracker_fd = prep_data["mp_tracker_fd"]
124124
self.duplicate_for_child(mp_tracker_fd)
125125

126126
from .fork_exec import fork_exec

loky/backend/resource_tracker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ def ensure_running(self):
171171
else:
172172
os.close(r)
173173

174+
def __del__(self):
175+
# ignore error due to trying to clean up child process which has already been
176+
# shutdown on windows See https://github.com/joblib/loky/pull/450
177+
# This is only required if __del__ is defined
178+
if not hasattr(ResourceTracker, "__del__"):
179+
return
180+
try:
181+
super().__del__()
182+
except ChildProcessError:
183+
pass
184+
174185

175186
_resource_tracker = ResourceTracker()
176187
ensure_running = _resource_tracker.ensure_running

loky/backend/spawn.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,10 @@ def get_preparation_data(name, init_main_module=True):
8282
from .resource_tracker import _resource_tracker
8383

8484
_resource_tracker.ensure_running()
85-
d["tracker_args"] = {"pid": _resource_tracker._pid}
8685
if sys.platform == "win32":
87-
d["tracker_args"]["fh"] = msvcrt.get_osfhandle(_resource_tracker._fd)
86+
d["tracker_fd"] = msvcrt.get_osfhandle(_resource_tracker._fd)
8887
else:
89-
d["tracker_args"]["fd"] = _resource_tracker._fd
88+
d["tracker_fd"] = _resource_tracker._fd
9089

9190
if sys.version_info >= (3, 8) and os.name == "posix":
9291
# joblib/loky#242: allow loky processes to retrieve the resource
@@ -105,10 +104,7 @@ def get_preparation_data(name, init_main_module=True):
105104
# process is created (othewise the child won't be able to use it if it
106105
# is created later on)
107106
mp_resource_tracker.ensure_running()
108-
d["mp_tracker_args"] = {
109-
"fd": mp_resource_tracker._fd,
110-
"pid": mp_resource_tracker._pid,
111-
}
107+
d["mp_tracker_fd"] = mp_resource_tracker._fd
112108

113109
# Figure out whether to initialise main in the subprocess as a module
114110
# or through direct execution (or to leave it alone entirely)
@@ -172,23 +168,21 @@ def prepare(data, parent_sentinel=None):
172168
if "orig_dir" in data:
173169
process.ORIGINAL_DIR = data["orig_dir"]
174170

175-
if "mp_tracker_args" in data:
171+
if "mp_tracker_fd" in data:
176172
from multiprocessing.resource_tracker import (
177173
_resource_tracker as mp_resource_tracker,
178174
)
179175

180-
mp_resource_tracker._fd = data["mp_tracker_args"]["fd"]
181-
mp_resource_tracker._pid = data["mp_tracker_args"]["pid"]
182-
if "tracker_args" in data:
176+
mp_resource_tracker._fd = data["mp_tracker_fd"]
177+
if "tracker_fd" in data:
183178
from .resource_tracker import _resource_tracker
184179

185-
_resource_tracker._pid = data["tracker_args"]["pid"]
186180
if sys.platform == "win32":
187-
handle = data["tracker_args"]["fh"]
181+
handle = data["tracker_fd"]
188182
handle = duplicate(handle, source_process=parent_sentinel)
189183
_resource_tracker._fd = msvcrt.open_osfhandle(handle, os.O_RDONLY)
190184
else:
191-
_resource_tracker._fd = data["tracker_args"]["fd"]
185+
_resource_tracker._fd = data["tracker_fd"]
192186

193187
if "init_main_from_name" in data:
194188
_fixup_main_from_name(data["init_main_from_name"])

tests/test_resource_tracker.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def _resource_unlink(name, rtype):
2323
resource_tracker._CLEANUP_FUNCS[rtype](name)
2424

2525

26-
def get_rtracker_pid():
26+
def get_rtracker_fd():
2727
resource_tracker.ensure_running()
28-
return resource_tracker._resource_tracker._pid
28+
return resource_tracker._resource_tracker._fd
2929

3030

3131
class TestResourceTracker:
@@ -40,12 +40,18 @@ def test_resource_utils(self, rtype):
4040
assert not resource_exists(name, rtype)
4141

4242
def test_child_retrieves_resource_tracker(self):
43-
parent_rtracker_pid = get_rtracker_pid()
44-
executor = ProcessPoolExecutor(max_workers=2)
45-
child_rtracker_pid = executor.submit(get_rtracker_pid).result()
4643

47-
# First simple pid retrieval check (see #200)
48-
assert child_rtracker_pid == parent_rtracker_pid
44+
# First simple fd retrieval check (see #200)
45+
# checking fd only work on posix for now
46+
if sys.platform != "win32":
47+
try:
48+
parent_rtracker_fd = get_rtracker_fd()
49+
executor = ProcessPoolExecutor(max_workers=2)
50+
child_rtracker_fd = executor.submit(get_rtracker_fd).result()
51+
52+
assert child_rtracker_fd == parent_rtracker_fd
53+
finally:
54+
executor.shutdown()
4955

5056
# Register a resource in the parent process, and un-register it in the
5157
# child process. If the two processes do not share the same
@@ -77,23 +83,20 @@ def maybe_unlink(name, rtype):
7783
e.submit(maybe_unlink, filename, "file").result()
7884
e.shutdown()
7985
"""
80-
try:
81-
p = subprocess.run(
82-
[sys.executable, "-E", "-c", cmd],
83-
capture_output=True,
84-
text=True,
85-
)
86-
filename = p.stdout.strip()
8786

88-
pattern = f"decremented refcount of file {filename}"
89-
assert pattern in p.stderr
90-
assert "leaked" not in p.stderr
87+
p = subprocess.run(
88+
[sys.executable, "-E", "-c", cmd],
89+
capture_output=True,
90+
text=True,
91+
)
92+
filename = p.stdout.strip()
9193

92-
pattern = f"KeyError: '{filename}'"
93-
assert pattern not in p.stderr
94+
pattern = f"decremented refcount of file {filename}"
95+
assert pattern in p.stderr
96+
assert "leaked" not in p.stderr
9497

95-
finally:
96-
executor.shutdown()
98+
pattern = f"KeyError: '{filename}'"
99+
assert pattern not in p.stderr
97100

98101
# The following four tests are inspired from cpython _test_multiprocessing
99102
@pytest.mark.parametrize("rtype", ["file", "folder", "semlock"])
@@ -294,15 +297,12 @@ def test_loky_process_inherit_multiprocessing_resource_tracker(self):
294297
cmd = """if 1:
295298
from loky import get_reusable_executor
296299
from multiprocessing.shared_memory import SharedMemory
297-
from multiprocessing.resource_tracker import (
298-
_resource_tracker as mp_resource_tracker
299-
)
300300
301-
def mp_rtracker_getattrs():
301+
def mp_rtracker_getfd():
302302
from multiprocessing.resource_tracker import (
303303
_resource_tracker as mp_resource_tracker
304304
)
305-
return mp_resource_tracker._fd, mp_resource_tracker._pid
305+
return mp_resource_tracker._fd
306306
307307
308308
if __name__ == '__main__':
@@ -312,9 +312,9 @@ def mp_rtracker_getattrs():
312312
313313
# loky forces the creation of the resource tracker at process
314314
# creation so that loky processes can inherit its file descriptor.
315-
fd, pid = executor.submit(mp_rtracker_getattrs).result()
316-
assert fd == mp_resource_tracker._fd
317-
assert pid == mp_resource_tracker._pid
315+
parent_fd = mp_rtracker_getfd()
316+
child_fd = executor.submit(mp_rtracker_getfd).result()
317+
assert child_fd == parent_fd
318318
319319
# non-regression test for #242: unlinking in a loky process a
320320
# shared_memory segment tracked by multiprocessing and created its
@@ -326,5 +326,5 @@ def mp_rtracker_getattrs():
326326
p = subprocess.run(
327327
[sys.executable, "-c", cmd], capture_output=True, text=True
328328
)
329-
assert not p.stdout
330-
assert not p.stderr
329+
assert not p.stdout, p.stdout
330+
assert not p.stderr, p.stderr

0 commit comments

Comments
 (0)