-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Memory Leak #3198
Comments
What makes you think this is an issue with actix-web? Could you please provide a minimal reproducible example of this? |
code
|
cargo.toml
|
I also can reproduce increased memory usage both with debug and release mode. Project - actix.zip Maybe this is problem with memory allocator, that not have enough pressure to clear memory and do this only when necessary(for me, when I got ~300). simplescreenrecorder-2023-11-23_12.35.54.mp4Valgrind not show any memory leaks
It is possible that data passed to body function |
I don't think that's the problem |
code
Still not released, but if data is not returned, it can be automatically released
Still not released, but if data is not returned, it can be automatically released |
you say I also can reproduce increased memory usage both with debug and release mode.
you say Maybe this is problem with memory allocator, that not have enough pressure to clear memory and do this only when necessary(for me, when I got ~300). |
I am using actix as well as a backend web server. And it seems like it is leaking memory. Even though the server is not receiving incoming request, the memory used never goes down and keep increasing upon new incoming request. I am seeing the same behavior (actix-web = "4.4.0"). I am using a tool for memory leak called HeapTrack, and here are some figures below. It seems like there is no deallocation of the used memory even after hours of idle period. According to HeapTrack, that memory peak allocation is coming from Actix Http Dispatcher. See the figures below for more details: |
Does anyone explain? Does it mean that Rust is not used by anyone in the production environment? It looks like it's too weak and cannot be truly used |
I would not state about the language. i would state about the specific library we're using. We should understand the problem and have feedbacks about why this is happening. there will be an explanation for sure about why Actix Heap memory keeps growing. No programming language is safe from Memory leak if you have a long running thread that keeps a memory reference alive. Rust can't decide to clear a variable reference if it is being used. This issue here has nothing to do with rust. Rust is used in many prod environments. I have seen it myself in action in one of the organizations i was working for. Let's focus instead on the problem instead of having let go with opinionated view about a programming language. |
You overthink it. I'm definitely using it because I trust and value it. Of course, I hope it's better, not because I have any bias against Rust |
Rust is just a tool, if you used it not properly it will yield undesirable effects. it's just what needed to be understood here. Hope we have an answer from the main devs of this framework. Actix is great. |
By the way, I also use the Same Actix.zip file, and i am able to reproduce the memory leak with a rust load testing tool called drill. cargo install drill
drill --benchmark benchmark.yml Here is a heaptrack memory dump you can use to analyze with heaptrack along with the project. I see the memory is growing at the same component location as mine which is Actix Http Dispatcher. It clearly shows that Actix Web is leaking 5GB of memory when launching drill upon "/check" endpoint in this example. The memory leak is really relevant and should be adressed with a Pull Request.
I al |
Yes, what you said makes sense. I have also posted on the forum before and asked about it. Most people mean that Rust releases memory, but the system memory collector did not trigger the collection or did not reach the pressure point to release this part of the memory. At the same time, this part of the memory is expected to be used in the future to avoid duplicate applications and releases |
Yes, your observation is right. and I also try to use "actix::main" wrapper instead of "tokio::main" , and it yields the same. I think, there may be leaked references. Rust ensures from compilation that active memory references are still valid. So, from a developer perspective, you may think a variable is not used. but, in the underlying system, there may be an active reference pointing to it. and being used by a sleeping thread. Actix is running on daemon mod, the variable lifecycle is bound to the threads maintaining alive by Tokio. On runtime, if there is any active reference pointing to a heap memory location, there can be memory Leaks. and rust official documentation states that as well with the use of RefCell smart pointers there can be memory leak if not being used properly. There is no where in rust documentation that they mention there can't be memory leak. |
This issue may be related to this problem - rust-lang/rust#73307 |
@qarmin Thanks for pointing us to one similar issue opened at Rust lang. It seems like the issue is closed without code change. or any sort of solution on how to fix it. |
I was thinking that this should be a very, very common question, but there is very little relevant information |
I agree. On Mac OS, my memory seems to grow between 29M-30M and may return to 29M after a few attempts |
This request output shows that res was dropped after use |
It seems it has to do with the underlying system allocator as well as argued in the issue closed by one of the rust maintainer. Memory issue is being seen with some object types like Hashmap or vectors because they reside on the Heap and not on the stack. And actix web as well is doing some heavy lifting using those types as wells. |
@zqlpaopao Thanks once again for this link above about the issue which is opened to Rust lang itself. LD_PRELOAD=/usr/lib/libjemalloc.so.2 cargo run --release Without having to use memory allocator crate that will grow my executable after compilation, it really performs well and my actix web application responds even better under heavy load testing with 8000 concurrent connections with 200 000 http requests that makes access to database to return info. I even use it as well under alpine linux. require to set the environment variable for your container with the one above such that: LD_PRELOAD=/usr/lib/libjemalloc.so.2 cargo That should help a bit those who are struggling with their heap memory that grows(especially under linux/unix like system). |
Thank you for the experiment and answers. Below are my test results If I don't specify the size of the memory allocator startup to be around 7.1m
If I specify
I have made 100 concurrent requests and my memory is growing After the request ends, the memory returns to a result that is not much different from the initial value |
@zqlpaopao And it seems like all system memory allocators are trying to solve one common problem which is "Memory Fragmentation". And i stumble on upon a blog From CloudFlare team that was dealing with a similar issue we're dealing with. And they really explain it very clear. Google has implemented a memory allocator called TCMalloc. And it seems that it solves their problem. |
I did a test with libtcmalloc.so as LD_PRELOAD. it doesn't perform better than libjemalloc.so in my case. For my scenario with actix jemalloc really performs very well. This is a view of my process and memory consumption and deallocation in real time seen with atop linux utility. To have this view on atop, you have to press "m" to toggle the memory usage panel view. With Jemalloc it really decreases my memory by looking at the VGROW and RGROW field with minus values "-0.2G" and "-0.1G" i.e reduces the virtual memory to 200M and the RAM to 100M. Jemalloc perform better for my case by launching my app with LD_PRELOAD=/usr/lib/libjemalloc.so ./my_application Feel free to use the malloc implementation that performs better in your case either the default one provided by your Operating Systems(Linux Glibc Malloc), or a custom one like TCMalloc(from Google), Jemalloc and so on. Jemalloc is being maintained by Facebook. |
I have a requirement to run an agent monitoring program on Linux, but the requirement is that the memory cannot exceed 100mb, otherwise it will be killed. Therefore, I have encountered two problems: if libjemalloc is used, the memory will be a bit high, and for single threads, it still needs to reach 200mb+. TCmalloc has not been tested yet, but from your test screenshot, it is not low. If glibc is used, the initialization memory is not high, but it continues to increase, and it will also be killed. Is there a method for initializing the memory size under the control of libjemalloc, which would be perfect |
@zqlpaopao I am wondering by curiosity what's your target hardware. is it an embedded system or a bare metal , Virtual machine, a Container deployed under Kubernetes with such limitation about memory. Or is this program will be running under a LXC cgroup which limits the maximum memory. In either scenario, i don't think a memory allocator is gonna solve your problem. With that requirement, it means that you really need to have control over the memory usage. Allocators behavior won't solve that issue. No matter which one you choose. For example, i ran my app within a Alpine Docker container image of 17MB of size. |
|
if you revert back to using glibc , don't do library preload with LD_PRELOAD. MALLOC_ARENA_MAX=2 run_my_program https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior |
Each memory allocator has different algorithm and heuristics. I don't know what type of allocator CentOs uses by default whether or not it is the slab allocator provided along with the linux kernel. I would advise you to install Docker on your CentOs Machine and docker pull alpine:latest
docker run --rm -it --name my_container alpine:latest sh Open another shell and run this command to push your program onto the docker image: docker push target/release/my_program my_container:/bin/ Go back to your docker container shell prompt opened previously. and run your program: /bin/my_program Go to your linux host and use htop or any other similar utility to look at your program memory usage |
centos7 3.10.0-327.28.3.el7.x86_64 |
I am wondering why you're choosing CENTOS 7 as RedHat will no longer support CENTOS 7 within 6 months. The kernel you're using is very old comparing to the latest state of the kernel development and improvement added. This centOs will reach its end of life support in June 2024. I would consider moving to another distribution as this one will no longer receives updates any time soon. Why don't you move onto long term support Linux server distribution such as Debian or Ubuntu that has the latest kernels. |
The main reason is that the company has over 300000 servers, most of which are versions of Centos7. Currently, there are relatively few versions of Centos8, and the company's business and traffic usually do not allow for large-scale system replacement, which poses great risks. However, the program needs to adapt to Centos7 and 8 |
Oh you have your organizations constraints. So, they will be forced anyway to move away from it anytime if obsolescence and security/vulnerabilty will be exposed them to risk of being hacked. |
Well, I'll try running it on Centos8 or Docker to see the results |
Try to run it under alpine:3.18 docker image. this image has a small size 17MB. and a small memory footprint. |
thank you very much ,i will for trying it out |
Actually, I want to determine whether it is caused by the underlying rust or by the |
Hello, do you have the corresponding code? As you said, I tested it with Tokio. This is my code, but the last memory was not released. I ran it on Mac m1, and I feel that it is related to the memory allocator code
end
|
this is my test code(System: MacOS M1 CPU 32GMEM)
use std::collections::HashMap;
use serde::Serialize;
#[tokio::main]
async fn main() {
for i in 0..10 {
std::thread::sleep(std::time::Duration::from_secs(1));
test_vec(Vec::new());
//test_hashmap(HashMap::new());
}
println!("all done,now wait");
std::thread::sleep(std::time::Duration::from_secs(3600));
}
fn test_hashmap(mut h: HashMap<i64, i64>) {
println!("start");
for v in 0..1000000 {
h.insert(v, v);
}
}
fn test_vec(mut h: Vec<i64>) {
println!("start");
for v in 0..1000000 {
h.push( v);
}
}
use std::collections::HashMap;
use serde::Serialize;
#[tokio::main]
async fn main() {
for i in 0..10 {
std::thread::sleep(std::time::Duration::from_secs(1));
//test_vec(Vec::new());
test_hashmap(HashMap::new());
}
println!("all done,now wait");
std::thread::sleep(std::time::Duration::from_secs(3600));
}
fn test_hashmap(mut h: HashMap<i64, i64>) {
println!("start");
for v in 0..1000000 {
h.insert(v, v);
}
}
fn test_vec(mut h: Vec<i64>) {
println!("start");
for v in 0..1000000 {
h.push( v);
}
} This seems to be a leak(HashMap) on macos, But most of the memory has indeed been released, and I guess the rest should be memory fragments。 |
HashMap is constantly inserting data, which may cause memory reallocation. When HashMap expands or redistributes internal nodes, it may allocate more memory to accommodate more elements, which may lead to an increase in memory usage. In addition, the internal structure of HashMap may lead to fragmentation, especially after a large number of insertion and deletion operations.
use std::collections::HashMap;
use serde::Serialize;
#[tokio::main]
async fn main() {
for i in 0..10 {
test_hashmap(HashMap::with_capacity(10000000));
}
println!("all done,now wait");
std::thread::sleep(std::time::Duration::from_secs(3600));
}
fn test_hashmap(mut h: HashMap<i64, i64>) {
println!("start");
for v in 0..1000000 {
h.insert(v, v);
}
println!("ok");
} |
Yes, you don't have this one, but it seems like you and I created a new object in a function and then released it. After generating a reference, it was not released, but the scope of the drink was also exceeded. This seems to be caused by the language itself Are you Chinese? How about adding contact information to discuss together |
Finally, I suggest that
|
yes,you can see me on https://github.com/zhuxiujia |
Okay, I have applied to add Duoduo Communication |
Any update on this topic. On my side, things have been improved by running my app outside of the IDE shell. |
I have tried to reproduce this issue. This is my understanding of it: some people have observed increased memory usage when running their web applications with actix-web, which does not seem to be released even after the requests are completed. This is suspected to be a memory leak in the actix-web library or its dependencies. My attempt to reproduceCreate a project with the following files/src/main.rs #![forbid(unsafe_code)]
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use std::collections::HashMap;
#[get("/leak")]
async fn leak() -> impl Responder {
let mut h: HashMap<i64, i64> = HashMap::with_capacity(100000);
test(&mut h).await;
HttpResponse::Ok().body(format!("{:?}", h))
}
async fn test(h: &mut HashMap<i64, i64>) {
//println!("Populating HashMap to 10 times its capacity...");
for i in 0..1000000 {
h.insert(i, i);
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(leak))
.bind(("127.0.0.1", 9000))?
.run()
.await
} Cargo.toml [package]
name = "leak"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.5.1"
tokio = "1.36.0"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
debug = false benchmark.yml ---
concurrency: 100
rampup: 10
iterations: 3000
base: 'http://127.0.0.1:9000'
plan:
- name: Stress test
request:
url: /leak Run the web servercargo run --release Install and run drillcargo install drill
drill --benchmark benchmark.yml --stats Install and run htopsnap install htop
htop Environment
ResultsMemory usage increases during the stress test and is partially released after the test is completed. Memory usage starts at essentially 0% of my RAM, caps at 11% and reverts back to 2.4% after the first runs. Without restarting the server, I tried running the benchmarks again and after the peak, memory reverted to 3.3% of my RAM. I am ill-equipped to get to the conclusion that this is an actix-web issue, or even to assert that this is actually a memory leak and not some caching mechanism. I thought it could even be an issue with HashMap, since the sample code provided elsewhere in this issue overpopulates a HashMap instance by 10 times its capacity, but I have played around a bit with the capacity and the filling loop and I am convinced that's not where the problem lies. I would appreciate any help in understanding this behavior, and if it is indeed a memory leak, to provide enough information for the devs to fix it. |
@robjtede, this issue has more than 50 comments. If you are able, please investigate. Thanks for the great job on Actix! |
Have you tried changing your allocator (and allocator configuration) ? The culprit might be fragmentation in the allocator. See https://sourceware.org/bugzilla/show_bug.cgi?id=14581 |
Same issue on axum and salvo, I don't think this is a actix problem. |
Memory Leak / Memory "not recycled" in Actix 3.3 #1943
The program is active_ On the web, my program has a startup memory of 14MB and requests the interface to perform some operations with a memory of 800MB+. However, the 800MB+in subsequent programs has not been released. What is the reason for this? Some of the language's setting features, or are there any useful tools such as PPROF that can detect memory overflow
Actix_ When web requests return big data, the memory is not released and will continue to be occupied. Is the original intention of this issue and will it be improved in the future
use actix_web::{get, HttpResponse, Responder};
use crate::controller::link_inspection::check::LinkInspection;
use std::collections::HashMap;
#[get("/check")]
async fn check() -> impl Responder {
}
async fn test (h : &mut HashMap<i64,i64>){
}
The text was updated successfully, but these errors were encountered: