sinatra/sinatra — pull request #846 by sholden (2014-02-20 21:22:15+00:00)
Title: Fix template cache memory leak

This patch fixes a memory leak with Tilt::Cache from the in place merge! on options.

Here is test code that produces the current issue.

```ruby
require 'sinatra'

LAYOUT = <<-LAYOUT
= yield
LAYOUT

VIEW = <<-VIEW
= cache_keys
VIEW

class TestApp < Sinatra::Application
disable :reload_templates

get ('/') do
$cache ||= template_cache.instance_eval { @cache }

haml VIEW, layout: LAYOUT,
locals: {cache_keys: $cache.size },
layout_options: {option: 'thing'}
end
end

require 'test/unit'
require 'rack/test'

if ENV['PATCH']
class Sinatra::Base
def render(engine, data, options = {}, locals = {}, &block)
# merge app-level options
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
options.merge!(engine_options) { |key, v1, v2| v1 }

# extract generic options
locals = options.delete(:locals) || locals || {}
views = options.delete(:views) || settings.views || "./views"
layout = options[:layout]
layout = false if layout.nil? && options.include?(:layout)
eat_errors = layout.nil?
layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
layout = @default_layout if layout.nil? or layout == true
layout_options = options.delete(:layout_options) || {}
content_type = options.delete(:content_type) || options.delete(:default_content_type)
layout_engine = options.delete(:layout_engine) || engine
scope = options.delete(:scope) || self
options.delete(:layout)

# set some defaults
options[:outvar] ||= '@_out_buf'
options[:default_encoding] ||= settings.default_encoding

# compile and render template
begin
layout_was = @default_layout
@default_layout = false
template = compile_template(engine, data, options, views)
output = template.render(scope, locals, &block)
ensure
@default_layout = layout_was
end

# render layout
if layout
options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope).merge!(layout_options)
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
end

output.extend(ContentTyped).content_type = content_type if content_type
output
end
end
end

class CacheTest < Test::Unit::TestCase
include Rack::Test::Methods

def app
TestApp
end

def test_cache_leak
2.times { get '/' }
assert_equal 2, $cache.size
end
end
```