Skip to content

govips lifecycle in production

Copy page

govips keeps libvips images inside a Go process. Start the library once, and close each image on its own. A missed Close() raises OpenImageRefs() and retains the native memory for that image. That is not a VRAM leak, and it does not by itself prove that process RSS grows without a bound.

Startup initialises libvips. Since v2.18.0 it returns an error, and that error has to be checked. Call it once per process, not on every request.

func main() {
if err := vips.Startup(nil); err != nil {
log.Fatalf("start libvips: %v", err)
}
defer vips.Shutdown()
if err := serve(); err != nil {
log.Fatal(err)
}
}

Shutdown releases process-wide libvips resources. Do not call it while a request still uses an ImageRef.

Each image: close only after a successful create

Section titled “Each image: close only after a successful create”

The function below reads a path the caller has already checked, resizes, and exports. A failed create leaves nothing to close. After a successful create, defer Close() covers later resize and export errors.

func resizeJPEG(path string) ([]byte, error) {
image, err := vips.NewImageFromFile(path)
if err != nil {
return nil, fmt.Errorf("load: %w", err)
}
defer image.Close()
if err := image.Resize(0.5, vips.KernelLanczos3); err != nil {
return nil, fmt.Errorf("resize: %w", err)
}
data, _, err := image.ExportJpeg(vips.NewJpegExportParams())
if err != nil {
return nil, fmt.Errorf("export: %w", err)
}
return data, nil
}

Do not pass a raw query string to NewImageFromFile. The path should come from a location the application has already resolved and allowed. Do not call the ImageRef after Close(): govips does not promise a readable image from that use.

Common lifecycle anti-patterns:

  • Ignoring the error from NewImageFromFile and then using defer Close() closes a nil reference when loading failed.
  • An explicit Close() followed by another Close() from defer closes the same reference twice.
  • A successful create with an early-return path that omits Close() leaves the reference count above the baseline.

Since govips v2.18.0, OpenImageRefs() returns the number of unclosed ImageRef values, and AssertNoLeaks(t) can check that at the end of a test. On v2.19.0, loading one known image and closing it immediately, repeated 1000 times, returned the count from a baseline of 0 to 0. That result says this minimal path left no ImageRef. It does not measure RSS, and it does not rule out pages the allocator keeps.

Set cache ceilings with MaxCacheMem, MaxCacheSize, and MaxCacheFiles on vips.Config before startup. ClearCache() is a diagnostic tool.

sharp, pyvips, and the CLI do not have OpenImageRefs(). When their objects are released depends on their own runtimes. Do not copy the govips count across. Streaming has a separate rule that the reader must stay alive until Close(); see Reader/Writer streaming I/O.