Skip to content

Commit fe6366d

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 f628679 commit fe6366d

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
@@ -37,6 +37,8 @@ def __init__(self, figure):
3737
super().__init__(figure=figure)
3838
self._draw_pending = False
3939
self._is_drawing = False
40+
# Keep track of the timers that are alive
41+
self._timers = set()
4042

4143
def draw(self):
4244
"""Render the figure and update the macosx canvas."""
@@ -59,14 +61,16 @@ def draw_idle(self):
5961

6062
def _single_shot_timer(self, callback):
6163
"""Add a single shot timer with the given callback"""
62-
# We need to explicitly stop (called from delete) the timer after
64+
# We need to explicitly stop and remove the timer after
6365
# firing, otherwise segfaults will occur when trying to deallocate
6466
# the singleshot timers.
6567
def callback_func(callback, timer):
6668
callback()
67-
del timer
69+
self._timers.remove(timer)
70+
timer.stop()
6871
timer = self.new_timer(interval=0)
6972
timer.add_callback(callback_func, callback, timer)
73+
self._timers.add(timer)
7074
timer.start()
7175

7276
def _draw_idle(self):
@@ -150,6 +154,14 @@ def _close_button_pressed(self):
150154
Gcf.destroy(self)
151155
self.canvas.flush_events()
152156

157+
def destroy(self):
158+
# We need to clear any pending timers that never fired, otherwise
159+
# we get a memory leak from the timer callbacks holding a reference
160+
while self.canvas._timers:
161+
timer = self.canvas._timers.pop()
162+
timer.stop()
163+
super().destroy()
164+
153165
@classmethod
154166
def start_main_loop(cls):
155167
_macosx.show()

0 commit comments

Comments
 (0)