From 3ce6487914bc142c98d1f30773076996a079f7ac Mon Sep 17 00:00:00 2001 From: IlyaVrum Date: Sun, 23 Aug 2026 23:42:25 +0200 Subject: [PATCH] power applet: debounce device menu rebuilds _devicesChanged() is connected to the csd-power proxy's g-properties-changed and rebuilt the whole device menu (destroying and recreating every DeviceItem) on each signal. With a flapping AC adapter (loose plug or failing cable) csd-power emits a storm of signals - 126 in 3 minutes measured against ~18 at idle - and the flood of destroyed and recreated actors drives GJS into high-frequency incremental GC. While that GC runs, GJS blocks JS callbacks from C, including the shell's paint vfunc, so the entire screen freezes or goes black for seconds every time the charger blinks; destroy callbacks of notification banners get blocked too, leaving stuck banners floating over all windows. Debounce the rebuild: handle the first signal immediately and fold the rest of a burst into a single trailing rebuild 500ms later. An A/B test with a deliberately flapped charger went from 200 unpainted frames over 9s to under 30, and with this fix applied the blackouts are gone. Co-Authored-By: Claude Fable 5 --- .../applets/power@cinnamon.org/applet.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/files/usr/share/cinnamon/applets/power@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/power@cinnamon.org/applet.js index 4c98cb72d0..60b1d1ed93 100644 --- a/files/usr/share/cinnamon/applets/power@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/power@cinnamon.org/applet.js @@ -579,6 +579,33 @@ class CinnamonPowerApplet extends Applet.TextIconApplet { } _devicesChanged() { + // Coalesce bursts of change notifications into one rebuild per 500ms. + // csd-power emits g-properties-changed for every UPower device update; + // a flapping AC adapter (loose plug) produces a storm of them (measured + // 126 signals in 3 minutes), and rebuilding the device menu - destroying + // and recreating every DeviceItem - on each signal floods GJS with + // garbage. The resulting high-frequency GC blocks JS callbacks, + // including the shell's own paint path, so the whole screen goes black + // for seconds at a time (and pending actor destroys leak, leaving stuck + // notification banners). The first signal is handled immediately, the + // rest of the burst is folded into one trailing rebuild. + if (this._rebuildGate === undefined) + this._rebuildGate = 0; + if (this._rebuildGate > 0) { + this._rebuildDirty = true; + return; + } + this._rebuildDirty = false; + this._rebuildGate = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => { + this._rebuildGate = 0; + if (this._rebuildDirty) + this._devicesChanged(); + return GLib.SOURCE_REMOVE; + }); + this._devicesChangedReal(); + } + + _devicesChangedReal() { this._devices = []; this._primaryDevice = null;