Search code examples
ruby-on-rails-3passengerrmagick

RMagick - Correct way to create a thumbnail without leaking memory


I'm using RMagick to do some fairly simple image manipulations in a Rails 3 app (running on Passenger). I'm having a terrible experience trying to control associated memory leaks, despite using the destroy! method. An important thing to note is that the incoming images may be transparent PNGs, and I want them on a white background, not the default black which RMagick uses. Others may be PDFs, in which case I want to use the first page.

Currently resizing a 3.6MB JPEG results in a 110MB memory leak.

So, is anybody able to take a look at my sample and maybe spot where I'm going wrong:

    path = 'some/path'
    page_id = "0" 
    in_doc_o = nil
    begin
      in_doc_o = ImageList.new(path+"[#{page_id}]")
      # Create a white background layer
      in_doc_temp = in_doc_o.new_image(in_doc_o.first.columns, in_doc_o.first.rows) { self.background_color = "white" } 
      in_doc = in_doc_o.reverse.flatten_images
      in_doc.format = "jpg"
    rescue
      # Something went wrong so create a dummy 'error' image
      in_doc = ImageList.new("public/images/doc.jpg"+"[#{page_id}]")
    end

    in_doc.change_geometry!('600x200>') { |cols, rows, img| img.resize!(cols, rows) }

    out_blob = in_doc.to_blob() { self.quality = 60 }
    res = save_to_special_storage_device(out_blob)
    mime_type = in_doc.mime_type

    begin        
      in_doc.each {|img| img.destroy!}
    rescue=>e
      Rails.logger.info "rescued attempting destroy on in_doc image list #{e.inspect}"
    end
    begin        
      in_doc_o.each {|img| img.destroy!}
    rescue=>e
      Rails.logger.info "rescued attempting destroy on in_doc_o image list #{e.inspect}"
    end        
    begin
      in_doc.destroy!          
      in_doc_o.destroy! unless in_doc_o.nil?
      in_doc = nil          
      in_doc_o = nil
      in_doc_temp.destroy! if in_doc_temp
      in_doc_temp = nil
    rescue
      Rails.logger.info "rescued attempting image cleanups"
    end
    GC.start

I'm guessing that I could probably save some issues at the expense of a little more code by identifying PNGs up front and not creating the blank white image, but in tests I still leak 60MB which suggests a fundamental issue that I would like to resolve.

Strangely, or not, if I re-run the test with another image of approximately the same size, the memory usage does not jump significantly a second time (fortunately).

Any reasonable ideas would be appreciated, especially with a view of how much memory I could save.


Solution

  • Try minimagic, which was written to deal with exactly the problem you're having.

    For most tasks, the minimagic gem linked here is sufficient. It runs ImageMagick's command-line tools, which means a new process is created, it runs, and then stops, freeing memory. We replaced RMagic with MiniMagic and our memory woes were a thing of the past.