Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 896145d

Browse files
authored
bpo-26732: fix too many fds in processes started with the "forkserver" method (#2813)
* bpo-26732: fix too many fds in processes started with the "forkserver" method A child process would inherit as many fds as the number of still-running children. * Add blurb and test comment
1 parent 616ecf1 commit 896145d

5 files changed

Lines changed: 75 additions & 33 deletions

File tree

Lib/multiprocessing/forkserver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,11 @@ def sigchld_handler(*_unused):
236236
code = 1
237237
try:
238238
listener.close()
239+
selector.close()
240+
unused_fds = [alive_r, child_w, sig_r, sig_w]
241+
unused_fds.extend(pid_to_fd.values())
239242
code = _serve_one(child_r, fds,
240-
(alive_r, child_w, sig_r, sig_w),
243+
unused_fds,
241244
old_handlers)
242245
except Exception:
243246
sys.excepthook(*sys.exc_info())

Lib/test/_test_multiprocessing.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,40 @@ def test_lose_target_ref(self):
494494
self.assertIs(wr(), None)
495495
self.assertEqual(q.get(), 5)
496496

497+
@classmethod
498+
def _test_child_fd_inflation(self, evt, q):
499+
q.put(test.support.fd_count())
500+
evt.wait()
501+
502+
def test_child_fd_inflation(self):
503+
# Number of fds in child processes should not grow with the
504+
# number of running children.
505+
if self.TYPE == 'threads':
506+
self.skipTest('test not appropriate for {}'.format(self.TYPE))
507+
508+
sm = multiprocessing.get_start_method()
509+
if sm == 'fork':
510+
# The fork method by design inherits all fds from the parent,
511+
# trying to go against it is a lost battle
512+
self.skipTest('test not appropriate for {}'.format(sm))
513+
514+
N = 5
515+
evt = self.Event()
516+
q = self.Queue()
517+
518+
procs = [self.Process(target=self._test_child_fd_inflation, args=(evt, q))
519+
for i in range(N)]
520+
for p in procs:
521+
p.start()
522+
523+
try:
524+
fd_counts = [q.get() for i in range(N)]
525+
self.assertEqual(len(set(fd_counts)), 1, fd_counts)
526+
527+
finally:
528+
evt.set()
529+
for p in procs:
530+
p.join()
497531

498532
#
499533
#

Lib/test/libregrtest/refleak.py

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,6 @@
77
from test import support
88

99

10-
try:
11-
MAXFD = os.sysconf("SC_OPEN_MAX")
12-
except Exception:
13-
MAXFD = 256
14-
15-
16-
def fd_count():
17-
"""Count the number of open file descriptors"""
18-
if sys.platform.startswith(('linux', 'freebsd')):
19-
try:
20-
names = os.listdir("/proc/self/fd")
21-
return len(names)
22-
except FileNotFoundError:
23-
pass
24-
25-
count = 0
26-
for fd in range(MAXFD):
27-
try:
28-
# Prefer dup() over fstat(). fstat() can require input/output
29-
# whereas dup() doesn't.
30-
fd2 = os.dup(fd)
31-
except OSError as e:
32-
if e.errno != errno.EBADF:
33-
raise
34-
else:
35-
os.close(fd2)
36-
count += 1
37-
return count
38-
39-
4010
def dash_R(the_module, test, indirect_test, huntrleaks):
4111
"""Run a test multiple times, looking for reference leaks.
4212
@@ -174,7 +144,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
174144
func1 = sys.getallocatedblocks
175145
func2 = sys.gettotalrefcount
176146
gc.collect()
177-
return func1(), func2(), fd_count()
147+
return func1(), func2(), support.fd_count()
178148

179149

180150
def clear_caches():

Lib/test/support/__init__.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"check_warnings", "check_no_resource_warning", "EnvironmentVarGuard",
108108
"run_with_locale", "swap_item",
109109
"swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
110-
"run_with_tz", "PGO", "missing_compiler_executable",
110+
"run_with_tz", "PGO", "missing_compiler_executable", "fd_count",
111111
]
112112

113113
class Error(Exception):
@@ -2647,3 +2647,34 @@ def disable_faulthandler():
26472647
finally:
26482648
if is_enabled:
26492649
faulthandler.enable(file=fd, all_threads=True)
2650+
2651+
2652+
try:
2653+
MAXFD = os.sysconf("SC_OPEN_MAX")
2654+
except Exception:
2655+
MAXFD = 256
2656+
2657+
2658+
def fd_count():
2659+
"""Count the number of open file descriptors.
2660+
"""
2661+
if sys.platform.startswith(('linux', 'freebsd')):
2662+
try:
2663+
names = os.listdir("/proc/self/fd")
2664+
return len(names)
2665+
except FileNotFoundError:
2666+
pass
2667+
2668+
count = 0
2669+
for fd in range(MAXFD):
2670+
try:
2671+
# Prefer dup() over fstat(). fstat() can require input/output
2672+
# whereas dup() doesn't.
2673+
fd2 = os.dup(fd)
2674+
except OSError as e:
2675+
if e.errno != errno.EBADF:
2676+
raise
2677+
else:
2678+
os.close(fd2)
2679+
count += 1
2680+
return count
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix too many fds in processes started with the "forkserver" method.
2+
3+
A child process would inherit as many fds as the number of still-running
4+
children.

0 commit comments

Comments
 (0)