mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-16 17:30:43 -07:00
Eagerly cache global filters
Including a module can cause Ruby's global constant cache to be busted if the included module contain constants. So that's something you don't want to happen at "runtime", otherwise it will severely degrade performance and if you are using YJIT or MJIT most of the compiled code will be invalidated. To limit the impact of this, we can pre-include the global filters, as they're generally registered during boot, that limits the problem to non-global filters.
This commit is contained in:
+1
-1
@@ -59,8 +59,8 @@ require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/strainer_template'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
|
||||
@@ -7,25 +7,26 @@ module Liquid
|
||||
|
||||
def add_global_filter(filter)
|
||||
strainer_class_cache.clear
|
||||
global_filters << filter
|
||||
GlobalCache.add_filter(filter)
|
||||
end
|
||||
|
||||
def create(context, filters = [])
|
||||
strainer_from_cache(filters).new(context)
|
||||
end
|
||||
|
||||
GlobalCache = Class.new(StrainerTemplate)
|
||||
|
||||
private
|
||||
|
||||
def global_filters
|
||||
@global_filters ||= []
|
||||
end
|
||||
|
||||
def strainer_from_cache(filters)
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(StrainerTemplate)
|
||||
global_filters.each { |f| klass.add_filter(f) }
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
if filters.empty?
|
||||
GlobalCache
|
||||
else
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(GlobalCache)
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ module Liquid
|
||||
filter_methods.include?(method.to_s)
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_methods
|
||||
|
||||
+10
-10
@@ -72,21 +72,21 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_global_filter(*globals)
|
||||
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters)
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, [])
|
||||
globals.each do |global|
|
||||
Liquid::StrainerFactory.add_global_filter(global)
|
||||
end
|
||||
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
original_global_cache = Liquid::StrainerFactory::GlobalCache
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
|
||||
|
||||
globals.each do |global|
|
||||
Liquid::Template.register_filter(global)
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
end
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
|
||||
@@ -52,7 +52,8 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
exception.message
|
||||
)
|
||||
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
|
||||
source = AccessScopeFilters.instance_method(:public_filter).source_location
|
||||
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
|
||||
end
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
|
||||
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
with_global_filter do
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert(strainer.class.send(:filter_methods).include?('public_filter'))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user