Evaluate compiled blocks in an isolated Binding
Re-using the Binding of the XPath::Compiler#compile method would lead to
race conditions, and possibly a memory leak due to the Binding sticking
around for compiled Proc's lifetime.
By using a dedicated class (and its corresponding Binding) we can work
around this. Access to this class is not synchronized as compiled Procs
don't mutate their enclosing environment.
The race condition can be demonstrated using code such as the
following:
xml = <<-EOF
<people>
<person>
<name>Alice</name>
</person>
<person>
<name>Bob</name>
</person>
<person>
<name>Eve</name>
</person>
</people>
EOF
4.times.map do
Thread.new do
10_000.times do
document = Oga.parse_xml(xml)
document.at_xpath('people/person/name').text
end
end
end.each(&:join)
Running this code would result in NoMethodErrors due to "at_xpath"
returning a NilClass opposed to an Oga::XML::Element.
This commit is contained in:
1 parent
b07c75e964
commit
bd48dc15cc
5 files changed
+69
-1
No files matched your search
@@ -55,6 +55,7 @@ require 'oga/ruby/generator'
|
||||
|
||||
require 'oga/xpath/lexer'
|
||||
require 'oga/xpath/parser'
|
||||
require 'oga/xpath/context'
|
||||
require 'oga/xpath/compiler'
|
||||
require 'oga/xpath/conversion'
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ module Oga
|
||||
# @return [Oga::LRU]
|
||||
CACHE = LRU.new
|
||||
|
||||
# Context for compiled Procs. As compiled Procs do not mutate the
|
||||
# enclosing environment we can just re-use the same instance without
|
||||
# synchronization.
|
||||
CONTEXT = Context.new
|
||||
|
||||
# Wildcard for node names/namespace prefixes.
|
||||
STAR = '*'
|
||||
|
||||
@@ -86,7 +91,7 @@ module Oga
|
||||
generator = Ruby::Generator.new
|
||||
source = generator.process(proc_ast)
|
||||
|
||||
eval(source)
|
||||
CONTEXT.evaluate(source)
|
||||
ensure
|
||||
reset
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module Oga
|
||||
module XPath
|
||||
# Class used as the context for compiled XPath Procs.
|
||||
#
|
||||
# The binding of this class is used for the binding of Procs compiled by
|
||||
# {Compiler}. Not using a specific binding would result in the procs
|
||||
# using the binding of {Compiler#compile}, which could lead to race
|
||||
# conditions.
|
||||
class Context
|
||||
# @param [String] string
|
||||
# @return [Proc]
|
||||
def evaluate(string)
|
||||
binding.eval(string)
|
||||
end
|
||||
end # Context
|
||||
end # XPath
|
||||
end # Oga
|
||||
Reference in new issue
Block a user