-
Notifications
You must be signed in to change notification settings - Fork 79
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
fix memory leak in gif thumbnail encoding #565
base: main
Are you sure you want to change the base?
Conversation
When using an io.Pipe, it must be ensured that the pipe is drained. If it is not, all references required for the goroutine will not be freed by the garbage collector, as an active reference still exists (the abandoned goroutine the pipe is written to. To fix this, write to a non-blocking buffer. This may increase the RAM usage in the short run, but can be fully garbage collected, even if not read from. Since the size of the buffer is at most the size of the thumbnail and it being freed quickly, this should post a significantly smaller burden on the server. Signed-off-by: Moritz Poldrack <git@moritz.sh> Fixes: 4378a4e ("Avoid use of buffers throughout thumbnailing") Fixes: t2bot#561
This has been running on my server since the patch was submitted and there have been zero OOM kills since (I have limited MMR to 2GB of RAM for testing purposes). |
_ = pw.Close() | ||
} | ||
}(pw, targetImg) | ||
buf := bytes.NewBuffer(make([]byte, 0, 128*1024)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these allocations are deliberately avoided to reduce memory volatility on large instances. This is the reason for the complicated io.Pipe
stuff - if we can get that to work instead, would be best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require conditionally draining the pipe, which would probably introduce a fair amount of complexity, I will give it a look though. I would argue that even 50 MB of temporary allocation is preferable over a 10 MB memory leak though. This stuff adds up quickly. After running this, the normal ram usage of MMR is about 8-10 MB
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The average supported MMR deployment uses around 1gb of memory already, and is generally handling quite a few requests for thumbnails. 50mb per frame (or 50mb per thumbnail when animation is disabled) is quite a lot when considering traffic levels and performance impacts of the garbage collector.
MMR used to have "sawtooth" memory usage because of temporary buffers like this, and it was a major operational problem for hosting providers. To fix it, all code dropped in-memory buffers in favour of direct pipes or file system caching. This not only stabilized memory usage, but increased performance and throughput as well, though the underlying libraries for things like files still use a partial buffer themselves. This leads to a tiny bit of sawtooth-shaped graphs, but not nearly as bad as it could be with manual buffers.
When using an io.Pipe, it must be ensured that the pipe is drained. If it is not, all references required for the goroutine will not be freed by the garbage collector, as an active reference still exists (the abandoned goroutine the pipe is written to.
To fix this, write to a non-blocking buffer. This may increase the RAM usage in the short run, but can be fully garbage collected, even if not read from. Since the size of the buffer is at most the size of the thumbnail and it being freed quickly, this should post a significantly smaller burden on the server.
Fixes: 4378a4e ("Avoid use of buffers throughout thumbnailing")
Fixes: #561