govips lifecycle in production
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.
Process level: start once
Section titled “Process level: start once”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
NewImageFromFileand then usingdefer Close()closes a nil reference when loading failed. - An explicit
Close()followed by anotherClose()fromdefercloses the same reference twice. - A successful create with an early-return path that omits
Close()leaves the reference count above the baseline.
What the reference count proves
Section titled “What the reference count proves”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.
Boundary with the other interfaces
Section titled “Boundary with the other interfaces”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.