Skip to content

Commit

Permalink
mirrors: Run speed tests as GTask async operations
Browse files Browse the repository at this point in the history
Quickly opening and closing the mirror selection dialog repeatedly has
a chance of causing a segfault. In addition, the following Gtk critical
error message is printed multiple times for every mirror that has not yet
been tested at the time that the dialog is closed.
Gtk-CRITICAL **: gtk_list_store_get_value: assertion 'iter_is_valid (iter, list_store)' failed.

This occurs because the mirrors model gets cleared without waiting for the
speed test thread to finish thus invalidating the remaining iterators.

The speed tests are now run using Gio Task async operations.
They are launched in new threads and when complete, the callback which
is invoked from the main thread checks if the task has been cancelled
and if so it does not modify the model or access the now invalid iterators.
  • Loading branch information
MavChtz committed Jan 21, 2025
1 parent 6670ed3 commit 3ead817
Showing 1 changed file with 41 additions and 36 deletions.
77 changes: 41 additions & 36 deletions usr/lib/linuxmint/mintSources/mintSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,6 @@

os.umask(0o022)

# Used as a decorator to run things in the background
def run_async(func):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread
return wrapper

# Used as a decorator to run things in the main loop, from another thread
def idle(func):
def wrapper(*args):
GLib.idle_add(func, *args)
return wrapper

def signal_handler(signum, _):
print("")
sys.exit(128 + signum)
Expand Down Expand Up @@ -502,6 +487,9 @@ def __init__(self, application, ui_builder):

self._mirrors_model.set_sort_column_id(MirrorSelectionDialog.MIRROR_SPEED_COLUMN, Gtk.SortType.DESCENDING)

# Since GtkListStore sorts the mirrors internally, we need a copy of the iterators in their original order
self._mirrors_iters = []

r = Gtk.CellRendererPixbuf()
col = Gtk.TreeViewColumn(_("Country"), r, pixbuf = MirrorSelectionDialog.MIRROR_COUNTRY_FLAG_COLUMN)
col.set_cell_data_func(r, self.data_func_surface)
Expand Down Expand Up @@ -542,6 +530,8 @@ def get_country(self, country_code):

def _update_list(self):
self._mirrors_model.clear()
self._mirrors_iters.clear()

for mirror in self.visible_mirrors:
if mirror.country_code == "WD":
flag = FLAG_PATH % '_united_nations'
Expand All @@ -565,7 +555,10 @@ def _update_list(self):
mirror.name
))

self._all_speed_tests()
iter = self._mirrors_model.get_iter_first()
while iter is not None:
self._mirrors_iters.append(iter)
iter = self._mirrors_model.iter_next(iter)

def get_url_last_modified(self, url):
try:
Expand Down Expand Up @@ -610,24 +603,6 @@ def check_mint_mirror_up_to_date(self, url):
def check_base_mirror_up_to_date(self, url):
return self.check_mirror_up_to_date(url, 14)

@run_async
def _all_speed_tests(self):
model_iters = [] # Don't iterate through iters directly.. we're modifying their orders..
iter = self._mirrors_model.get_iter_first()
while iter is not None:
model_iters.append(iter)
iter = self._mirrors_model.iter_next(iter)

for iter in model_iters:
try:
if iter is not None:
mirror = self._mirrors_model.get_value(iter, MirrorSelectionDialog.MIRROR_COLUMN)
if mirror in self.visible_mirrors:
url = self._mirrors_model.get_value(iter, MirrorSelectionDialog.MIRROR_URL_COLUMN)
self._speed_test (iter, url)
except Exception as e:
pass # null types will occur here...

def _get_speed_label(self, speed):
if speed > 0:
divider = (1024 * 1.0)
Expand All @@ -649,10 +624,11 @@ def _get_speed_label(self, speed):
represented_speed = ("0 %s") % _("kB/s")
return represented_speed

def _speed_test(self, iter, url):
def _speed_test_thread(self, task, source_object, task_data, cancellable):
download_speed = 0
try:
up_to_date = False
url = self.current_speed_test_mirror

if self.is_base:
test_url = "%s/dists/%s/main/binary-amd64/Packages.gz" % (url, self.codename)
Expand All @@ -679,9 +655,23 @@ def _speed_test(self, iter, url):
print ("Error '%s' on url %s" % (error, url))
download_speed = 0

task.return_value(download_speed)

def speed_test_finished_cb(self, source, task, iter):

# Get speed test result
if task.get_cancellable().is_cancelled():
del self._gtask
return
download_speed = task.propagate_value().value

# Add the result to the model
self.show_speed_test_result(iter, download_speed)

@idle
del self._gtask
# Run speed test for the next mirror
self._create_speed_test_gtask()

def show_speed_test_result(self, iter, download_speed):
if (iter is not None): # recheck as it can get null
if download_speed == -1:
Expand All @@ -694,6 +684,17 @@ def show_speed_test_result(self, iter, download_speed):
self._mirrors_model.set_value(iter, MirrorSelectionDialog.MIRROR_SPEED_COLUMN, download_speed)
self._mirrors_model.set_value(iter, MirrorSelectionDialog.MIRROR_SPEED_LABEL_COLUMN, self._get_speed_label(download_speed))

def _create_speed_test_gtask(self):
if not self._mirrors_iters:
return

iter = self._mirrors_iters.pop(0)
self._gtask = Gio.Task.new(self._dialog, Gio.Cancellable(), self.speed_test_finished_cb, iter)

# This must only be set here and read inside the speed test thread
self.current_speed_test_mirror = self._mirrors_model.get_value(iter, MirrorSelectionDialog.MIRROR_URL_COLUMN)
self._gtask.run_in_thread(self._speed_test_thread)

def run(self, mirrors, config, is_base):

self.config = config
Expand Down Expand Up @@ -795,6 +796,8 @@ def run(self, mirrors, config, is_base):
self.default_mirror_age = (now - self.default_mirror_date).days

self._update_list()
self._create_speed_test_gtask()

self._dialog.show_all()
retval = self._dialog.run()
if retval == Gtk.ResponseType.APPLY:
Expand All @@ -806,6 +809,8 @@ def run(self, mirrors, config, is_base):
res = None
else:
res = None

self._gtask.get_cancellable().cancel()
self._dialog.hide()
self._mirrors_model.clear()
return res
Expand Down

0 comments on commit 3ead817

Please sign in to comment.