When I gave my talk on Rublique (which has now been rendered quite obsolete by Evan Weaver's Bleak House, which uses C instrumentation much faster than ObjectSpace) I mentioned at the end that the memory leak I was experiencing was due to send_file. A lot of people were confused because of that, specifically what the nature of the memory leak was, and I didn't exactly do a good job of describing what the problem was. It's not really a memory leak. The problem is Rails returns a StringIO to Mongrel which represents the entire content body. That's fine for most documents, but what happens when you're trying to send a 1GB+ file?
<br><br>send_file returns a Proc object as the :text option to render. This proc contains a simple routine to print out the file. The problem with the Rails/Mongrel combination is that this Proc simply writes to a StringIO, which when the entire content body has been emitted (
i.e. the entire contents of the file) returns it all to Mongrel, which then sends it to the network:<br><pre>70: <span class="ruby-identifier">len</span> = <span class="ruby-identifier">options</span>[<span class="ruby-identifier">
:buffer_size</span>] <span class="ruby-operator">||</span> <span class="ruby-value">4096</span><br>71: <span class="ruby-constant">File</span>.<span class="ruby-identifier">open</span>(<span class="ruby-identifier">
path</span>, <span class="ruby-value str">'rb'</span>) <span class="ruby-keyword kw">do</span> <span class="ruby-operator">|</span><span class="ruby-identifier">file</span><span class="ruby-operator">|</span><br>72:
<span class="ruby-keyword kw">while</span> <span class="ruby-identifier">buf</span> = <span class="ruby-identifier">file</span>.<span class="ruby-identifier">read</span>(<span class="ruby-identifier">len</span>)<br>73:
<span class="ruby-identifier">output</span>.<span class="ruby-identifier">write</span>(<span class="ruby-identifier">buf</span>)<br>74: <span class="ruby-keyword kw">end</span><br>75: <span class="ruby-keyword kw">
end</span></pre><br>When Mongrel is serving multiple, large files to the network, this is obviously going to eat up a lot of RAM.<br><br>The intermediate StringIO is what causes the massive memory consumption. If you could instance_eval the Proc returned by send_file and expose a method which writes directly to the network, the intermediate StringIO could be avoided.
<br clear="all"><br>The advantages to this would be pure-Ruby instrumentation which can act upon exactly how much data is sent, for example a statistics tracking system which monitors how much of a given file was sent after it was requested. You can do this now, but the cost is every file presently being sent will remain in memory until the transfer ends.
<br><br>-- <br>Tony Arcieri<br>ClickCaster, Inc.<br><a href="mailto:tony@clickcaster.com">tony@clickcaster.com</a><br>