Production resource lifecycle
govips wraps a C library (libvips), so resource management is more involved than pure-Go programs. Every open image corresponds to libvips-allocated memory — missing one Close() means leaking thousands to millions of pixels worth of VRAM/RAM.
Process-level lifecycle
Section titled “Process-level lifecycle”func main() { // 1. Initialise — must check the error if err := vips.Startup(nil); err != nil { log.Fatalf("start libvips: %v", err) } defer vips.Shutdown()
// 2. Serve loop server.Run()}- Startup(nil) is called once per process. It initialises the GLib type system and libvips internal state. As of v2.18.0,
Startupmay return an error (previously panicked internally). - Shutdown() frees all libvips global resources, purges caches, and tears down thread pools.
- Always defer Shutdown(), even across panics.
ImageRef lifecycle
Section titled “ImageRef lifecycle”func handler(w http.ResponseWriter, r *http.Request) error { image, err := vips.NewImageFromFile(r.URL.Query().Get("path")) if err != nil { return fmt.Errorf("load: %w", err) } defer image.Close()
if err := image.Resize(0.5, vips.KernelLanczos3); err != nil { return fmt.Errorf("resize: %w", err) } // image auto-closes when handler returns return nil}- Every
ImageRefallocates libvips memory. defer image.Close()is the idiomatic Go pattern.- Never use image after Close — libvips will either segfault or return garbled data.
Correct pattern
Section titled “Correct pattern”func process(input string) ([]byte, error) { image, err := vips.NewImageFromFile(input) if err != nil { return nil, err } defer image.Close()
if err := image.AutoRotate(); err != nil { return nil, err // image.Close() runs via defer } buf, _, err := image.ExportJpeg(vips.NewJpegExportParams()) return buf, err}Wrong pattern
Section titled “Wrong pattern”func process(input string) ([]byte, error) { image, _ := vips.NewImageFromFile(input) // BAD: ignored error defer image.Close() // defer is correctly placed image.Close() // BAD: closed too early — double-close on return return image.ExportJpeg(nil) // BAD: use after close — segfault}func process(input string) ([]byte, error) { image, err := vips.NewImageFromFile(input) if err != nil { return nil, err } // Forgot to Close — leaks memory every call return image.ExportJpeg(nil)}Leak detection (v2.18.0+)
Section titled “Leak detection (v2.18.0+)”govips v2.18.0 introduced leak detection tools:
OpenImageRefs()returns the number of currently unclosed ImageRef instances (development/debugging).AssertNoLeaks(t)asserts at the end of a test that nothing is leaking — use withgo test.
func TestResize(t *testing.T) { defer vips.AssertNoLeaks(t) // checks at test end
image, err := vips.NewImageFromFile("input.jpg") if err != nil { t.Fatal(err) } defer image.Close() // ...}Panic and error boundaries
Section titled “Panic and error boundaries”govips panics can occur in these scenarios:
- ImageRef used after Close (double free).
- cgo calls made before libvips is initialised (forgot Startup).
- Go slices passed to C are GC’d mid-call (pre-v2.19.0
SetBlobbug; fixed).
Defence: use recover at goroutine entry points, converting panics into error logs. Do not assume the libvips state is consistent after a panic.
Production checklist
Section titled “Production checklist”-
Startup(nil)return value is checked. -
Shutdown()is deferred inmain. - Every
NewImageFromFile/NewImageFromBuffererror is checked. - Every ImageRef has
defer Close()or explicit close. - ImageRef is never accessed after Close.
-
MALLOC_ARENA_MAX=2is tested under concurrency load. -
AssertNoLeaks(t)is present in tests. - Goroutine panic recovery is set up in the serving process.