add StringScannerPool for thread safety

This commit is contained in:
Michael Go
2025-01-07 14:32:07 -04:00
parent eff5c5de8e
commit 3c16c27ee1
5 changed files with 57 additions and 46 deletions
+1
View File
@@ -49,6 +49,7 @@ end
require "liquid/version"
require "liquid/deprecations"
require "liquid/const"
require "liquid/string_scanner_pool"
require 'liquid/standardfilters'
require 'liquid/file_system'
require 'liquid/parser_switching'
+3 -6
View File
@@ -72,10 +72,6 @@ module Liquid
CACHE = LruRedux::Cache.new(10_000) # most themes would have less than 2,000 unique expression
class << self
def string_scanner
@ss ||= StringScanner.new("")
end
def parse(markup)
return unless markup
@@ -106,8 +102,7 @@ module Liquid
end
def parse_number(markup)
ss = string_scanner
ss.string = markup
ss = StringScannerPool.pop(markup)
is_integer = true
last_dot_pos = nil
@@ -147,6 +142,8 @@ module Liquid
# we should never reach this point
false
end
ensure
StringScannerPool.release(ss)
end
end
end
+27 -31
View File
@@ -157,40 +157,34 @@ module Liquid
table.freeze
end
class << self
def string_scanner
@string_scanner ||= StringScanner.new("")
end
end
def initialize(input)
@ss = self.class.string_scanner
@ss.string = input
@input = input
end
# rubocop:disable Metrics/BlockNesting
def tokenize
ss = StringScannerPool.pop(@input)
@output = []
until @ss.eos?
@ss.skip(WHITESPACE_OR_NOTHING)
until ss.eos?
ss.skip(WHITESPACE_OR_NOTHING)
break if @ss.eos?
break if ss.eos?
start_pos = @ss.pos
peeked = @ss.peek_byte
start_pos = ss.pos
peeked = ss.peek_byte
if (special = SPECIAL_TABLE[peeked])
@ss.scan_byte
ss.scan_byte
# Special case for ".."
if special == DOT && @ss.peek_byte == DOT_ORD
@ss.scan_byte
if special == DOT && ss.peek_byte == DOT_ORD
ss.scan_byte
@output << DOTDOT
elsif special == DASH
# Special case for negative numbers
if (peeked_byte = @ss.peek_byte) && NUMBER_TABLE[peeked_byte]
@ss.pos -= 1
@output << [:number, @ss.scan(NUMBER_LITERAL)]
if (peeked_byte = ss.peek_byte) && NUMBER_TABLE[peeked_byte]
ss.pos -= 1
@output << [:number, ss.scan(NUMBER_LITERAL)]
else
@output << special
end
@@ -198,25 +192,25 @@ module Liquid
@output << special
end
elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])
@ss.scan_byte
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
ss.scan_byte
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
@output << found
@ss.scan_byte
ss.scan_byte
else
raise_syntax_error(start_pos)
raise_syntax_error(start_pos, ss)
end
elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])
@ss.scan_byte
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
ss.scan_byte
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
@output << found
@ss.scan_byte
ss.scan_byte
else
@output << SINGLE_COMPARISON_TOKENS[peeked]
end
else
type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]
if type && (t = @ss.scan(pattern))
if type && (t = ss.scan(pattern))
# Special case for "contains"
@output << if type == :id && t == "contains" && @output.last&.first != :dot
COMPARISON_CONTAINS
@@ -224,19 +218,21 @@ module Liquid
[type, t]
end
else
raise_syntax_error(start_pos)
raise_syntax_error(start_pos, ss)
end
end
end
# rubocop:enable Metrics/BlockNesting
@output << EOS
ensure
StringScannerPool.release(ss)
end
def raise_syntax_error(start_pos)
@ss.pos = start_pos
def raise_syntax_error(start_pos, ss)
ss.pos = start_pos
# the character could be a UTF-8 character, use getch to get all the bytes
raise SyntaxError, "Unexpected character #{@ss.getch}"
raise SyntaxError, "Unexpected character #{ss.getch}"
end
end
+23
View File
@@ -0,0 +1,23 @@
module Liquid
class StringScannerPool
class << self
def pop(input)
@ss_pool ||= [StringScanner.new("")] * 5
if @ss_pool.empty?
StringScanner.new(input)
else
ss = @ss_pool.pop
ss.string = input
ss
end
end
def release(ss)
binding.irb if ss.nil?
@ss_pool ||= []
@ss_pool << ss
end
end
end
end
+3 -9
View File
@@ -56,12 +56,6 @@ module Liquid
CLOSE_CURLEY = "}".ord
PERCENTAGE = "%".ord
class << self
def string_scanner
@string_scanner ||= StringScanner.new("")
end
end
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@@ -91,13 +85,13 @@ module Liquid
if @for_liquid_tag
@tokens = @source.split("\n")
else
@ss = self.class.string_scanner
@ss.string = @source
@ss = StringScannerPool.pop(@source)
@tokens << shift_normal until @ss.eos?
end
@ss = nil
@source = nil
ensure
StringScannerPool.release(@ss) if @ss
end
def shift_normal