Skip to content

Commit b81b014

Browse files
author
Release Manager
committed
sagemathgh-41227: Fix external graph generators leaking file descriptors This fixes a "Too many open files" error when running ```sage import resource max_open_files = 20 resource.setrlimit(resource.RLIMIT_NOFILE,(max_open_files,max_open_files )) for _ in range(max_open_files): for G in graphs.nauty_geng("9"): break ``` The above relies on the fact that `geng 9` takes a while to finish. While the first `geng 9` process is still running, up to 20 other processes are started until the operating system runs out of file descriptors. This pull request fixes this by making sure a process is terminated when it's no longer needed. I did not (and do not plan to) add any tests for this. URL: sagemath#41227 Reported by: Lennard Hofmann Reviewer(s): David Coudert
2 parents 8a10ab3 + 543dbd6 commit b81b014

File tree

4 files changed

+164
-164
lines changed

4 files changed

+164
-164
lines changed

src/sage/graphs/digraph_generators.py

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -646,36 +646,36 @@ def tournaments_nauty(self, n,
646646
from sage.features.nauty import NautyExecutable
647647
gentourng_path = NautyExecutable("gentourng").absolute_filename()
648648

649-
sp = subprocess.Popen(shlex.quote(gentourng_path) + " {0}".format(nauty_input),
649+
with subprocess.Popen(shlex.quote(gentourng_path) + " {0}".format(nauty_input),
650650
shell=True,
651651
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
652-
stderr=subprocess.PIPE, close_fds=True)
653-
654-
if debug:
655-
yield sp.stderr.readline()
656-
657-
def edges(s):
658-
i = 0
659-
j = 1
660-
for b in s[:-1]:
661-
yield (i, j) if b == '0' else (j, i)
662-
663-
if j == n - 1:
664-
i += 1
665-
j = i + 1
666-
else:
667-
j += 1
668-
669-
gen = sp.stdout
670-
while True:
671-
try:
672-
s = bytes_to_str(next(gen))
673-
except StopIteration:
674-
# Exhausted list of graphs from nauty geng
675-
return
676-
677-
yield DiGraph([range(n), edges(s)], format='vertices_and_edges',
678-
immutable=immutable)
652+
stderr=subprocess.PIPE, close_fds=True) as sp:
653+
654+
if debug:
655+
yield sp.stderr.readline()
656+
657+
def edges(s):
658+
i = 0
659+
j = 1
660+
for b in s[:-1]:
661+
yield (i, j) if b == '0' else (j, i)
662+
663+
if j == n - 1:
664+
i += 1
665+
j = i + 1
666+
else:
667+
j += 1
668+
669+
gen = sp.stdout
670+
while True:
671+
try:
672+
s = bytes_to_str(next(gen))
673+
except StopIteration:
674+
# Exhausted list of graphs from nauty geng
675+
return
676+
677+
yield DiGraph([range(n), edges(s)], format='vertices_and_edges',
678+
immutable=immutable)
679679

680680
def nauty_directg(self, graphs, options='', debug=False, immutable=False):
681681
r"""
@@ -780,29 +780,29 @@ def nauty_directg(self, graphs, options='', debug=False, immutable=False):
780780
from sage.features.nauty import NautyExecutable
781781
directg_path = NautyExecutable("directg").absolute_filename()
782782

783-
sub = subprocess.Popen(shlex.quote(directg_path) + ' {0}'.format(options),
783+
with subprocess.Popen(shlex.quote(directg_path) + ' {0}'.format(options),
784784
shell=True,
785785
stdout=subprocess.PIPE,
786786
stdin=subprocess.PIPE,
787787
stderr=subprocess.STDOUT,
788-
encoding='latin-1')
789-
out, err = sub.communicate(input=input)
788+
encoding='latin-1') as sub:
789+
out, err = sub.communicate(input=input)
790790

791-
if debug:
792-
if err:
793-
print(err)
791+
if debug:
792+
if err:
793+
print(err)
794794

795-
if out:
796-
print(out)
795+
if out:
796+
print(out)
797797

798-
for line in out.split('\n'):
799-
# directg return graphs in the digraph6 format.
800-
# digraph6 is very similar with the dig6 format used in sage :
801-
# digraph6_string = '&' + dig6_string
802-
# digraph6 specifications:
803-
# http://users.cecs.anu.edu.au/~bdm/data/formats.txt
804-
if line and line[0] == '&':
805-
yield DiGraph(line[1:], format='dig6', immutable=immutable)
798+
for line in out.split('\n'):
799+
# directg return graphs in the digraph6 format.
800+
# digraph6 is very similar with the dig6 format used in sage :
801+
# digraph6_string = '&' + dig6_string
802+
# digraph6 specifications:
803+
# http://users.cecs.anu.edu.au/~bdm/data/formats.txt
804+
if line and line[0] == '&':
805+
yield DiGraph(line[1:], format='dig6', immutable=immutable)
806806

807807
def nauty_posetg(self, options='', debug=False, immutable=False):
808808
r"""
@@ -845,23 +845,23 @@ def nauty_posetg(self, options='', debug=False, immutable=False):
845845
import shlex
846846
from sage.features.nauty import NautyExecutable
847847
geng_path = NautyExecutable("genposetg").absolute_filename()
848-
sp = subprocess.Popen(shlex.quote(geng_path) + f" {options}", shell=True,
848+
with subprocess.Popen(shlex.quote(geng_path) + f" {options}", shell=True,
849849
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
850850
stderr=subprocess.PIPE, close_fds=True,
851-
encoding='latin-1')
852-
msg = sp.stderr.readline()
853-
if debug:
854-
yield msg
855-
elif msg.startswith('>E'):
856-
raise ValueError('wrong format of parameter option')
857-
gen = sp.stdout
858-
while True:
859-
try:
860-
s = next(gen)
861-
except StopIteration:
862-
# Exhausted list of graphs from nauty genposetg
863-
return
864-
yield DiGraph(s[1:-1], format='dig6', immutable=immutable)
851+
encoding='latin-1') as sp:
852+
msg = sp.stderr.readline()
853+
if debug:
854+
yield msg
855+
elif msg.startswith('>E'):
856+
raise ValueError('wrong format of parameter option')
857+
gen = sp.stdout
858+
while True:
859+
try:
860+
s = next(gen)
861+
except StopIteration:
862+
# Exhausted list of graphs from nauty genposetg
863+
return
864+
yield DiGraph(s[1:-1], format='dig6', immutable=immutable)
865865

866866
def Complete(self, n, loops=False, immutable=False):
867867
r"""

src/sage/graphs/generators/trees.pyx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -893,21 +893,21 @@ def nauty_gentreeg(options='', debug=False):
893893
import subprocess
894894
from sage.features.nauty import NautyExecutable
895895
gen_path = NautyExecutable("gentreeg").absolute_filename()
896-
sp = subprocess.Popen(shlex.quote(gen_path) + " {0}".format(options), shell=True,
896+
with subprocess.Popen(shlex.quote(gen_path) + " {0}".format(options), shell=True,
897897
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
898898
stderr=subprocess.PIPE, close_fds=True,
899-
encoding='latin-1')
900-
msg = sp.stderr.readline()
901-
if debug:
902-
yield msg
903-
elif msg.startswith('>E'):
904-
raise ValueError('wrong format of parameter options')
905-
gen = sp.stdout
906-
while True:
907-
try:
908-
s = next(gen)
909-
except StopIteration:
910-
# Exhausted list of graphs from nauty geng
911-
return
912-
G = Graph(s[:-1], format='sparse6', loops=False, multiedges=False)
913-
yield G
899+
encoding='latin-1') as sp:
900+
msg = sp.stderr.readline()
901+
if debug:
902+
yield msg
903+
elif msg.startswith('>E'):
904+
raise ValueError('wrong format of parameter options')
905+
gen = sp.stdout
906+
while True:
907+
try:
908+
s = next(gen)
909+
except StopIteration:
910+
# Exhausted list of graphs from nauty geng
911+
return
912+
G = Graph(s[:-1], format='sparse6', loops=False, multiedges=False)
913+
yield G

src/sage/graphs/graph_generators.py

Lines changed: 74 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,23 +1016,23 @@ def nauty_geng(self, options='', debug=False, immutable=False):
10161016

10171017
from sage.features.nauty import NautyExecutable
10181018
geng_path = NautyExecutable("geng").absolute_filename()
1019-
sp = subprocess.Popen(shlex.quote(geng_path) + " {0}".format(options), shell=True,
1019+
with subprocess.Popen(shlex.quote(geng_path) + " {0}".format(options), shell=True,
10201020
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
10211021
stderr=subprocess.PIPE, close_fds=True,
1022-
encoding='latin-1')
1023-
msg = sp.stderr.readline()
1024-
if debug:
1025-
yield msg
1026-
elif msg.startswith('>E'):
1027-
raise ValueError('wrong format of parameter option')
1028-
gen = sp.stdout
1029-
while True:
1030-
try:
1031-
s = next(gen)
1032-
except StopIteration:
1033-
# Exhausted list of graphs from nauty geng
1034-
return
1035-
yield graph.Graph(s[:-1], format='graph6', immutable=immutable)
1022+
encoding='latin-1') as sp:
1023+
msg = sp.stderr.readline()
1024+
if debug:
1025+
yield msg
1026+
elif msg.startswith('>E'):
1027+
raise ValueError('wrong format of parameter option')
1028+
gen = sp.stdout
1029+
while True:
1030+
try:
1031+
s = next(gen)
1032+
except StopIteration:
1033+
# Exhausted list of graphs from nauty geng
1034+
return
1035+
yield graph.Graph(s[:-1], format='graph6', immutable=immutable)
10361036

10371037
def nauty_genbg(self, options='', debug=False, immutable=False):
10381038
r"""
@@ -1203,41 +1203,41 @@ def nauty_genbg(self, options='', debug=False, immutable=False):
12031203

12041204
from sage.features.nauty import NautyExecutable
12051205
genbg_path = NautyExecutable("genbgL").absolute_filename()
1206-
sp = subprocess.Popen(shlex.quote(genbg_path) + " {0}".format(options), shell=True,
1206+
with subprocess.Popen(shlex.quote(genbg_path) + " {0}".format(options), shell=True,
12071207
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
12081208
stderr=subprocess.PIPE, close_fds=True,
1209-
encoding='latin-1')
1210-
msg = sp.stderr.readline()
1211-
if debug:
1212-
yield msg
1213-
elif msg.startswith('>E'):
1214-
raise ValueError('wrong format of parameter options')
1215-
1216-
if msg.startswith('>A'):
1217-
# We extract the partition of the vertices from the msg string
1218-
for s in msg.split(' '):
1219-
if s.startswith('n='):
1220-
from sage.rings.integer import Integer
1221-
n1, n2 = (Integer(t) for t in s[2:].split('+') if t.isdigit())
1222-
partition = [set(range(n1)), set(range(n1, n1 + n2))]
1223-
break
1209+
encoding='latin-1') as sp:
1210+
msg = sp.stderr.readline()
1211+
if debug:
1212+
yield msg
1213+
elif msg.startswith('>E'):
1214+
raise ValueError('wrong format of parameter options')
1215+
1216+
if msg.startswith('>A'):
1217+
# We extract the partition of the vertices from the msg string
1218+
for s in msg.split(' '):
1219+
if s.startswith('n='):
1220+
from sage.rings.integer import Integer
1221+
n1, n2 = (Integer(t) for t in s[2:].split('+') if t.isdigit())
1222+
partition = [set(range(n1)), set(range(n1, n1 + n2))]
1223+
break
1224+
else:
1225+
# should never happen
1226+
raise ValueError('unable to recover the partition')
12241227
else:
1225-
# should never happen
1226-
raise ValueError('unable to recover the partition')
1227-
else:
1228-
# Either msg starts with >E or option -q has been given
1229-
partition = None
1230-
1231-
gen = sp.stdout
1232-
from sage.graphs.bipartite_graph import BipartiteGraph
1233-
while True:
1234-
try:
1235-
s = next(gen)
1236-
except StopIteration:
1237-
# Exhausted list of bipartite graphs from nauty genbgL
1238-
return
1239-
yield BipartiteGraph(s[:-1], format='graph6', partition=partition,
1240-
immutable=immutable)
1228+
# Either msg starts with >E or option -q has been given
1229+
partition = None
1230+
1231+
gen = sp.stdout
1232+
from sage.graphs.bipartite_graph import BipartiteGraph
1233+
while True:
1234+
try:
1235+
s = next(gen)
1236+
except StopIteration:
1237+
# Exhausted list of bipartite graphs from nauty genbgL
1238+
return
1239+
yield BipartiteGraph(s[:-1], format='graph6', partition=partition,
1240+
immutable=immutable)
12411241

12421242
def nauty_genktreeg(self, options='', debug=False, immutable=False):
12431243
r"""
@@ -1339,23 +1339,23 @@ def nauty_genktreeg(self, options='', debug=False, immutable=False):
13391339

13401340
from sage.features.nauty import NautyExecutable
13411341
geng_path = NautyExecutable("genktreeg").absolute_filename()
1342-
sp = subprocess.Popen(shlex.quote(geng_path) + " {0}".format(options), shell=True,
1342+
with subprocess.Popen(shlex.quote(geng_path) + " {0}".format(options), shell=True,
13431343
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
13441344
stderr=subprocess.PIPE, close_fds=True,
1345-
encoding='latin-1')
1346-
msg = sp.stderr.readline()
1347-
if debug:
1348-
yield msg
1349-
elif msg.startswith('>E'):
1350-
raise ValueError('wrong format of parameter option')
1351-
gen = sp.stdout
1352-
while True:
1353-
try:
1354-
s = next(gen)
1355-
except StopIteration:
1356-
# Exhausted list of graphs from nauty geng
1357-
return
1358-
yield graph.Graph(s[:-1], format='graph6', immutable=immutable)
1345+
encoding='latin-1') as sp:
1346+
msg = sp.stderr.readline()
1347+
if debug:
1348+
yield msg
1349+
elif msg.startswith('>E'):
1350+
raise ValueError('wrong format of parameter option')
1351+
gen = sp.stdout
1352+
while True:
1353+
try:
1354+
s = next(gen)
1355+
except StopIteration:
1356+
# Exhausted list of graphs from nauty geng
1357+
return
1358+
yield graph.Graph(s[:-1], format='graph6', immutable=immutable)
13591359

13601360
def cospectral_graphs(self, vertices, matrix_function=None, graphs=None,
13611361
immutable=False):
@@ -1718,11 +1718,11 @@ def fullerenes(self, order, ipr=False, immutable=False):
17181718
command = shlex.quote(Buckygen().absolute_filename())
17191719
command += ' -' + ('I' if ipr else '') + 'd {0}d'.format(order)
17201720

1721-
sp = subprocess.Popen(command, shell=True,
1721+
with subprocess.Popen(command, shell=True,
17221722
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1723-
stderr=subprocess.PIPE, close_fds=True)
1723+
stderr=subprocess.PIPE, close_fds=True) as sp:
17241724

1725-
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
1725+
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
17261726

17271727
def fusenes(self, hexagon_count, benzenoids=False, immutable=False):
17281728
r"""
@@ -1806,11 +1806,11 @@ def fusenes(self, hexagon_count, benzenoids=False, immutable=False):
18061806
command = shlex.quote(Benzene().absolute_filename())
18071807
command += (' b' if benzenoids else '') + ' {0} p'.format(hexagon_count)
18081808

1809-
sp = subprocess.Popen(command, shell=True,
1809+
with subprocess.Popen(command, shell=True,
18101810
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1811-
stderr=subprocess.PIPE, close_fds=True)
1811+
stderr=subprocess.PIPE, close_fds=True) as sp:
18121812

1813-
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
1813+
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
18141814

18151815
def plantri_gen(self, options="", immutable=False):
18161816
r"""
@@ -1991,14 +1991,14 @@ def plantri_gen(self, options="", immutable=False):
19911991
import shlex
19921992
command = '{} {}'.format(shlex.quote(Plantri().absolute_filename()),
19931993
options)
1994-
sp = subprocess.Popen(command, shell=True,
1994+
with subprocess.Popen(command, shell=True,
19951995
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1996-
stderr=subprocess.PIPE, close_fds=True)
1996+
stderr=subprocess.PIPE, close_fds=True) as sp:
19971997

1998-
try:
1999-
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
2000-
except (TypeError, AssertionError):
2001-
raise AttributeError("invalid options '{}'".format(options))
1998+
try:
1999+
yield from graphs._read_planar_code(sp.stdout, immutable=immutable)
2000+
except (TypeError, AssertionError):
2001+
raise AttributeError("invalid options '{}'".format(options))
20022002

20032003
def planar_graphs(self, order, minimum_degree=None,
20042004
minimum_connectivity=None,

0 commit comments

Comments
 (0)