Remove all timer deadlocks by using idle_add priority for them.

Add util.idle_timeout_add() to register timers with the same priority as
idle_add(). Change the IdleTimeout to also use the idle_add() priority.
This eliminates the chance of a timer blocking any idle_add from
running.

Change most timeout_add's to idle_timeout_add. Change the timer in
DownloadStatusModel::get_next() back to an idle_add.
This commit is contained in:
auouymous 2022-12-17 15:49:52 -07:00
parent d2f34d0d87
commit e1fc290ef2
5 changed files with 26 additions and 10 deletions

View File

@ -195,10 +195,7 @@ class DownloadStatusModel(Gtk.ListStore):
# as only the main thread is allowed to manipulate the list store.
def get_next(self):
dqr = DequeueRequest()
# this can not be idle_add because update_downloads_list() is called from a higher
# priority timeout_add and would spin forever, never calling this.
from gi.repository import GLib
GLib.timeout_add(0, self.__get_next, dqr)
util.idle_add(self.__get_next, dqr)
return dqr.dequeue()
def _work_gen(self):

View File

@ -50,6 +50,7 @@ class ProgressIndicator(object):
self._initial_message = None
self._initial_progress = None
self._progress_set = False
# use timeout_add, not util.idle_timeout_add, so it updates before Gtk+ redraws the dialog
self.source_id = GLib.timeout_add(self.DELAY, self._create_progress)
self.set_max_ticks(max_ticks)
@ -111,6 +112,7 @@ class ProgressIndicator(object):
self._update_gui()
# previous self.source_id timeout is removed when this returns False
# use timeout_add, not util.idle_timeout_add, so it updates before Gtk+ redraws the dialog
self.source_id = GLib.timeout_add(self.INTERVAL, self._update_gui)
return False

View File

@ -48,6 +48,7 @@ class SearchTree:
if self.search_box.get_property('visible'):
if self._search_timeout is not None:
GLib.source_remove(self._search_timeout)
# use timeout_add, not util.idle_timeout_add, so it updates the TreeView before background tasks
self._search_timeout = GLib.timeout_add(
self.config.ui.gtk.live_search_delay,
self.set_search_term, editable.get_chars(0, -1))

View File

@ -2431,7 +2431,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
if remaining_seconds > 3600:
# timeout an hour early in the event daylight savings changes the clock forward
remaining_seconds = remaining_seconds - 3600
GLib.timeout_add(remaining_seconds * 1000, self.refresh_episode_dates)
util.idle_timeout_add(remaining_seconds * 1000, self.refresh_episode_dates)
def update_podcast_list_model(self, urls=None, selected=False, select_url=None,
sections_changed=False):
@ -3821,8 +3821,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
interval = 60 * 1000 * self.config.auto.update.frequency
logger.debug('Setting up auto update timer with interval %d.',
self.config.auto.update.frequency)
self._auto_update_timer_source_id = GLib.timeout_add(
interval, self._on_auto_update_timer)
self._auto_update_timer_source_id = util.idle_timeout_add(interval, self._on_auto_update_timer)
def _on_auto_update_timer(self):
if self.config.check_connection and not util.connection_available():

View File

@ -1320,8 +1320,25 @@ def idle_add(func, *args):
func(*args)
def idle_timeout_add(milliseconds, func, *args):
"""Run a function in the main GUI thread at regular intervals, at idle priority
PRIORITY_HIGH -100
PRIORITY_DEFAULT 0 timeout_add()
PRIORITY_HIGH_IDLE 100
resizing 110
redraw 120
PRIORITY_DEFAULT_IDLE 200 idle_add()
PRIORITY_LOW 300
"""
if not gpodder.ui.gtk:
raise Exception('util.idle_timeout_add() is only supported by Gtk+')
from gi.repository import GLib
return GLib.timeout_add(milliseconds, func, *args, priority=GLib.PRIORITY_DEFAULT_IDLE)
class IdleTimeout(object):
"""Run a function in the main GUI thread at regular intervals since the last run
"""Run a function in the main GUI thread at regular intervals since the last run, at idle priority
A simple timeout_add() continuously calls the function if it exceeds the interval,
which lags the UI and prevents idle_add() calls from happening. This class restarts
@ -1333,13 +1350,13 @@ class IdleTimeout(object):
self.milliseconds = milliseconds
self.func = func
from gi.repository import GLib
self.id = GLib.timeout_add(milliseconds, self._callback, *args)
self.id = GLib.timeout_add(milliseconds, self._callback, *args, priority=GLib.PRIORITY_DEFAULT_IDLE)
def _callback(self, *args):
self.cancel()
if self.func(*args):
from gi.repository import GLib
self.id = GLib.timeout_add(self.milliseconds, self._callback, *args)
self.id = GLib.timeout_add(self.milliseconds, self._callback, *args, priority=GLib.PRIORITY_DEFAULT_IDLE)
def cancel(self):
if self.id: