From af9e3e2b3f05bff471681acbd2388ea149fbf070 Mon Sep 17 00:00:00 2001 From: richardsheridan Date: Wed, 14 Apr 2021 09:39:13 -0400 Subject: [PATCH 1/5] handle timeouts less than 1 millisecond --- lib/matplotlib/backends/_backend_tk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index ffb3b17d33cb..2c376291e131 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -358,8 +358,11 @@ def flush_events(self): def start_event_loop(self, timeout=0): # docstring inherited - if timeout > 0: - self._master.after(int(1000*timeout), self.stop_event_loop) + milliseconds = int(1000 * timeout) + if milliseconds > 0: + self._master.after(milliseconds, self.stop_event_loop) + elif milliseconds == 0: + self._master.after_idle(self.stop_event_loop) self._master.mainloop() def stop_event_loop(self): From dc093a569b7b7cf14d1240905906cc97d64fc7ce Mon Sep 17 00:00:00 2001 From: richardsheridan Date: Wed, 14 Apr 2021 09:40:14 -0400 Subject: [PATCH 2/5] cancel timeout if manually stopped --- lib/matplotlib/backends/_backend_tk.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 2c376291e131..1d927c7f5e0d 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -166,6 +166,7 @@ def __init__(self, figure=None, master=None, resize_callback=None): super().__init__(figure) self._idle = True self._idle_callback = None + self._event_loop_id = None w, h = self.figure.bbox.size.astype(int) self._tkcanvas = tk.Canvas( master=master, background="white", @@ -360,13 +361,16 @@ def start_event_loop(self, timeout=0): # docstring inherited milliseconds = int(1000 * timeout) if milliseconds > 0: - self._master.after(milliseconds, self.stop_event_loop) - elif milliseconds == 0: - self._master.after_idle(self.stop_event_loop) + self._event_loop_id = self._tkcanvas.after(milliseconds, self.stop_event_loop) + else: + self._event_loop_id = self._tkcanvas.after_idle(self.stop_event_loop) self._master.mainloop() def stop_event_loop(self): # docstring inherited + if self._event_loop_id: + self._master.after_cancel(self._event_loop_id) + self._event_loop_id = None self._master.quit() @@ -442,6 +446,8 @@ def destroy(*args): def destroy(self, *args): if self.canvas._idle_callback: self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback) + if self.canvas._event_loop_id: + self.canvas._tkcanvas.after_cancel(self.canvas._event_loop_id) # NOTE: events need to be flushed before issuing destroy (GH #9956), # however, self.window.update() can break user code. This is the From b92ecda9b3945589042fdb2c9be900ad1c4fab58 Mon Sep 17 00:00:00 2001 From: richardsheridan Date: Wed, 14 Apr 2021 12:37:34 -0400 Subject: [PATCH 3/5] respect timeout==0 -> no timeout --- lib/matplotlib/backends/_backend_tk.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 1d927c7f5e0d..620162395e7f 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -359,11 +359,12 @@ def flush_events(self): def start_event_loop(self, timeout=0): # docstring inherited - milliseconds = int(1000 * timeout) - if milliseconds > 0: - self._event_loop_id = self._tkcanvas.after(milliseconds, self.stop_event_loop) - else: - self._event_loop_id = self._tkcanvas.after_idle(self.stop_event_loop) + if timeout > 0: + milliseconds = int(1000 * timeout) + if milliseconds > 0: + self._event_loop_id = self._tkcanvas.after(milliseconds, self.stop_event_loop) + else: + self._event_loop_id = self._tkcanvas.after_idle(self.stop_event_loop) self._master.mainloop() def stop_event_loop(self): From 41bb1d45f11fcfe3de0a5c3ac97b694bb05daba9 Mon Sep 17 00:00:00 2001 From: richardsheridan Date: Wed, 14 Apr 2021 14:29:35 -0400 Subject: [PATCH 4/5] extra defensive delay to prevent race with draw_idle --- lib/matplotlib/backends/_backend_tk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 620162395e7f..539025b7c39b 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -461,7 +461,8 @@ def delayed_destroy(): if self._owns_mainloop and not Gcf.get_num_fig_managers(): self.window.quit() - self.window.after_idle(delayed_destroy) + # "after idle after 0" avoids Tcl error/race (GH #19940) + self.window.after_idle(self.window.after, 0, delayed_destroy) def get_window_title(self): return self.window.wm_title() From 9418774423c291411c76dc41ef6b9d1ade8c95a7 Mon Sep 17 00:00:00 2001 From: richardsheridan Date: Wed, 14 Apr 2021 14:39:24 -0400 Subject: [PATCH 5/5] linting --- lib/matplotlib/backends/_backend_tk.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 539025b7c39b..bc69e08aa396 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -362,9 +362,11 @@ def start_event_loop(self, timeout=0): if timeout > 0: milliseconds = int(1000 * timeout) if milliseconds > 0: - self._event_loop_id = self._tkcanvas.after(milliseconds, self.stop_event_loop) + self._event_loop_id = self._tkcanvas.after( + milliseconds, self.stop_event_loop) else: - self._event_loop_id = self._tkcanvas.after_idle(self.stop_event_loop) + self._event_loop_id = self._tkcanvas.after_idle( + self.stop_event_loop) self._master.mainloop() def stop_event_loop(self):