Skip to content

Power applet: memory leak when a Bluetooth device reports its battery #14026

Description

@hubyhuby

Summary

PopupMenuBase._connectItemSignals() (js/ui/popupMenu.js) connects four handlers on each menu item through this._signals. When the item is destroyed, the destroy handler disconnects activate, active-changed and sensitive-changed, but never its own destroy entry. That [ 'destroy', menuItem, callback, id ] entry stays in the parent menu's SignalManager._storage forever. It keeps the destroyed item, its actors, its GObjects and its closures reachable, so they are never garbage-collected.

Any applet that rebuilds its menu items leaks. The power applet makes this severe. It rebuilds all DeviceItems on every g-properties-changed of the csd-power proxy ([email protected]/applet.js:461 → _devicesChanged() → :715). With a Bluetooth mouse that reports its battery, csd-power emits PropertiesChanged about 6 times per second.

On my machine, cinnamon RssAnon grows about 6 MB/min while I work. At about 500 MB (RSS ~550 MB), I get 3–4 black flashes and then the whole session freezes. This happens about twice a day. Moving from 16 to 32 GB RAM changed nothing.

Environment

  • Linux Mint 22.3 (Zena), kernel 7.0.0-34
  • Cinnamon 6.6.9, cinnamon-settings-daemon 6.6.4, muffin 6.6.3, cjs 115.1, upower 1.90.3
  • Framework laptop (BAT1), Logitech MX Vertical over Bluetooth (HID++ 4.5, hidpp_battery_0)

Evidence

1. The power menu's SignalManager accumulates destroy entries. Counts are taken from the live session through org.Cinnamon.Eval:

applet menu _signals._storage.length of which destroy
power 15 349 → 15 411 in 20 s 15 317 → 15 379
network 62 14
sound 41 7
notifications 23 4

Every other signal type stays flat. Only destroy grows, by about 3 per second.

2. GJS heap dumps (imports.system.dumpHeap), 4 minutes apart, RssAnon 190 → 235 MB:

object type h1 h2 delta
Object 54 651 66 441 +11 790
GObject_Object 45 317 55 208 +9 891
Function _connectItemSignals/< 8 785 10 787 +2 002
GObject_Boxed 196 1 866 +1 670

The number of actors on the stage (2 487) and the item count in each applet menu stay constant. The leaked items are detached, but still referenced.

3. Who creates the items. I wrapped PopupMenuBase.prototype._connectItemSignals for 45 s and recorded each caller: 352 calls, all from [email protected]/applet.js:715 (_devicesChanged → GetDevicesRemote callback).

4. Trigger rate. dbus-monitor on org.cinnamon.SettingsDaemon.Power:

situation PropertiesChanged / 10 s content RssAnon growth
MX Vertical connected 57 Percentage alternating 55 ↔ 91 (mouse ↔ laptop battery) ~6 MB/min
Bluetooth on, mouse off (headset with no battery level) 10 Percentage 91 ↔ 93, Icon charging ↔ discharging ~1.5 MB/min
Bluetooth off 3 — ~flat

UPower itself is quiet. The oscillation comes from csd-power's aggregated Percentage/Icon, which flips between the mouse and the laptop battery. That looks like a second bug in csd-power.

Suggested fix

In _connectItemSignals, the destroy handler should also disconnect itself, for example:

this._signals.connect(menuItem, 'destroy', (emitter) => {
    this._signals.disconnect('activate', menuItem);
    this._signals.disconnect('active-changed', menuItem);
    this._signals.disconnect('sensitive-changed', menuItem);
    this._signals.disconnect('destroy', menuItem);   // missing
    ...
});

In the same handler, this._signals.disconnect('open-state-changed', this) looks wrong: it targets the parent menu instead of menuItem.menu.

Two optional hardening changes:

  • the power applet could skip rebuilding when the device list and its values have not changed, or debounce _devicesChanged;
  • csd-power should not alternate its aggregated Percentage between devices.

Relation to the original report (veth / Docker)

The earlier observation in this issue, a leak of about 0.4–0.6 MB per veth interface created by Docker, follows the same mechanism. The network applet rebuilds its device items on every NetworkManager device add/remove, and each destroyed item stays referenced through the same destroy entry. The power applet is simply the most frequent trigger.

Workaround

  • Remove the power applet from the panel, or disconnect Bluetooth battery-reporting devices.
  • As a safety net, set gsettings set org.cinnamon.launcher memory-limit 420 and gsettings set org.cinnamon.launcher check-frequency 30. Cinnamon then restarts cleanly before the freeze.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions