Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Make XmlMini.with_backend usable with threads #8219

Merged
merged 1 commit into from

3 participants

@nikitug

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/lib/active_support/xml_mini.rb
@@ -163,6 +163,27 @@ def _parse_file(file, entity)
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] = cast_backend_name_to_module(name)
+ end
+
+ def cast_backend_name_to_module(name)
+ return name unless name
@rafaelfranca Owner

Why this check?

@nikitug
nikitug added a note

@rafaelfranca to be sure we cast nil properly.

if name.nil?
  nil
elsif name.is_a?(Module)    
  name
else

Would be more readable here, wdyt?

Or Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) is better?

@rafaelfranca Owner

And what are the case where name is nil? Maybe we can avoid nil in the source, unless we want to allow the users to pass nil

@nikitug
nikitug added a note

This call will try to cast nil: self.current_thread_backend = nil, but it's not a public API, so it should be ok to use name && cast_backend_name_to_module(name).

@rafaelfranca Owner

Yes, seems fine

@nikitug
nikitug added a note

@rafaelfranca done, thanks for review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rafaelfranca

I'm getting errors in the tests.

/Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require': cannot load such file -- libxml (LoadError)
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `block in require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:212:in `load_dependency'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/xml_mini/libxml.rb:1:in `<top (required)>'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `block in require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:212:in `load_dependency'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/test/xml_mini_test.rb:3:in `<top (required)>'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `block in require'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:212:in `load_dependency'
    from /Users/rafaelmfranca/Projects/github/rails/activesupport/lib/active_support/dependencies.rb:227:in `require'
    from /Users/rafaelmfranca/.rbenv/versions/1.9.3-p327/lib/ruby/gems/1.9.1/gems/rake-10.0.1/lib/rake/rake_test_loader.rb:10:in `block (2 levels) in <main>'
    from /Users/rafaelmfranca/.rbenv/versions/1.9.3-p327/lib/ruby/gems/1.9.1/gems/rake-10.0.1/lib/rake/rake_test_loader.rb:9:in `each'
    from /Users/rafaelmfranca/.rbenv/versions/1.9.3-p327/lib/ruby/gems/1.9.1/gems/rake-10.0.1/lib/rake/rake_test_loader.rb:9:in `block in <main>'
    from /Users/rafaelmfranca/.rbenv/versions/1.9.3-p327/lib/ruby/gems/1.9.1/gems/rake-10.0.1/lib/rake/rake_test_loader.rb:4:in `select'
    from /Users/rafaelmfranca/.rbenv/versions/1.9.3-p327/lib/ruby/gems/1.9.1/gems/rake-10.0.1/lib/rake/rake_test_loader.rb:4:in `<main>'

Also it needs a rebase

@nikitug

@rafaelfranca oops, my bad. Seems like I've push -f some old code. That's two-workspaces-specific problem :smiley: Fixed.

@carlosantoniodasilva carlosantoniodasilva commented on the diff
activesupport/test/xml_mini_test.rb
((49 lines not shown))
+ 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
+
+ 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

Doesn't this belong to the other WithBackendTest?

@nikitug
nikitug added a note

@carlosantoniodasilva you're right, thanks! :smiley: Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activesupport/lib/active_support/xml_mini.rb
((11 lines not shown))
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
+ self.current_thread_backend = name if current_thread_backend
+ @backend = name && cast_backend_name_to_module(name)
@rafaelfranca Owner

I was talking with @carlosantoniodasilva and we think is better to store the casted model in a local variable and pass it to the thread local and to the instance variable. This would avoid cast the name twice. Something like this:

backend = name && cast_backend_name_to_module(name)
self.current_thread_backend = backend if current_thread_backend
@backend = backend
@nikitug
nikitug added a note

@rafaelfranca yep, makes sense! Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@nikitug nikitug 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.
1fab200
@rafaelfranca

Thank you

@rafaelfranca rafaelfranca merged commit 1fab200 into rails:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 15, 2012
  1. @nikitug

    Make XmlMini.with_backend usable with threads

    nikitug authored
    `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.
This page is out of date. Refresh to see the latest.
View
13 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*
View
38 activesupport/lib/active_support/xml_mini.rb
@@ -76,23 +76,24 @@ def content_type
)
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 @@ def _parse_file(file, entity)
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'
View
62 activesupport/test/xml_mini_test.rb
@@ -99,4 +99,66 @@ def to_xml(options) options[:builder].yo(options[:root].to_s) end
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

Doesn't this belong to the other WithBackendTest?

@nikitug
nikitug added a note

@carlosantoniodasilva you're right, thanks! :smiley: Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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
Something went wrong with that request. Please try again.