ActiveStorage自定义Vips分析器触发VipsJpeg out of order read异常
问题背景
- 运行环境:Rails 7.0.3
- 开发内容:自定义实现继承自
ActiveStorage::Analyzer::ImageAnalyzer::Vips的BlurhashAnalyzer分析器,用于提取图片尺寸元数据、生成Blurhash编码,核心逻辑为:- 重写
metadata方法:通过父类提供的read_image方法获取Vips图片实例,判断图片旋转状态后提取宽高信息,合并Blurhash编码结果返回 - 私有
blurhash方法:为提升大尺寸图片编码效率,先通过ImageProcessing::Vips将传入的Vips图片实例缩放填充为200*200尺寸缩略图,再读取缩略图像素数组完成编码;方法内添加异常捕获逻辑:开发环境直接抛出异常,生产环境记录错误日志后返回空对象
- 重写
触发异常
运行时blurhash方法抛出如下异常:
(process:29640): VIPS-WARNING **: 17:25:01.323: error in tile 0 x 120 *** Vips::Error Exception: VipsJpeg: out of order read at line 1440
已完成排查
- 针对同一份源文件,手动调用
::Vips::Image.new_from_file传入图片路径重新初始化Vips实例后,执行完全相同的resize_and_pad(200, 200)缩放逻辑可正常运行,初始化时无论是否指定access: :sequential参数都无异常 - 查阅Rails 7.0.3内置
Analyzer::ImageAnalyzer::Vips源码确认,框架自带的read_image方法本身通过::Vips::Image.new_from_file(file.path, access: :sequential)从临时文件初始化Vips实例,与手动调试的初始化逻辑完全一致,但直接使用read_image代码块传入的image对象执行处理就会触发上述异常 - 已知同类报错多与图片旋转操作相关,但当前代码逻辑中未显式执行任何图片旋转操作
异常根因
该报错的核心触发原因是access: :sequential顺序访问模式的特性限制:
- 顺序访问模式下libvips会按字节流从前到后单向读取图片文件,不支持读取指针回退、随机访问文件任意位置,该模式本身是为大图片低内存读取设计的优化项
- Rails的
read_image方法在将image实例传入代码块之前,已经先执行了内置的元数据读取逻辑:读取图片宽高、解析EXIF信息,这一步已经消耗了文件流的前序字节,读取指针已经移动到文件靠后的位置 - 此时复用同一个image实例做缩放填充操作,libvips需要读取文件前面位置的像素数据,但顺序模式下指针无法回退,就会抛出
out of order read错误 - 手动初始化Vips实例无异常,是因为新实例的读取指针在文件起始位置,先执行缩放操作时是从前往后顺序读,没有回退需求,自然不会触发异常;同类报错多和旋转相关,本质也是旋转操作需要先读EXIF方向信息、移动了流指针,后续操作触发回读导致的,和代码中是否手动编写旋转逻辑无关。
修复方案
按优先级从高到低可选:
- 方案一(最稳妥、改动最小):不要复用
read_image返回的image实例做Blurhash编码处理,在blurhash方法中直接通过当前分析的临时文件路径重新初始化一个新的Vips实例再做缩放。该方案额外开销极低,因为本身只需要生成200*200的缩略图,重新打开文件的性能损耗可以忽略,也不需要修改父类逻辑。 - 方案二:重写
read_image调用逻辑,打开图片时将access参数改为:random随机访问模式,该模式下支持读取指针任意回退,不会触发顺序读报错,但内存占用会比顺序模式稍高,对于生成缩略图的场景影响可以忽略。 - 方案三:在拿到
read_image返回的image实例后,先调用.copy方法生成一个独立的内存副本再做后续处理,但该方案在图片尺寸较大时内存开销高于前两个方案,不推荐使用。
内容的提问来源于stack exchange,提问作者Fangxing
相关产品推荐
相关产品推荐

