From 1fab2002e3385c40ef48008b649d78ce5e16a868 Mon Sep 17 00:00:00 2001 From: Nikita Afanasenko Date: Wed, 14 Nov 2012 09:58:33 +0400 Subject: Make XmlMini.with_backend usable with threads `XmlMini.with_backend` now may be safely used with threads: Thread.new do XmlMini.with_backend("REXML") { rexml_power } end Thread.new do XmlMini.with_backend("LibXML") { libxml_power } end Each thread will use it's own backend. --- activesupport/CHANGELOG.md | 13 ++++++ activesupport/lib/active_support/xml_mini.rb | 38 +++++++++++++---- activesupport/test/xml_mini_test.rb | 62 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 7daa0d5bca..b4c49918d1 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,18 @@ ## Rails 4.0.0 (unreleased) ## +* `XmlMini.with_backend` now may be safely used with threads: + + Thread.new do + XmlMini.with_backend("REXML") { rexml_power } + end + Thread.new do + XmlMini.with_backend("LibXML") { libxml_power } + end + + Each thread will use it's own backend. + + *Nikita Afanasenko* + * Dependencies no longer trigger Kernel#autoload in remove_const [fixes #8213]. *Xavier Noria* * Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead* diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 88f9acb588..d082a0a499 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -76,23 +76,24 @@ module ActiveSupport ) end - attr_reader :backend delegate :parse, :to => :backend + def backend + current_thread_backend || @backend + end + def backend=(name) - if name.is_a?(Module) - @backend = name - else - require "active_support/xml_mini/#{name.downcase}" - @backend = ActiveSupport.const_get("XmlMini_#{name}") - end + backend = name && cast_backend_name_to_module(name) + self.current_thread_backend = backend if current_thread_backend + @backend = backend end def with_backend(name) - old_backend, self.backend = backend, name + old_backend = current_thread_backend + self.current_thread_backend = name && cast_backend_name_to_module(name) yield ensure - self.backend = old_backend + self.current_thread_backend = old_backend end def to_tag(key, value, options) @@ -163,6 +164,25 @@ module ActiveSupport f.content_type = entity['content_type'] f end + + private + + def current_thread_backend + Thread.current[:xml_mini_backend] + end + + def current_thread_backend=(name) + Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) + end + + def cast_backend_name_to_module(name) + if name.is_a?(Module) + name + else + require "active_support/xml_mini/#{name.downcase}" + ActiveSupport.const_get("XmlMini_#{name}") + end + end end XmlMini.backend = 'REXML' diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 504fc96493..a025279e16 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -99,4 +99,66 @@ module XmlMiniTest end # TODO: test the remaining functions hidden in #to_tag. end + + class WithBackendTest < ActiveSupport::TestCase + module REXML end + module LibXML end + module Nokogiri end + + setup do + @xml = ActiveSupport::XmlMini + end + + test "#with_backend should switch backend and then switch back" do + @xml.backend = REXML + @xml.with_backend(LibXML) do + assert_equal LibXML, @xml.backend + @xml.with_backend(Nokogiri) do + assert_equal Nokogiri, @xml.backend + end + assert_equal LibXML, @xml.backend + end + assert_equal REXML, @xml.backend + end + + test "backend switch inside #with_backend block" do + @xml.with_backend(LibXML) do + @xml.backend = REXML + assert_equal REXML, @xml.backend + end + assert_equal REXML, @xml.backend + end + end + + class ThreadSafetyTest < ActiveSupport::TestCase + module REXML end + module LibXML end + + setup do + @xml = ActiveSupport::XmlMini + end + + test "#with_backend should be thread-safe" do + @xml.backend = REXML + t = Thread.new do + @xml.with_backend(LibXML) { sleep 1 } + end + sleep 0.1 while t.status != "sleep" + + # We should get `old_backend` here even while another + # thread is using `new_backend`. + assert_equal REXML, @xml.backend + end + + test "nested #with_backend should be thread-safe" do + @xml.with_backend(REXML) do + t = Thread.new do + @xml.with_backend(LibXML) { sleep 1 } + end + sleep 0.1 while t.status != "sleep" + + assert_equal REXML, @xml.backend + end + end + end end -- cgit v1.2.3