Rick Olson
2006-Sep-20 14:59 UTC
[Mongrel] boy, that mongrel_upload_progress handler sucks!
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
