From da911546c68adc6cebabc5288d91fcf859edd487 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 7 Oct 2020 15:46:24 +0200 Subject: [PATCH] Further simplify pgf tmpdir cleanup. By carefully tearing down components of LatexManager in the right order (first the subprocess), we avoid the need for a global registry of things that need to be cleared at process termination. --- .../deprecations/18679-AL.rst | 3 + lib/matplotlib/backends/backend_pgf.py | 71 +++++++------------ 2 files changed, 30 insertions(+), 44 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/18679-AL.rst diff --git a/doc/api/next_api_changes/deprecations/18679-AL.rst b/doc/api/next_api_changes/deprecations/18679-AL.rst new file mode 100644 index 000000000000..417dd6a35a77 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/18679-AL.rst @@ -0,0 +1,3 @@ +``backend_pgf.TmpDirCleaner`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated, with no replacement. diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 2288397343a0..5c9ad776ec22 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -11,7 +11,6 @@ import shutil import subprocess import sys -import tempfile from tempfile import TemporaryDirectory import weakref @@ -200,7 +199,6 @@ class LatexManager: determining the metrics of text elements. The LaTeX environment can be modified by setting fonts and/or a custom preamble in `.rcParams`. """ - _unclean_instances = weakref.WeakSet() @staticmethod def _build_latex_header(): @@ -237,12 +235,6 @@ def _get_cached_or_new(cls): def _get_cached_or_new_impl(cls, header): # Helper for _get_cached_or_new. return cls() - @staticmethod - def _cleanup_remaining_instances(): - unclean_instances = list(LatexManager._unclean_instances) - for latex_manager in unclean_instances: - latex_manager._cleanup() - def _stdin_writeln(self, s): if self.latex is None: self._setup_latex_process() @@ -268,13 +260,10 @@ def _expect_prompt(self): return self._expect("\n*") def __init__(self): - # store references for __del__ - self._os_path = os.path - self._shutil = shutil - - # create a tmp directory for running latex, remember to cleanup - self.tmpdir = tempfile.mkdtemp(prefix="mpl_pgf_lm_") - LatexManager._unclean_instances.add(self) + # create a tmp directory for running latex, register it for deletion + self._tmpdir = TemporaryDirectory() + self.tmpdir = self._tmpdir.name + self._finalize_tmpdir = weakref.finalize(self, self._tmpdir.cleanup) # test the LaTeX setup to ensure a clean startup of the subprocess self.texcommand = mpl.rcParams["pgf.texsystem"] @@ -303,11 +292,21 @@ def __init__(self): self.str_cache = {} # cache for strings already processed def _setup_latex_process(self): - # open LaTeX process for real work + # Open LaTeX process for real work; register it for deletion. On + # Windows, we must ensure that the subprocess has quit before being + # able to delete the tmpdir in which it runs; in order to do so, we + # must first `kill()` it, and then `communicate()` with it. self.latex = subprocess.Popen( [self.texcommand, "-halt-on-error"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding="utf-8", cwd=self.tmpdir) + + def finalize_latex(latex): + latex.kill() + latex.communicate() + + self._finalize_latex = weakref.finalize( + self, finalize_latex, self.latex) # write header with 'pgf_backend_query_start' token self._stdin_writeln(self._build_latex_header()) # read all lines until our 'pgf_backend_query_start' token appears @@ -318,23 +317,6 @@ def _setup_latex_process(self): def latex_stdin_utf8(self): return self.latex.stdin - def _cleanup(self): - if not self._os_path.isdir(self.tmpdir): - return - try: - self.latex.communicate() - except Exception: - pass - try: - self._shutil.rmtree(self.tmpdir) - LatexManager._unclean_instances.discard(self) - except Exception: - sys.stderr.write("error deleting tmp directory %s\n" % self.tmpdir) - - def __del__(self): - _log.debug("deleting LatexManager") - self._cleanup() - def get_width_height_descent(self, text, prop): """ Get the width, total height and descent for a text typeset by the @@ -766,16 +748,25 @@ class GraphicsContextPgf(GraphicsContextBase): pass +@cbook.deprecated("3.4") class TmpDirCleaner: - remaining_tmpdirs = set() + _remaining_tmpdirs = set() + + @cbook._classproperty + @cbook.deprecated("3.4") + def remaining_tmpdirs(cls): + return cls._remaining_tmpdirs @staticmethod + @cbook.deprecated("3.4") def add(tmpdir): - TmpDirCleaner.remaining_tmpdirs.add(tmpdir) + TmpDirCleaner._remaining_tmpdirs.add(tmpdir) @staticmethod + @cbook.deprecated("3.4") + @atexit.register def cleanup_remaining_tmpdirs(): - for tmpdir in TmpDirCleaner.remaining_tmpdirs: + for tmpdir in TmpDirCleaner._remaining_tmpdirs: error_message = "error deleting tmp directory {}".format(tmpdir) shutil.rmtree( tmpdir, @@ -930,14 +921,6 @@ class _BackendPgf(_Backend): FigureCanvas = FigureCanvasPgf -def _cleanup_all(): - LatexManager._cleanup_remaining_instances() - TmpDirCleaner.cleanup_remaining_tmpdirs() - - -atexit.register(_cleanup_all) - - class PdfPages: """ A multi-page PDF file using the pgf backend