From a85936671aa7c6ce4802e9adabb47ba08de312f3 Mon Sep 17 00:00:00 2001 From: Lewis Goddard Date: Mon, 27 Jul 2026 07:58:31 +0100 Subject: [PATCH] FlatpakBackend: debounce external-change refreshes and trim after updates Each change to a flatpak installation fires many FileMonitor events, and every event queued a full reload of both AppStream pools. With no debounce and no re-entrancy guard, routine flatpak activity stacked reloads whose large transient allocations ratcheted RSS to a multi-hundred-MB high-water that glibc never returned, so an idle --silent daemon sat near a gigabyte. Coalesce the monitor events into a single refresh once activity settles, guard trigger_update_check so refreshes cannot stack, and malloc_trim() after each update check to hand the freed transients back to the OS. --- src/Core/FlatpakBackend.vala | 94 +++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/src/Core/FlatpakBackend.vala b/src/Core/FlatpakBackend.vala index c1efd07be..930baf193 100644 --- a/src/Core/FlatpakBackend.vala +++ b/src/Core/FlatpakBackend.vala @@ -17,6 +17,9 @@ * Authored by: David Hewitt */ +[CCode (cheader_filename = "malloc.h")] +extern int malloc_trim (size_t pad); + public class AppCenterCore.FlatpakPackage : Package { public weak Flatpak.Installation installation { public get; construct; } @@ -129,6 +132,10 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { private static GLib.FileMonitor user_installation_changed_monitor; private static GLib.FileMonitor system_installation_changed_monitor; + private uint installation_changed_timeout_id = 0; + private bool update_check_running = false; + private bool update_check_pending = false; + private uint total_operations; private int current_operation; @@ -295,21 +302,7 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { warning ("Couldn't user create Installation File Monitor : %s", e.message); } - user_installation_changed_monitor.changed.connect (() => { - if (!working) { - debug ("Flatpak user installation changed."); - - // Clear the installed state of all packages as something may have changed we weren't - // aware of - foreach (var package in package_list.values) { - if (package.state != Package.State.NOT_INSTALLED || package.installed) { - package.clear_installed (); - } - } - - trigger_update_check.begin (); - } - }); + user_installation_changed_monitor.changed.connect (() => queue_installation_changed_check ()); } else { warning ("Couldn't create user Installation File Monitor due to no installation"); } @@ -321,24 +314,10 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { warning ("Couldn't create system Installation File Monitor : %s", e.message); } - system_installation_changed_monitor.changed.connect (() => { - // Only trigger a cache refresh if we're not doing anything (i.e. its an external change) - if (!working) { - debug ("Flatpak system installation changed."); - - // Clear the installed state of all packages as something may have changed we weren't - // aware of - foreach (var package in package_list.values) { - if (package.state != Package.State.NOT_INSTALLED || package.installed) { - package.clear_installed (); - } - } - - // Reloads the appstream data for enabled remotes and checks what applications are - // installed/require updates - trigger_update_check.begin (); - } - }); + // Only trigger a cache refresh if we're not doing anything (i.e. its an external change). + // Reloads the appstream data for enabled remotes and checks what applications are + // installed/require updates. + system_installation_changed_monitor.changed.connect (() => queue_installation_changed_check ()); } else { warning ("Couldn't create system Installation File Monitor due to no installation"); } @@ -423,15 +402,50 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { }); } + private void queue_installation_changed_check () { + if (installation_changed_timeout_id != 0) { + Source.remove (installation_changed_timeout_id); + } + + installation_changed_timeout_id = Timeout.add_seconds_once (2, () => { + installation_changed_timeout_id = 0; + + debug ("Flatpak installation changed (debounced); triggering update check."); + + // Clear the installed state of all packages as something may have changed + // we weren't aware of + foreach (var package in package_list.values) { + if (package.state != Package.State.NOT_INSTALLED || package.installed) { + package.clear_installed (); + } + } + + trigger_update_check.begin (); + }); + } + private async void trigger_update_check () { - try { - yield refresh_cache (null); - } catch (Error e) { - warning ("Unable to refresh cache after external change: %s", e.message); + if (update_check_running) { + update_check_pending = true; + return; } - reload_installed_packages (); - yield get_updates (null); + update_check_running = true; + + do { + update_check_pending = false; + + try { + yield refresh_cache (null); + } catch (Error e) { + warning ("Unable to refresh cache after external change: %s", e.message); + } + + reload_installed_packages (); + yield get_updates (null); + } while (update_check_pending); + + update_check_running = false; } static construct { @@ -2067,6 +2081,8 @@ public class AppCenterCore.FlatpakBackend : Object, Backend { } fill_runtime_updates (); + + malloc_trim (0); } private void repair_internal (Job job) {