From ba483b0e8b5cb390d13d92b7e8d19b33d730ca30 Mon Sep 17 00:00:00 2001 From: Mridul Seth Date: Thu, 12 Feb 2026 11:00:36 +0100 Subject: [PATCH] monkeypatch _mathtext.Parser.parse() with a threading.Lock() This is to prevent concurrent corruption of the parser singleton's mutable state. --- ipympl/backend_nbagg.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ipympl/backend_nbagg.py b/ipympl/backend_nbagg.py index 900c6048..0b083df8 100644 --- a/ipympl/backend_nbagg.py +++ b/ipympl/backend_nbagg.py @@ -60,6 +60,30 @@ from ._version import js_semver +# Workaround for thread-safety issues in matplotlib's mathtext parser. +# The _mathtext.Parser singleton has mutable state (_state_stack, etc.) that is +# not protected against concurrent access from multiple threads. When ipympl is +# used with ipykernel >= 7 (which introduces additional threads for message +# routing), concurrent calls to the parser can corrupt this state, leading to +# ParseException errors like 'Expected end of text, found "$"'. +# See https://github.com/matplotlib/ipympl/issues/610 +try: + import functools + + from matplotlib._mathtext import Parser as _MathTextInternalParser + + _mathtext_parse_lock = Lock() + _original_mathtext_parse = _MathTextInternalParser.parse + + @functools.wraps(_original_mathtext_parse) + def _thread_safe_mathtext_parse(self, *args, **kwargs): + with _mathtext_parse_lock: + return _original_mathtext_parse(self, *args, **kwargs) + + _MathTextInternalParser.parse = _thread_safe_mathtext_parse +except Exception: + pass + cursors_str = { cursors.HAND: 'pointer', cursors.POINTER: 'default',