libvips/ruby-vips

Vips hangs when drawing an image after a `fork` call

malomalo opened this issue · 4 comments

ruby-vips hangs when drawing a image after a fork call.

Confirmed on both MacOS 14.2.1 and 13.6.6. Likely also affects linux as our CI system has the same issue.

I've attached the following test file which will hang; once you terminate the script you will also want to terminate the rouge process left behind from the fork.

require 'tempfile'
require 'vips'

class Image
  class << self
    def png(**kwargs)
      f = Tempfile.new(['image', '.png'], nil, encoding: 'ascii-8bit')
      draw_image(**kwargs).pngsave(f.path)
    end
  
    def draw_image(width: nil, height: nil, depth: 8, alpha: false)
      width ||= rand(256)  + 1
      height ||= rand(256) + 1
      bands = alpha ? 4 : 3
      image = Vips::Image.black(width, height, bands: bands)
      (0...width).each do |x|
        color = Array.new(bands) { rand(2^depth) }
        y1, y2 = Array.new(2) { rand(height) }.sort
        # After fork gets stuck here and process lingers even after terminating
        # parent process
        image = image.draw_line(color, x, y1, x, y2) 
      end
      image
    end
  end
end

Image.png # Works

fork {  Image.png }

# Won't terminate because inside the fork hangs
Process.wait

This might be related to #155, but I don't believe so.

Hello @malomalo,

libvips is very threaded, so like all threaded libraries, you must be very careful when mixing it with fork.

When a process with several running threads forks, none of the threads copy over, instead you will have a new process with a single main thread. mutexes are in an indeterminate state, confusion is everywhere, nothing will work.

It's safest to only require 'ruby-vips' in the child and after the fork. On some platforms you can do the require, then fork, and then process an image, but not all. You can never process some images, fork, and then process some more.

Interesting

I'm running into this in my Rails test env when trying to parallelize test. I will look into what is happening before the fork to cause this as error #155 mentions it must be required before the fork

Is there anyway to reset vips after a fork perhaps?

You're right, I'd forgotten all the detail around appkit.

No, libvips doesn't support unload and reload, unfortunately.

This is an aside, but have you seen draw_line!? It's usually many times faster than plain draw_line since it can avoid a large copy on each operation.

https://www.libvips.org/2021/03/08/ruby-vips-mutate.html

Ah...

We have a Rails gem with an initializer that uses Vips to know image sizes on boot. Then Rails forks for testing and this issue presents itself.

Thanks re the mutate, new to me.