Skip to content

Commit 855bb6f

Browse files
committed
Fix a race condition in TexManager.make_dvi.
Previously, a race condition could occur if, while a process had called make_tex (generating the tex file in the global cache) and was going to call the latex subprocess (to generate the dvi file), another process also called make_tex for the same tex string and started rewriting the tex source. In that case, the latex subprocess could see a partially written (invalid) tex source. Fix that by generating the tex source in a process-private temporary directory, where the latex process is already going to run anyways. (This is cheap compared to the latex subprocess invocation.)
1 parent 780e66c commit 855bb6f

File tree

1 file changed

+12
-11
lines changed

1 file changed

+12
-11
lines changed

lib/matplotlib/texmanager.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,9 @@ def make_dvi(cls, tex, fontsize):
281281
282282
Return the file name.
283283
"""
284-
basefile = cls.get_basefile(tex, fontsize)
285-
dvifile = '%s.dvi' % basefile
286-
if not os.path.exists(dvifile):
287-
texfile = Path(cls.make_tex(tex, fontsize))
288-
# Generate the dvi in a temporary directory to avoid race
284+
dvifile = Path(cls.get_basefile(tex, fontsize)).with_suffix(".dvi")
285+
if not dvifile.exists():
286+
# Generate the tex and dvi in a temporary directory to avoid race
289287
# conditions e.g. if multiple processes try to process the same tex
290288
# string at the same time. Having tmpdir be a subdirectory of the
291289
# final output dir ensures that they are on the same filesystem,
@@ -294,15 +292,18 @@ def make_dvi(cls, tex, fontsize):
294292
# the absolute path may contain characters (e.g. ~) that TeX does
295293
# not support; n.b. relative paths cannot traverse parents, or it
296294
# will be blocked when `openin_any = p` in texmf.cnf).
297-
cwd = Path(dvifile).parent
295+
cwd = dvifile.parent
298296
with TemporaryDirectory(dir=cwd) as tmpdir:
299-
tmppath = Path(tmpdir)
297+
Path(tmpdir, "file.tex").write_text(
298+
cls._get_tex_source(tex, fontsize), encoding='utf-8')
300299
cls._run_checked_subprocess(
301300
["latex", "-interaction=nonstopmode", "--halt-on-error",
302-
f"--output-directory={tmppath.name}",
303-
f"{texfile.name}"], tex, cwd=cwd)
304-
(tmppath / Path(dvifile).name).replace(dvifile)
305-
return dvifile
301+
"file.tex"], tex, cwd=tmpdir)
302+
Path(tmpdir, "file.dvi").replace(dvifile)
303+
# Also move the tex source to the main cache directory, but
304+
# only for backcompat.
305+
Path(tmpdir, "file.tex").replace(dvifile.with_suffix(".tex"))
306+
return str(dvifile)
306307

307308
@classmethod
308309
def make_png(cls, tex, fontsize, dpi):

0 commit comments

Comments
 (0)