mirror of
https://github.com/Shopify/liquid.git
synced 2026-10-03 00:55:11 -07:00
Move resource limit tracking to its own class
This commit is contained in:
@@ -63,6 +63,7 @@ require 'liquid/variable'
|
|||||||
require 'liquid/variable_lookup'
|
require 'liquid/variable_lookup'
|
||||||
require 'liquid/range_lookup'
|
require 'liquid/range_lookup'
|
||||||
require 'liquid/file_system'
|
require 'liquid/file_system'
|
||||||
|
require 'liquid/resource_limits'
|
||||||
require 'liquid/template'
|
require 'liquid/template'
|
||||||
require 'liquid/standardfilters'
|
require 'liquid/standardfilters'
|
||||||
require 'liquid/condition'
|
require 'liquid/condition'
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ module Liquid
|
|||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = []
|
output = []
|
||||||
context.resource_limits[:render_length_current] = 0
|
context.resource_limits.render_length = 0
|
||||||
context.resource_limits[:render_score_current] += @nodelist.length
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
@nodelist.each do |token|
|
@nodelist.each do |token|
|
||||||
# Break out if we have any unhanded interrupts.
|
# Break out if we have any unhanded interrupts.
|
||||||
@@ -104,9 +104,8 @@ module Liquid
|
|||||||
|
|
||||||
def render_token(token, context)
|
def render_token(token, context)
|
||||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||||
context.increment_used_resources(:render_length_current, token_output)
|
context.resource_limits.increment_render_length(token_output)
|
||||||
if context.resource_limits_reached?
|
if context.resource_limits.reached?
|
||||||
context.resource_limits[:reached] = true
|
|
||||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||||
end
|
end
|
||||||
token_output
|
token_output
|
||||||
|
|||||||
+6
-17
@@ -21,9 +21,12 @@ module Liquid
|
|||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@registers = registers
|
@registers = registers
|
||||||
@errors = []
|
@errors = []
|
||||||
@resource_limits = resource_limits || Template.default_resource_limits.dup
|
|
||||||
@resource_limits[:render_score_current] = 0
|
@resource_limits = if resource_limits.is_a?(ResourceLimits)
|
||||||
@resource_limits[:assign_score_current] = 0
|
resource_limits
|
||||||
|
else
|
||||||
|
ResourceLimits.new(resource_limits || Template.default_resource_limits)
|
||||||
|
end
|
||||||
squash_instance_assigns_with_environments
|
squash_instance_assigns_with_environments
|
||||||
|
|
||||||
@this_stack_used = false
|
@this_stack_used = false
|
||||||
@@ -36,20 +39,6 @@ module Liquid
|
|||||||
@filters = []
|
@filters = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment_used_resources(key, obj)
|
|
||||||
@resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
|
|
||||||
obj.length
|
|
||||||
else
|
|
||||||
1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def resource_limits_reached?
|
|
||||||
(@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
|
|
||||||
(@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
|
|
||||||
(@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
|
|
||||||
end
|
|
||||||
|
|
||||||
def strainer
|
def strainer
|
||||||
@strainer ||= Strainer.create(self, @filters)
|
@strainer ||= Strainer.create(self, @filters)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
module Liquid
|
||||||
|
class ResourceLimits
|
||||||
|
attr_accessor :render_length, :render_score, :assign_score,
|
||||||
|
:render_length_limit, :render_score_limit, :assign_score_limit
|
||||||
|
|
||||||
|
def initialize(limits)
|
||||||
|
@render_length_limit = limits[:render_length_limit]
|
||||||
|
@render_score_limit = limits[:render_score_limit]
|
||||||
|
@assign_score_limit = limits[:assign_score_limit]
|
||||||
|
|
||||||
|
# render_length is assigned by BlockBody
|
||||||
|
# @render_length = 0
|
||||||
|
@render_score = 0
|
||||||
|
@assign_score = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def reached?
|
||||||
|
(@render_length_limit && @render_length > @render_length_limit) ||
|
||||||
|
(@render_score_limit && @render_score > @render_score_limit ) ||
|
||||||
|
(@assign_score_limit && @assign_score > @assign_score_limit )
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_render_length(obj)
|
||||||
|
@render_length += increment_for(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_render_score(obj)
|
||||||
|
@render_score += increment_for(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_assign_score(obj)
|
||||||
|
@assign_score += increment_for(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def increment_for(obj)
|
||||||
|
obj.instance_of?(String) || obj.instance_of?(Array) || obj.instance_of?(Hash) ? obj.length : 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -25,7 +25,7 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
context.scopes.last[@to] = val
|
||||||
context.increment_used_resources(:assign_score_current, val)
|
context.resource_limits.increment_assign_score(val)
|
||||||
''.freeze
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context.scopes.last[@to] = output
|
context.scopes.last[@to] = output
|
||||||
context.increment_used_resources(:assign_score_current, output)
|
context.resource_limits.increment_assign_score(output)
|
||||||
''.freeze
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ module Liquid
|
|||||||
:locale => I18n.new
|
:locale => I18n.new
|
||||||
}
|
}
|
||||||
|
|
||||||
attr_accessor :root, :resource_limits
|
attr_accessor :root
|
||||||
|
attr_reader :resource_limits
|
||||||
|
|
||||||
|
def resource_limits=(limits)
|
||||||
|
@resource_limits = ResourceLimits.new(limits)
|
||||||
|
end
|
||||||
|
|
||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
class TagRegistry
|
class TagRegistry
|
||||||
@@ -110,7 +116,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@resource_limits = self.class.default_resource_limits.dup
|
self.resource_limits = self.class.default_resource_limits
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
|
|||||||
@@ -101,61 +101,62 @@ class TemplateTest < Minitest::Test
|
|||||||
t = Template.parse("0123456789")
|
t = Template.parse("0123456789")
|
||||||
t.resource_limits = { :render_length_limit => 5 }
|
t.resource_limits = { :render_length_limit => 5 }
|
||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits.reached?
|
||||||
|
|
||||||
t.resource_limits = { :render_length_limit => 10 }
|
t.resource_limits = { :render_length_limit => 10 }
|
||||||
assert_equal "0123456789", t.render!()
|
assert_equal "0123456789", t.render!()
|
||||||
refute_nil t.resource_limits[:render_length_current]
|
refute_nil t.resource_limits.render_length
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_score
|
def test_resource_limits_render_score
|
||||||
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
||||||
t.resource_limits = { :render_score_limit => 50 }
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits.reached?
|
||||||
|
|
||||||
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
||||||
t.resource_limits = { :render_score_limit => 50 }
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits.reached?
|
||||||
|
|
||||||
t.resource_limits = { :render_score_limit => 200 }
|
t.resource_limits = { :render_score_limit => 200 }
|
||||||
assert_equal (" foo " * 100), t.render!()
|
assert_equal (" foo " * 100), t.render!()
|
||||||
refute_nil t.resource_limits[:render_score_current]
|
refute_nil t.resource_limits.render_score
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_assign_score
|
def test_resource_limits_assign_score
|
||||||
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||||
t.resource_limits = { :assign_score_limit => 1 }
|
t.resource_limits = { :assign_score_limit => 1 }
|
||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits.reached?
|
||||||
|
|
||||||
t.resource_limits = { :assign_score_limit => 2 }
|
t.resource_limits = { :assign_score_limit => 2 }
|
||||||
assert_equal "", t.render!()
|
assert_equal "", t.render!()
|
||||||
refute_nil t.resource_limits[:assign_score_current]
|
refute_nil t.resource_limits.assign_score
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_aborts_rendering_after_first_error
|
def test_resource_limits_aborts_rendering_after_first_error
|
||||||
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||||
t.resource_limits = { :render_score_limit => 50 }
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits.reached?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
||||||
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!()
|
t.render!()
|
||||||
assert t.resource_limits[:assign_score_current] > 0
|
assert t.resource_limits.assign_score > 0
|
||||||
assert t.resource_limits[:render_score_current] > 0
|
assert t.resource_limits.render_score > 0
|
||||||
assert t.resource_limits[:render_length_current] > 0
|
assert t.resource_limits.render_length > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_default_resource_limits_unaffected_by_render_with_context
|
def test_default_resource_limits_unaffected_by_render_with_context
|
||||||
context = Context.new
|
context = Context.new
|
||||||
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!(context)
|
t.render!(context)
|
||||||
assert context.resource_limits[:assign_score_current] > 0
|
assert context.resource_limits.assign_score > 0
|
||||||
assert context.resource_limits[:render_score_current] > 0
|
assert context.resource_limits.render_score > 0
|
||||||
assert context.resource_limits[:render_length_current] > 0
|
assert context.resource_limits.render_length > 0
|
||||||
refute Template.default_resource_limits.key?(:assign_score_current)
|
|
||||||
refute Template.default_resource_limits.key?(:render_score_current)
|
|
||||||
refute Template.default_resource_limits.key?(:render_length_current)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_can_use_drop_as_context
|
def test_can_use_drop_as_context
|
||||||
|
|||||||
Reference in New Issue
Block a user