Skip to content

Commit 08ed36d

Browse files
authored
Merge pull request #25474 from meeseeksmachine/auto-backport-of-pr-25470-on-v3.7.x
Backport PR #25470 on branch v3.7.x (FIX: do not cache exceptions)
2 parents 3ef2184 + 2958044 commit 08ed36d

File tree

4 files changed

+39
-8
lines changed

4 files changed

+39
-8
lines changed

lib/matplotlib/_api/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -385,4 +385,6 @@ def warn_external(message, category=None):
385385
frame.f_globals.get("__name__", "")):
386386
break
387387
frame = frame.f_back
388+
# premetively break reference cycle between locals and the frame
389+
del frame
388390
warnings.warn(message, category, stacklevel)

lib/matplotlib/cbook/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ def _get_running_interactive_framework():
8383
if frame.f_code in codes:
8484
return "tk"
8585
frame = frame.f_back
86+
# premetively break reference cycle between locals and the frame
87+
del frame
8688
macosx = sys.modules.get("matplotlib.backends._macosx")
8789
if macosx and macosx.event_loop_is_running():
8890
return "macosx"

lib/matplotlib/font_manager.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# - 'light' is an invalid weight value, remove it.
2525

2626
from base64 import b64encode
27+
from collections import namedtuple
2728
import copy
2829
import dataclasses
2930
from functools import lru_cache
@@ -126,6 +127,7 @@
126127
'sans',
127128
}
128129

130+
_ExceptionProxy = namedtuple('_ExceptionProxy', ['klass', 'message'])
129131

130132
# OS Font paths
131133
try:
@@ -1259,8 +1261,8 @@ def findfont(self, prop, fontext='ttf', directory=None,
12591261
ret = self._findfont_cached(
12601262
prop, fontext, directory, fallback_to_default, rebuild_if_missing,
12611263
rc_params)
1262-
if isinstance(ret, Exception):
1263-
raise ret
1264+
if isinstance(ret, _ExceptionProxy):
1265+
raise ret.klass(ret.message)
12641266
return ret
12651267

12661268
def get_font_names(self):
@@ -1411,10 +1413,12 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
14111413
fallback_to_default=False)
14121414
else:
14131415
# This return instead of raise is intentional, as we wish to
1414-
# cache the resulting exception, which will not occur if it was
1416+
# cache that it was not found, which will not occur if it was
14151417
# actually raised.
1416-
return ValueError(f"Failed to find font {prop}, and fallback "
1417-
f"to the default font was disabled")
1418+
return _ExceptionProxy(
1419+
ValueError,
1420+
f"Failed to find font {prop}, and fallback to the default font was disabled"
1421+
)
14181422
else:
14191423
_log.debug('findfont: Matching %s to %s (%r) with score of %f.',
14201424
prop, best_font.name, best_font.fname, best_score)
@@ -1434,9 +1438,9 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
14341438
prop, fontext, directory, rebuild_if_missing=False)
14351439
else:
14361440
# This return instead of raise is intentional, as we wish to
1437-
# cache the resulting exception, which will not occur if it was
1441+
# cache that it was not found, which will not occur if it was
14381442
# actually raised.
1439-
return ValueError("No valid font could be found")
1443+
return _ExceptionProxy(ValueError, "No valid font could be found")
14401444

14411445
return _cached_realpath(result)
14421446

lib/matplotlib/tests/test_font_manager.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from io import BytesIO, StringIO
2+
import gc
23
import multiprocessing
34
import os
45
from pathlib import Path
@@ -16,7 +17,7 @@
1617
json_dump, json_load, get_font, is_opentype_cff_font,
1718
MSUserFontDirectories, _get_fontconfig_fonts, ft2font,
1819
ttfFontProperty, cbook)
19-
from matplotlib import pyplot as plt, rc_context
20+
from matplotlib import pyplot as plt, rc_context, figure as mfigure
2021

2122
has_fclist = shutil.which('fc-list') is not None
2223

@@ -324,3 +325,25 @@ def test_get_font_names():
324325
assert set(available_fonts) == set(mpl_font_names)
325326
assert len(available_fonts) == len(mpl_font_names)
326327
assert available_fonts == mpl_font_names
328+
329+
330+
def test_donot_cache_tracebacks():
331+
332+
class SomeObject:
333+
pass
334+
335+
def inner():
336+
x = SomeObject()
337+
fig = mfigure.Figure()
338+
ax = fig.subplots()
339+
fig.text(.5, .5, 'aardvark', family='doesnotexist')
340+
with BytesIO() as out:
341+
with warnings.catch_warnings():
342+
warnings.filterwarnings('ignore')
343+
fig.savefig(out, format='png')
344+
345+
inner()
346+
347+
for obj in gc.get_objects():
348+
if isinstance(obj, SomeObject):
349+
pytest.fail("object from inner stack still alive")

0 commit comments

Comments
 (0)