[GH-ISSUE #19] Memory leak #9

Closed
opened 2026-05-22 22:04:47 +01:00 by JakeStanger · 8 comments
Owner

Originally created by @Levizor on GitHub (Feb 13, 2025).
Original GitHub issue: https://github.com/JakeStanger/system-tray/issues/19

Issues related:
https://github.com/way-edges/way-edges/issues/95
https://github.com/JakeStanger/system-tray/issues/15
https://github.com/JakeStanger/system-tray/pull/17

Hello.
I've thought that https://github.com/JakeStanger/system-tray/pull/17 fixed it but apparently there is more than meets the eye and the problem is not only with the Client::items HashMap.
I've done a little investigation.

Here's the code:

use std::io::{self, Write};
use std::process::Command;
use std::{thread::sleep, time::Duration};
use system_tray::client::Client;

#[tokio::main]
async fn main() {
    let _client = Client::new().await.unwrap();

    // loop to print memory usage each 100ms
    let pid = std::process::id();
    loop {
        let output = Command::new("ps")
            .arg("-p")
            .arg(pid.to_string())
            .arg("-o")
            .arg("rss")
            .output()
            .expect("failed to execute process");
        let result = String::from_utf8_lossy(&output.stdout);
        print!("\r{} KB", result.lines().nth(1).unwrap());
        io::stdout().flush().unwrap();

        sleep(Duration::from_millis(100));
    }
}

It creates client and loops with printing process memory usage.

Now, when we open and close apps that add an item to, memory usage increases. Doing it a few times - more memory used. The list of applications used in testing:

  • telegram-desktop
  • vesktop (high memory usage and high spike at startup)
  • steam
  • ytm-desktop
  • discord (canary build)
  • qbittorrent (interesting behavious, sometimes frees a bit of memory usage).

The problem is not drastic when application doesn't add much, but for example vesktop adds up to 10 Mb per opening. It also has some huge spikes in memory usage (up to 500 Mb) when starting the application, than it goes down, but 10 Mb is added on top.

I've continued investigation and tried a different application, specifically waybar.
And well... Issue is present there as well, though it's somehow leveraged.
Closing application doesn't free memory there, which also sounds like a bug. But at least reopening the applications doesn't increase memory usage (most times, it's not deterministic for some reason).

It would be great if people interested in solving the issue tested the code above and shared the results.
Seeing this leak in other application that implement system tray I suspect that it may be some problem with shared internals, maybe DBus (I have no clue).

Originally created by @Levizor on GitHub (Feb 13, 2025). Original GitHub issue: https://github.com/JakeStanger/system-tray/issues/19 Issues related: https://github.com/way-edges/way-edges/issues/95 https://github.com/JakeStanger/system-tray/issues/15 https://github.com/JakeStanger/system-tray/pull/17 Hello. I've thought that https://github.com/JakeStanger/system-tray/pull/17 fixed it but apparently there is more than meets the eye and the problem is not only with the Client::items HashMap. I've done a little investigation. Here's the code: ``` use std::io::{self, Write}; use std::process::Command; use std::{thread::sleep, time::Duration}; use system_tray::client::Client; #[tokio::main] async fn main() { let _client = Client::new().await.unwrap(); // loop to print memory usage each 100ms let pid = std::process::id(); loop { let output = Command::new("ps") .arg("-p") .arg(pid.to_string()) .arg("-o") .arg("rss") .output() .expect("failed to execute process"); let result = String::from_utf8_lossy(&output.stdout); print!("\r{} KB", result.lines().nth(1).unwrap()); io::stdout().flush().unwrap(); sleep(Duration::from_millis(100)); } } ``` It creates client and loops with printing process memory usage. Now, when we open and close apps that add an item to, memory usage increases. Doing it a few times - more memory used. The list of applications used in testing: - telegram-desktop - vesktop (high memory usage and high spike at startup) - steam - ytm-desktop - discord (canary build) - qbittorrent (interesting behavious, sometimes frees a bit of memory usage). The problem is not drastic when application doesn't add much, but for example vesktop adds up to 10 Mb per opening. It also has some huge spikes in memory usage (up to 500 Mb) when starting the application, than it goes down, but 10 Mb is added on top. I've continued investigation and tried a different application, specifically [waybar](https://github.com/Alexays/Waybar). And well... Issue is present there as well, though it's somehow leveraged. Closing application doesn't free memory there, which also sounds like a bug. But at least reopening the applications doesn't increase memory usage (most times, it's not deterministic for some reason). It would be great if people interested in solving the issue tested the code above and shared the results. Seeing this leak in other application that implement system tray I suspect that it may be some problem with shared internals, maybe DBus (I have no clue).
Author
Owner

@JakeStanger commented on GitHub (Feb 13, 2025):

It's unlikely D-Bus as that just serves as a bridge/protocol and won't (I hope at least) cling onto any memory.

I've not tested anything myself yet but I do have a couple of theories.

Generally allocated data structures (HashMap, HashSet, String) will only ever grow. This presents two possible causes:

  • When you remove an item (ie close a program), it is removed from the map but the memory remains allocated. Re-opening the program gives its tray item a different d-bus address, so it's added to a different key in the map. Logically this should re-use the already allocate memory, but I guess there could be issues with memory fragmentation (?).
  • When the map is emptied (ie Ironbar takes over from another host), that space will stay allocated. Same as removing items but on a larger scale.

Alternatively, this could be related to libdbusmenu. Are you using that feature?

I'd also be interested to see if #18 with the data feature disabled helps?

<!-- gh-comment-id:2657648443 --> @JakeStanger commented on GitHub (Feb 13, 2025): It's unlikely D-Bus as that just serves as a bridge/protocol and won't (I hope at least) cling onto any memory. I've not tested anything myself yet but I do have a couple of theories. Generally allocated data structures (HashMap, HashSet, String) will only ever grow. This presents two possible causes: - When you remove an item (ie close a program), it is removed from the map but the memory remains allocated. Re-opening the program gives its tray item a different d-bus address, so it's added to a different key in the map. Logically this *should* re-use the already allocate memory, but I guess there could be issues with memory fragmentation (?). - When the map is emptied (ie Ironbar takes over from another host), that space will stay allocated. Same as removing items but on a larger scale. Alternatively, this could be related to `libdbusmenu`. Are you using that feature? I'd also be interested to see if #18 with the data feature disabled helps?
Author
Owner

@Levizor commented on GitHub (Feb 13, 2025):

I don't use any features. I've tried disabling data feature in https://github.com/JakeStanger/system-tray/pull/18. Unfortunately, no impact.

<!-- gh-comment-id:2657943227 --> @Levizor commented on GitHub (Feb 13, 2025): I don't use any features. I've tried disabling data feature in https://github.com/JakeStanger/system-tray/pull/18. Unfortunately, no impact.
Author
Owner

@ogios commented on GitHub (Feb 14, 2025):

>I've tried disabling data feature in #18. Unfortunately, no impact.

you mean memory still goes up even if we don't cache the menu in client?
that's odd

<!-- gh-comment-id:2657962071 --> @ogios commented on GitHub (Feb 14, 2025): >I've tried disabling data feature in #18. Unfortunately, no impact. you mean memory still goes up even if we don't cache the menu in client? that's odd
Author
Owner

@Levizor commented on GitHub (Feb 14, 2025):

  • When you remove an item (ie close a program), it is removed from the map but the memory remains allocated. Re-opening the program gives its tray item a different d-bus address, so it's added to a different key in the map. Logically this should re-use the already allocate memory, but I guess there could be issues with memory fragmentation (?).

That seems to be the cause. I've conducted a little experiment: created 80 Mb usage with the code I showed earlier and made my laptop endure as much as it can opening new tabs in chrome browser. When memory usage rose to 80% - the program freed memory and went down to 8 MB. So yeah, it seems like default memory allocator is quite lazy and doesn't free memory until system is in need. Sorry for the hustle.

Another thing that I did is launching and killing memory greedy application, such as vesktop. Memory could not spike higher than 140 Mb or so. I still find it all strange, not having experienced this in my life, but I guess it's just as it is.

Probably there is more strict memory allocator in c++, cause waybar was reusing memory more often. Do you think it's possible incorporating any techniques to leverage that? At this point I understand that it's not a memory leak nor a real issue, but I still don't like seeing high numbers on a simple tray app 🙁.

<!-- gh-comment-id:2658016027 --> @Levizor commented on GitHub (Feb 14, 2025): > * When you remove an item (ie close a program), it is removed from the map but the memory remains allocated. Re-opening the program gives its tray item a different d-bus address, so it's added to a different key in the map. Logically this _should_ re-use the already allocate memory, but I guess there could be issues with memory fragmentation (?). That seems to be the cause. I've conducted a little experiment: created 80 Mb usage with the code I showed earlier and made my laptop endure as much as it can opening new tabs in chrome browser. When memory usage rose to 80% - the program freed memory and went down to 8 MB. So yeah, it seems like default memory allocator is quite lazy and doesn't free memory until system is in need. Sorry for the hustle. Another thing that I did is launching and killing memory greedy application, such as vesktop. Memory could not spike higher than 140 Mb or so. I still find it all strange, not having experienced this in my life, but I guess it's just as it is. Probably there is more strict memory allocator in c++, cause waybar was reusing memory more often. Do you think it's possible incorporating any techniques to leverage that? At this point I understand that it's not a memory leak nor a real issue, but I still don't like seeing high numbers on a simple tray app 🙁.
Author
Owner

@ogios commented on GitHub (Feb 14, 2025):

try mimalloc?
idk i planned but haven't done that

<!-- gh-comment-id:2658024565 --> @ogios commented on GitHub (Feb 14, 2025): try mimalloc? idk i planned but haven't done that
Author
Owner

@Levizor commented on GitHub (Feb 14, 2025):

try mimalloc? idk i planned but haven't done that

Thank you for the recommendation. But in my case it does things even worse, leaving 500 Mb not freed.
Maybe it should be configured properly to allow for better result, I just used the defaults.

<!-- gh-comment-id:2658063082 --> @Levizor commented on GitHub (Feb 14, 2025): > try mimalloc? idk i planned but haven't done that Thank you for the recommendation. But in my case it does things even worse, leaving 500 Mb not freed. Maybe it should be configured properly to allow for better result, I just used the defaults.
Author
Owner

@JakeStanger commented on GitHub (Feb 14, 2025):

There is a shrink_to_fit method on collections which reduces their capacity as much as possible. This could be called after removing an item. It can be quite slow though, as worst case the entire structure needs to be moved elsewhere in memory. Possibly worth trying if you're concerned about reducing the memory footprint as much as possible; would confirm the theory too.

Alternatively this could be called only when the map is emptied.

<!-- gh-comment-id:2658623370 --> @JakeStanger commented on GitHub (Feb 14, 2025): There is a `shrink_to_fit` method on collections which reduces their capacity as much as possible. This could be called after removing an item. It can be quite slow though, as worst case the entire structure needs to be moved elsewhere in memory. Possibly worth trying if you're concerned about reducing the memory footprint as much as possible; would confirm the theory too. Alternatively this could be called only when the map is emptied.
Author
Owner

@Levizor commented on GitHub (Feb 14, 2025):

There is a shrink_to_fit method on collections which reduces their capacity as much as possible. This could be called after removing an item. It can be quite slow though, as worst case the entire structure needs to be moved elsewhere in memory. Possibly worth trying if you're concerned about reducing the memory footprint as much as possible; would confirm the theory too.

Alternatively this could be called only when the map is emptied.

For some reason I don't see any impact from using shrink_to_fit on the map. Maybe there are other data structures used inside the library? After all, even having data feature disabled in https://github.com/JakeStanger/system-tray/pull/18 there was no change in memory growing.

<!-- gh-comment-id:2658947999 --> @Levizor commented on GitHub (Feb 14, 2025): > There is a `shrink_to_fit` method on collections which reduces their capacity as much as possible. This could be called after removing an item. It can be quite slow though, as worst case the entire structure needs to be moved elsewhere in memory. Possibly worth trying if you're concerned about reducing the memory footprint as much as possible; would confirm the theory too. > > Alternatively this could be called only when the map is emptied. For some reason I don't see any impact from using `shrink_to_fit` on the map. Maybe there are other data structures used inside the library? After all, even having data feature disabled in https://github.com/JakeStanger/system-tray/pull/18 there was no change in memory growing.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
JakeStanger/system-tray#9
No description provided.