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