Search code examples
rubyforkdelayed-jobprawnspawn

Does Ruby Spawn or Fork share memory with parent process after completion?


I have an app that processes images and creates PDFs in the background using Delayed Job. The images are being processed using Process.spawn (through the subexec gem) which runs a GraphicsMagick process. We are then creating a PDF file using the Prawn gem, which includes these images and text components. I don't believe that the Prawn gem using Fork or Spawn. We are using Ruby 1.9.3. What happens is that our Delayed Job processes balloons from ~120MB to over 800MB of memory after a few PDF files are made.

I know that the spawned GraphicsMagick processes share memory with the parent process, but is that memory given back to the system after the child processes are finished? If I created the PDF files in a forked process, would the memory used in creating the PDF file be returned to the system after that forked process was completed?


Solution

  • I went ahead and forked the PDF creation tasks and tested it out. The forked processes do indeed release the memory back to the system after they exit, and our Delayed Job processes no longer balloon out of control. I have attached the code in case anyone is facing a similar problem. Interestingly enough, running Ruby 1.9.3 + Rails 3.2.x, we don't need to reconnect to the database in either the forked processes or the parent process. There was much debate about this in other Stackoverflow questions.

    def run_in_fork
      read, write = IO.pipe
    
      pid = fork do
        error = nil
        read.close
        begin
          yield
        rescue => e
          error = e
        end
        Marshal.dump(error, write)
        exit!(0) # skips exit handlers.
      end
    
      write.close
      result = read.read
      Process.wait(pid)
      raise "Child process failed" if result.empty?
      if exception = Marshal.load(result)
        raise exception
      end
      return true
    end
    
    
    def my_method
      object = MyObject.find.first
      run_in_fork do
        object.long_running_process
      end
      object.reload
    end