From f62867949fffdb3635dee8e71d52cfe9c619c8b2 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 17 May 2022 19:20:02 -0600 Subject: [PATCH 1/2] FIX: Decrease figure refcount on close of a macosx figure We need to take the refcount of the figuremanager down when we close a window. This was leaking figures before. --- src/_macosx.m | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index 30fdf8fbd75c..d31758b95483 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -193,7 +193,6 @@ @interface Window : NSWindow - (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; - (BOOL)closeButtonPressed; -- (void)dealloc; @end @interface View : NSView @@ -1239,19 +1238,9 @@ - (void)close /* This is needed for show(), which should exit from [NSApp run] * after all windows are closed. */ -} - -- (void)dealloc -{ - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); + // For each new window, we have incremented the manager reference, so + // we need to bring that down during close and not just dealloc. Py_DECREF(manager); - PyGILState_Release(gstate); - /* The reference count of the view that was added as a subview to the - * content view of this window was increased during the call to addSubview, - * and is decreased during the call to [super dealloc]. - */ - [super dealloc]; } @end From fe6366df0b5111e09f32feb7fb53a36d2b562f61 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Wed, 18 May 2022 19:31:41 -0600 Subject: [PATCH 2/2] FIX: macosx, clear pending timers when the figure is destroyed Running the macosx backend without calling show() would cause Timers to pile up and not fire because the event loop was not running. This leaked objects when closing/opening multiple figures. To fix this, we keep track of the timers on the canvas and remove all of the pending timers when destroying the figure to clear all back-references. --- lib/matplotlib/backends/backend_macosx.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index e914ea7ae7f6..204f81fd098b 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -37,6 +37,8 @@ def __init__(self, figure): super().__init__(figure=figure) self._draw_pending = False self._is_drawing = False + # Keep track of the timers that are alive + self._timers = set() def draw(self): """Render the figure and update the macosx canvas.""" @@ -59,14 +61,16 @@ def draw_idle(self): def _single_shot_timer(self, callback): """Add a single shot timer with the given callback""" - # We need to explicitly stop (called from delete) the timer after + # We need to explicitly stop and remove the timer after # firing, otherwise segfaults will occur when trying to deallocate # the singleshot timers. def callback_func(callback, timer): callback() - del timer + self._timers.remove(timer) + timer.stop() timer = self.new_timer(interval=0) timer.add_callback(callback_func, callback, timer) + self._timers.add(timer) timer.start() def _draw_idle(self): @@ -150,6 +154,14 @@ def _close_button_pressed(self): Gcf.destroy(self) self.canvas.flush_events() + def destroy(self): + # We need to clear any pending timers that never fired, otherwise + # we get a memory leak from the timer callbacks holding a reference + while self.canvas._timers: + timer = self.canvas._timers.pop() + timer.stop() + super().destroy() + @classmethod def start_main_loop(cls): _macosx.show()