跳转到内容

生产环境里的 govips 生命周期

复制本页

govips 在 Go 进程里持有 libvips 的原生图像句柄。进程启动时初始化一次,每张图处理完单独关闭。漏掉 Close() 会让 OpenImageRefs() 升高,并保留那张图占用的原生内存。这不等于显存(VRAM)泄漏,也不等于进程 RSS 一定无界增长。

Startup 初始化 libvips。从 v2.18.0 起它返回 error,必须检查。每个进程调用一次,不要放进每条请求。

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 释放 libvips 的进程级资源。仍有请求在使用 ImageRef 时不要调用它。

下面的函数从调用方已经校验过的路径读取文件、缩小并导出。创建失败时没有可关闭的引用;创建成功后,defer Close() 覆盖后续的缩放和导出错误。

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
}

不要把请求里的原始查询字符串直接交给 NewImageFromFile。路径应来自应用自己解析并允许的存储位置。关闭之后不要再调用该 ImageRef:govips 不能保证对已关闭引用的访问是安全的。

常见生命周期反模式:

  • 忽略 NewImageFromFile 的错误并直接 defer Close(),会在加载失败时对空引用调用关闭。
  • 先手动 Close(),函数返回时 defer 再关一次,属于重复关闭。
  • 创建成功但存在提前返回分支未调用 Close(),引用计数将无法回到基线。

govips v2.18.0 起,OpenImageRefs() 返回当前未关闭的 ImageRef 数量,AssertNoLeaks(t) 可在测试结束时检查。在 v2.19.0 上,对一张已知图片加载并立即关闭、循环 1000 次之后,计数从基线 0 回到 0。该结果说明这条最小路径没有遗留 ImageRef;它不直接测量 RSS,也不排除原生分配器保留的缓存页面。

缓存上限用 vips.Config 的 MaxCacheMem、MaxCacheSize 和 MaxCacheFiles 在启动前设定。ClearCache() 只适合诊断。

sharp、pyvips 和 CLI 没有 OpenImageRefs()。它们的对象何时释放取决于各自的运行时,不能把 govips 的计数方式直接套用。流式读取另有 Reader 必须保持可用直到 Close() 的规则,见Reader/Writer 流式 I/O。