Skip to content

Thread safety Functions#329

Open
tompng wants to merge 1 commit into
ruby:masterfrom
tompng:threadsafe_xpath
Open

Thread safety Functions#329
tompng wants to merge 1 commit into
ruby:masterfrom
tompng:threadsafe_xpath

Conversation

@tompng

@tompng tompng commented Jun 7, 2026

Copy link
Copy Markdown
Member

REXML::Functions have a state and is not thread-safe.
Change it to a class, always create a new instance in XPath#match.
REXML::Functions = REXML::FunctionsClass.new is kept for untested REXML::Quickpath and for test code.

Bug reproduction code

['a', 'b'].map do |name|
  Thread.new do
    doc = REXML::Document.new '<div>' + "<#{name}/>"*100+'</div>'
    # Path that never matches.
    xpath = "//#{name}[name()!='#{name}']"
    loop do
      res = REXML::XPath.match(doc, xpath)
      raise "Shouldn't happen" unless res.empty?
    end
  end
end
sleep 100

After about 10 seconds, it raises "Shouldn't happen"

About deleted include Functions

Functions doesn't have instance methods, only have one constant for internal use. include Functions had no effect.

irb(main):001> REXML::VERSION
=> "3.4.4"
irb(main):002> REXML::Functions.instance_methods
=> []
irb(main):003> REXML::Functions.constants
=> [:INTERNAL_METHODS]

Copilot AI review requested due to automatic review settings June 7, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Refactors REXML XPath function handling to use instance-scoped state (context/namespaces/variables) instead of module-level shared state, improving isolation and enabling safer concurrent use.

Changes:

  • Introduces FunctionsClass with instance variables for context, namespace_context, and variables, plus a new default singleton Functions.
  • Updates XPathParser to use an instance of FunctionsClass for boolean/number/string conversions and function dispatch.
  • Removes include Functions from REXML::XPath and REXML::QuickPath.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
lib/rexml/xpath_parser.rb Switches function evaluation and coercions to an instance (@functions) rather than global Functions state.
lib/rexml/xpath.rb Removes include Functions, altering how XPath functions are accessed through REXML::XPath.
lib/rexml/quickpath.rb Removes include Functions, altering QuickPath’s access to XPath functions.
lib/rexml/functions.rb Replaces module Functions with FunctionsClass and introduces a default singleton instance Functions.
Comments suppressed due to low confidence (1)

lib/rexml/functions.rb:1

  • @@available_functions is automatically populated from all non-internal instance methods via method_added, and send will then invoke them via super (bypassing visibility checks). This makes it easy to accidentally expose non-XPath helper methods as callable XPath functions when new instance methods are added in the future. A safer approach is to explicitly whitelist exported XPath functions (or mark helper methods private and exclude private methods from registration), rather than implicitly exporting everything not listed in INTERNAL_METHODS.
# frozen_string_literal: false

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rexml/functions.rb
Comment on lines +10 to +17
class FunctionsClass
@@available_functions = {}
@@context = nil
@@namespace_context = {}
@@variables = {}

def initialize
@context = nil
@namespace_context = {}
@variables = {}
end
Comment thread lib/rexml/functions.rb
Comment on lines +450 to +453
# Using this singleton instance may cause thread-safety issues
# especially when accessing variables, context and namespace_context.
# Consider instantiating your own FunctionsClass object.
Functions = FunctionsClass.new
Comment thread lib/rexml/xpath.rb
Comment on lines 5 to 10
module REXML
# Wrapper class. Use this class to access the XPath functions.
class XPath
include Functions
# A base Hash object, supposing to be used when initializing a
# default empty namespaces set, but is currently unused.
# TODO: either set the namespaces=EMPTY_HASH, or deprecate this.
Comment thread lib/rexml/functions.rb
Comment on lines +439 to 447
def send(name, *args)
if @@available_functions[name.to_sym]
super
else
# TODO: Maybe, this is not XPath spec behavior.
# This behavior must be reconsidered.
XPath.match(@@context[:node], name.to_s)
XPath.match(@context[:node], name.to_s)
end
end
Comment thread lib/rexml/functions.rb
Comment on lines +450 to +453
# Using this singleton instance may cause thread-safety issues
# especially when accessing variables, context and namespace_context.
# Consider instantiating your own FunctionsClass object.
Functions = FunctionsClass.new
REXML::Functions have a state and is not thread-safe.
Change it to a class, always create a new instance in XPath#match.
`REXML::Functions = REXML::FunctionsClass.new` is kept for untested REXML::Quickpath and for test code.
@tompng tompng force-pushed the threadsafe_xpath branch from 9a862a6 to 8d3faa4 Compare June 8, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants