Skip to content

Commit 364194e

Browse files
committed
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.
1 parent ffd6836 commit 364194e

File tree

1 file changed

+14
-2
lines changed

1 file changed

+14
-2
lines changed

lib/matplotlib/backends/backend_macosx.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def __init__(self, figure):
3939
super().__init__(figure=figure)
4040
self._draw_pending = False
4141
self._is_drawing = False
42+
# Keep track of the timers that are alive
43+
self._timers = set()
4244

4345
def draw(self):
4446
"""Render the figure and update the macosx canvas."""
@@ -61,14 +63,16 @@ def draw_idle(self):
6163

6264
def _single_shot_timer(self, callback):
6365
"""Add a single shot timer with the given callback"""
64-
# We need to explicitly stop (called from delete) the timer after
66+
# We need to explicitly stop and remove the timer after
6567
# firing, otherwise segfaults will occur when trying to deallocate
6668
# the singleshot timers.
6769
def callback_func(callback, timer):
6870
callback()
69-
del timer
71+
self._timers.remove(timer)
72+
timer.stop()
7073
timer = self.new_timer(interval=0)
7174
timer.add_callback(callback_func, callback, timer)
75+
self._timers.add(timer)
7276
timer.start()
7377

7478
def _draw_idle(self):
@@ -161,6 +165,14 @@ def _close_button_pressed(self):
161165
Gcf.destroy(self)
162166
self.canvas.flush_events()
163167

168+
def destroy(self):
169+
# We need to clear any pending timers that never fired, otherwise
170+
# we get a memory leak from the timer callbacks holding a reference
171+
while self.canvas._timers:
172+
timer = self.canvas._timers.pop()
173+
timer.stop()
174+
super().destroy()
175+
164176
@_api.deprecated("3.6")
165177
def close(self):
166178
return self._close_button_pressed()

0 commit comments

Comments
 (0)