Fix template cache memory leak #846

Merged
merged 1 commit into from Feb 21, 2014

Projects

None yet

2 participants

@sholden
sholden commented Feb 20, 2014

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.

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
@rkh rkh merged commit 504bdad into sinatra:master Feb 21, 2014

1 check passed

Details default The Travis CI build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment