Skip to content

Commit

Permalink
fix memory leak in gif thumbnail encoding
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mpldr committed Mar 4, 2024
1 parent 4be8c69 commit 00f52fd
Showing 1 changed file with 14 additions and 20 deletions.
34 changes: 14 additions & 20 deletions thumbnailing/i/gif.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package i

import (
"bytes"
"errors"
"fmt"
"image"
"image/draw"
"image/gif"
Expand Down Expand Up @@ -76,19 +78,15 @@ func (d gifGenerator) GenerateThumbnail(b io.Reader, contentType string, width i
}

// The thumbnailer decided that it shouldn't thumbnail, so encode it ourselves
pr, pw := io.Pipe()
go func(pw *io.PipeWriter, p *image.Paletted) {
err = u.Encode(ctx, pw, p)
if err != nil {
_ = pw.CloseWithError(errors.New("gif: error encoding still frame thumbnail: " + err.Error()))
} else {
_ = pw.Close()
}
}(pw, targetImg)
buf := bytes.NewBuffer(make([]byte, 0, 128*1024))
err = u.Encode(ctx, buf, targetImg)
if err != nil {
return nil, fmt.Errorf("gif: error encoding static thumbnail: %w", err)
}
return &m.Thumbnail{
Animated: false,
ContentType: "image/png",
Reader: pr,
Reader: io.NopCloser(buf),
}, nil
}

Expand All @@ -110,20 +108,16 @@ func (d gifGenerator) GenerateThumbnail(b io.Reader, contentType string, width i
g.Config.Width = g.Image[0].Bounds().Max.X
g.Config.Height = g.Image[0].Bounds().Max.Y

pr, pw := io.Pipe()
go func(pw *io.PipeWriter, g *gif.GIF) {
err = gif.EncodeAll(pw, g)
if err != nil {
_ = pw.CloseWithError(errors.New("gif: error encoding final thumbnail: " + err.Error()))
} else {
_ = pw.Close()
}
}(pw, g)
buf := bytes.NewBuffer(make([]byte, 0, 512*1024))
err = gif.EncodeAll(buf, g)
if err != nil {
return nil, fmt.Errorf("gif: error encoding animated thumbnail: %w", err)
}

return &m.Thumbnail{
ContentType: "image/gif",
Animated: true,
Reader: pr,
Reader: io.NopCloser(buf),
}, nil
}

Expand Down

0 comments on commit 00f52fd

Please sign in to comment.