-
-
Notifications
You must be signed in to change notification settings - Fork 326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Possible memory leak in Disks::refresh_list
#1282
Comments
It means that Is it on mac M* or an older one? If it's on mac M*, I can't test it as I don't have one. |
I'm experiencing it on an M*, but I think others have experienced it on Intel. Let me try find an Intel to run the example above on and see if it persists. In the meantime, I downgraded to |
I've been able to confirm that this behaviour does still occur on an Intel based Mac |
Then I'll be able to check what's going on locally. Thanks for the information! |
I just checked and... I have absolutely no idea where the leak comes from... To be honest, I'm pretty bad at using mac tools to try to check memory issues since they don't have valgrind and their other tools are confusing. So for now, I'll consider it's "ok". Don't hesitate to give it a try though, help to fix this issue would be very welcome! |
Ok, if you leave the issue open, I will try circle back to it and see what I can find. Do you know of some way that I can work around the issue for now? Is there some way that I can instantiate the library in a way that forces it to clear the memory? |
It seems to be specific to the running process (or maybe the current thread? To be confirmed). So unless you get this information from another process/thread, I don't see how. |
Good suggestion, spawning a new thread does the trick. The underlying code still needs to be fixed, but the code below prevents the memory from growing past one invocation's worth: use std::thread::sleep;
use sysinfo::{Disks, MINIMUM_CPU_UPDATE_INTERVAL};
fn main() {
// Memory leak when using `refresh_list`:
loop {
let handle = std::thread::spawn(move || {
let mut disks = Disks::new_with_refreshed_list();
disks.refresh_list();
disks.refresh();
disks.first().map(|disk| {
let available_space = disk.available_space();
println!("{available_space}");
});
sleep(MINIMUM_CPU_UPDATE_INTERVAL);
});
let _ = handle.join();
}
} |
It'd be much simpler if we could find out where the leaked memory comes from. If anyone knows a good memory debugger for mac... |
In this comment, I used In the Instruments application, on the left side, you can change from Then, you can traverse the tree and see where the memory is being allocated from. In the comment above, you can see in my case the lowest level user-space call was I imagine the solution would involve calling |
Thanks for the detailed explanations! I'll give it another try when I have time. |
System: macOS Sonoma 14.5 It seems no problem. If I run the code below for a long time, I notice after the memory grows to 50M, but will drop back to about 10M, and then increase and drop again. loop {
let _ = Disks::new_with_refreshed_list();
sleep(MINIMUM_CPU_UPDATE_INTERVAL);
} |
Hi everyone. I'm the author of the code in question here. During its original development I spent a lot of time making sure there were no I actually had to deal with a very similar problem to this in my crate The root cause problem here is that Objective-C implicitly autoreleases a lot, and often caches will take advantage of this for automatic cleanup. The part that tipped me off here was this example spawning threads to remove the leak. On macOS every thread has its own autorelease pool stack, which its responsible for draining. In "normal" macOS apps, the main thread event loop does this for you implicitly. In Rust... it does not. This means that if you have a single thread calling the same function extremely fast with autoreleasing objects somewhere in the callstack, you get what appears to be a memory leak. But, once the To demonstrate this, you can use one of the original reproducers and wrap it in a new autorelease pool per loop, which will be drained when the scope ends (at the end of each loop iteration): let mut disks = Disks::new_with_refreshed_list();
loop {
objc2::rc::autoreleasepool(|_pool| {
disks.refresh_list();
disks.refresh();
std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
})
} See the with/without autorelease pool for allocation traces: With the explanation done, how should this be fixed? IMO calling refresh disks fast enough to incur this is pathological behavior and not something everyone will do, so adding an autorelease pool to |
Thanks for the very detailed explanations! So if I understand correctly, getting disk information in a thread would allow for the extra resources to be cleared once the thread is removed, right? That would be an acceptable solution. Otherwise, not doing anything would be acceptable too. |
Yes, that is correct 👍. At the cost of some unsafe code (or an |
I'd rather just use some unsafe code directly. To benchmark it, you can add a new benchmark for it in |
Thanks @complexspaces for the detailed response! Just to add a response to this portion of the comment:
I only used a tight loop to exacerbate the issue, it would normally only be called every 15 minutes in our application and the application would crash after a couple days. I don't think this use case for the library is too foreign, for example, a toolbar application that has a thread to check disk usage seems like it would be a fairly common use case. Either way, we will pursue the |
The fact that the application crashes after a few days is not acceptable for me. Rust now provides |
I agree, a crash every few days is more problematic. Please have a look at #1344, which implements the autorelease pool solution I proposed earlier. I would appreciate a smoke test of that branch if anyone has time too. |
Published the new version with this fix. |
Thanks for the work on this! I'll put it through testing on our side 🙂 |
Describe the bug
System:
macOS Sonoma 14.1.2 (23B92)
sysinfo
version:0.30.12
When calling
Disks::refresh_list()
repeatedly in a loop, the memory the application uses grows without bound. Variations shown in the minimum reproducible example below:To Reproduce
The text was updated successfully, but these errors were encountered: