[Mongrel] boy, that mongrel_upload_progress handler sucks!

Rick Olson technoweenie at gmail.com
Wed Sep 20 10:59:47 EDT 2006


I started actually using the upload progress handler, and noticed it
was leaking memory over time.  Looking at the code, I noticed that
cancelled uploads weren't being cleaned up.  I bugged Zed about adding
a request_abort callback to handlers, but I found a simple way to
monkey patch that in:

# config/mongrel_upload_progress.conf
# yes, this file is being used with -S meaning it's an actual ruby file.
# So, why don't I use the rb extension?  Hell if I know :)
Mongrel::HttpHandler.class_eval do
  # add the default stub method
  def request_aborted(params)
  end
end

# patch the monkey
Mongrel::HttpRequest.class_eval do
  def initialize_with_abort(params, socket, dispatcher)
    initialize_without_abort(params, socket, dispatcher)
    dispatcher.request_aborted(params) if @body.nil? && dispatcher
  end

  alias_method :initialize_without_abort, :initialize
  alias_method :initialize, :initialize_with_abort
end

Set up the handler
uri "/", :handler => plugin("/handlers/upload", :path_info => '/upload'),
  :in_front => true

# add the callback to the actual handler.
::Upload.class_eval do
  def request_aborted(params)
    return unless params['PATH_INFO'] == @path_info &&
      params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
      upload_id =
Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
    instance_variable_set(checked_var(upload_id), nil)
    Mongrel::Uploads.finish(upload_id)
  end
end

I'm going to work this into svn.  But, there's another issue.  Using
instance vars means you're basically adding random instance vars into
the class.  They're set to nil on finish, but this still eats up
memory.  _why's original version used hashes, but I had some nasty
locking isuses on multiple uploads.  I replaced this with the current
instance variable scheme, which helped with the locking, but now
leaks.  I think the locking issue is fixed now by setting the default
marking frequency to 3 seconds.  So, I'm going to try and go back to
the hashes method.

I did some quick tests in the console like this:

class Leaker
  def key
    Time.now.to_i.to_s.split('').sort_by { rand }.join
  end

  def inst_var
    instance_variable_set("@key_#{key}", nil)
  end

  def rem_var
    k = "@key_#{key}"
    instance_variable_set(k, Time.now)
    remove_instance_variable(k)
  end

  def hash
    @hash ||= {}
    k = key
    @hash[k] = Time.now
    @hash.delete k
  end
end

l = Leaker.new

while 1; l.inst_var; end # eats memory!
while 1; l.rem_var; end # eats memory!
while 1; l.hash; end # stays at a constant 2mb



-- 
Rick Olson
http://weblog.techno-weenie.net
http://mephistoblog.com


More information about the Mongrel-users mailing list