Skip to content

Commit 72accb1

Browse files
authored
Merge pull request matplotlib#25470 from tacaswell/fix/donot_cache_exceptions
FIX: do not cache exceptions
2 parents 2d8d098 + 01390c5 commit 72accb1

File tree

4 files changed

+39
-8
lines changed

4 files changed

+39
-8
lines changed

lib/matplotlib/_api/__init__.py

Lines changed: 2 additions & 0 deletions
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.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def _get_running_interactive_framework():
6767
if frame.f_code in codes:
6868
return "tk"
6969
frame = frame.f_back
70+
# premetively break reference cycle between locals and the frame
71+
del frame
7072
macosx = sys.modules.get("matplotlib.backends._macosx")
7173
if macosx and macosx.event_loop_is_running():
7274
return "macosx"

lib/matplotlib/font_manager.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
# - 'light' is an invalid weight value, remove it.
2727

2828
from base64 import b64encode
29+
from collections import namedtuple
2930
import copy
3031
import dataclasses
3132
from functools import lru_cache
@@ -128,6 +129,7 @@
128129
'sans',
129130
}
130131

132+
_ExceptionProxy = namedtuple('_ExceptionProxy', ['klass', 'message'])
131133

132134
# OS Font paths
133135
try:
@@ -1288,8 +1290,8 @@ def findfont(self, prop, fontext='ttf', directory=None,
12881290
ret = self._findfont_cached(
12891291
prop, fontext, directory, fallback_to_default, rebuild_if_missing,
12901292
rc_params)
1291-
if isinstance(ret, Exception):
1292-
raise ret
1293+
if isinstance(ret, _ExceptionProxy):
1294+
raise ret.klass(ret.message)
12931295
return ret
12941296

12951297
def get_font_names(self):
@@ -1440,10 +1442,12 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
14401442
fallback_to_default=False)
14411443
else:
14421444
# This return instead of raise is intentional, as we wish to
1443-
# cache the resulting exception, which will not occur if it was
1445+
# cache that it was not found, which will not occur if it was
14441446
# actually raised.
1445-
return ValueError(f"Failed to find font {prop}, and fallback "
1446-
f"to the default font was disabled")
1447+
return _ExceptionProxy(
1448+
ValueError,
1449+
f"Failed to find font {prop}, and fallback to the default font was disabled"
1450+
)
14471451
else:
14481452
_log.debug('findfont: Matching %s to %s (%r) with score of %f.',
14491453
prop, best_font.name, best_font.fname, best_score)
@@ -1463,9 +1467,9 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
14631467
prop, fontext, directory, rebuild_if_missing=False)
14641468
else:
14651469
# This return instead of raise is intentional, as we wish to
1466-
# cache the resulting exception, which will not occur if it was
1470+
# cache that it was not found, which will not occur if it was
14671471
# actually raised.
1468-
return ValueError("No valid font could be found")
1472+
return _ExceptionProxy(ValueError, "No valid font could be found")
14691473

14701474
return _cached_realpath(result)
14711475

lib/matplotlib/tests/test_font_manager.py

Lines changed: 24 additions & 1 deletion
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)