Skip to content

Loading…

Replace global Hash usage with TS::Cache #209

Closed
wants to merge 1 commit into from

7 participants

@thedarkone

This adds thread_safe gem dependency, do you want me to regenerate Gemfile and arel.gemspec?

@ernie
Ruby on Rails member

@thedarkone the dispatch cache is global, but also deterministic. Is there some real problem you can demonstrate this solving? It adds a lot of overhead, otherwise.

@thedarkone

@ernie Hash is not thread safe, that's about it. What this means is that it cannot be modified concurrently.

Try running (a couple of times) this gist on Rubinius for example: https://gist.github.com/thedarkone/559673.

@ernie
Ruby on Rails member

Right, but what I mean is that since the "downside" of this is that we potentially do a few more method lookups than necessary, but no real bugs occur, I'm not sure we need this patch, which adds a dependency and additional overhead.

Unless there is a reproducible bug, I think I'm -1 on this one.

@thedarkone

Right, but what I mean is that since the "downside" of this is that we potentially do a few more method lookups than necessary, but no real bugs occur, I'm not sure we need this patch, which adds a dependency and additional overhead.

The "downside" you are describing is what is going to happen after my patch, since ThreadSafe::Cache is thread safe.

Before the patch you're concurrently modifying a Hash instance, which is not thread safe. You might get random errors or all CPUs stuck in a infinite loop. Did you run my gist?

@ernie
Ruby on Rails member

I did not. I am on ios at the moment and unable to do so. If that is the case, then it would be a problem. I expected you were referring to a concern about potentially "losing" the capture of a cached value.

@thedarkone

Sorry, it's just that I've already been through a couple of discussions like this one :sweat_smile:. The gist I'm linking to is 3 years old after all :stuck_out_tongue:.

@ernie
Ruby on Rails member

Likewise. Didn't mean to be dismissive, it's just that the vast majority of folks in Ruby are concerned with a much less severe situation than you describe when reporting this type of thing. Also, I am severely jetlagged and grumpy. :p

@ernie
Ruby on Rails member

So, I ran the code in the gist, and also replaced it with some code that uses the Hash block syntax to set the values, as well. Both appear to work as I described in MRI, which shows how MRI-centric my thinking tends to be.

With Rubinius, I got an error about an index being out of bounds, and with JRuby, processor usage remained pegged but the process never finished.

Updating the gist to use ThreadSafe::Cache did fix the issue on JRuby, but in Rubinius (2.0.0rc1) I still get errors after running for a bit. Backtrace is as follows:

An exception occurred running ./thread.rb
    undefined method `get' on ThreadSafe::Util::XorShiftRandom (Module) (NoMethodError)

Backtrace:
   Kernel(Module)#get (method_missing) at kernel/delta/kernel.rb:81
  ThreadSafe::AtomicReferenceCacheBackend::Node#try_await_lock at \
          .gem/rbx/1.8.7/gems/thread_safe-0.1.3/lib/thread_safe/atomic_reference_cache_backend.rb:276
  ThreadSafe::AtomicReferenceCacheBackend(ThreadSafe::Cache)#try_await_lock at \
          .gem/rbx/1.8.7/gems/thread_safe-0.1.3/lib/thread_safe/atomic_reference_cache_backend.rb:759
  ThreadSafe::AtomicReferenceCacheBackend(ThreadSafe::Cache)#get_and_set at \
          .gem/rbx/1.8.7/gems/thread_safe-0.1.3/lib/thread_safe/atomic_reference_cache_backend.rb:485
  ThreadSafe::AtomicReferenceCacheBackend(ThreadSafe::Cache)#[]= at \
          .gem/rbx/1.8.7/gems/thread_safe-0.1.3/lib/thread_safe/atomic_reference_cache_backend.rb:404
              { } in Object#__script__ at thread.rb:7
                 ThreadSafe::Cache#[] at .gem/rbx/1.8.7/gems/thread_safe-0.1.3
                                         /lib/thread_safe/cache.rb:38
              { } in Object#__script__ at thread.rb:11
                 Integer(Fixnum)#times at kernel/common/integer.rb:83
              { } in Object#__script__ at thread.rb:11
                        Thread#__run__ at kernel/bootstrap/thread18.rb:56
@ernie
Ruby on Rails member

Update: because threading is fun, a second run under Rubinius completed without raising the above error.

@thedarkone

That Rubinius error is a concurrent autoload error, I thought they fixed it in rubinius/rubinius#2080.

@thedarkone

@ernie Rubinius is at RC1 for about 11 months now, can you check that your version has this commit rubinius/rubinius@1c0ccd9 from about 5 months ago? I can't repro that error with my build of rbi (not that this would be indicative of anything when it comes to threading).

@ernie
Ruby on Rails member

@thedarkone That was the actual RC1 release, I believe, as installed via ruby-install. I have been attempting to get the current dev version to build but not having any luck. :/

@rafaelfranca
Ruby on Rails member

@thedarkone could you rebase the PR?

cc @tenderlove

@tenderlove
Ruby on Rails member
@brixen

Any plans to fix this?

@headius

The patch looks good to me. If the addition of a dependency is not a problem, I'd say go for it. If, however, the dependency is a sticking point...or if @tenderlove is right and this could be calculated once and store immutably, there are some other options...

In the short term, to avoid adding another dependency, pre-calculating the cache and saving an immutable copy is probably the least invasive option.

In the long term, we can either incorporate thread_safe and use TS::Cache in a future release, or better yet we could remove the atomic gem dependency, add a dependency on concurrent-ruby (which contains or will contain all these tools).

In any case, it would be very nice to have this fixed in the next release :-)

@GeekOnCoffee

thread_safe is a dependency of activesupport, which may or may not making pulling it in here less of a concern.

I imagine nothing will be done with it without the build passing and master merged in so that it'll merge cleanly...

@tenderlove tenderlove commented on the diff
lib/arel/visitors/to_sql.rb
@@ -56,8 +57,8 @@ class ToSql < Arel::Visitors::Visitor
def initialize connection
@connection = connection
@schema_cache = connection.schema_cache
- @quoted_tables = {}
- @quoted_columns = {}
+ @quoted_tables = ThreadSafe::Cache.new
+ @quoted_columns = ThreadSafe::Cache.new
@tenderlove Ruby on Rails member

Visitor instances aren't shared among threads, so I don't think we don't need these. (Side note: I'm not sure that those caches are useful anyway)

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

@GeekOnCoffee Ahh that certainly seems like it would lessen the pain :-)

@tenderlove
Ruby on Rails member

This should be fixed in b57a11c

@tenderlove tenderlove closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 30, 2013
  1. @thedarkone
Showing with 10 additions and 5 deletions.
  1. +2 −0 Rakefile
  2. +3 −2 lib/arel/visitors/to_sql.rb
  3. +5 −3 lib/arel/visitors/visitor.rb
View
2 Rakefile
@@ -17,4 +17,6 @@ Hoe.spec 'arel' do
self.licenses = ['MIT']
self.readme_file = 'README.markdown'
self.extra_rdoc_files = FileList['README.markdown']
+
+ dependency('thread_safe', '~> 0.1')
end
View
5 lib/arel/visitors/to_sql.rb
@@ -1,5 +1,6 @@
require 'bigdecimal'
require 'date'
+require 'thread_safe'
module Arel
module Visitors
@@ -56,8 +57,8 @@ class ToSql < Arel::Visitors::Visitor
def initialize connection
@connection = connection
@schema_cache = connection.schema_cache
- @quoted_tables = {}
- @quoted_columns = {}
+ @quoted_tables = ThreadSafe::Cache.new
+ @quoted_columns = ThreadSafe::Cache.new
@tenderlove Ruby on Rails member

Visitor instances aren't shared among threads, so I don't think we don't need these. (Side note: I'm not sure that those caches are useful anyway)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
end
private
View
8 lib/arel/visitors/visitor.rb
@@ -1,3 +1,5 @@
+require 'thread_safe'
+
module Arel
module Visitors
class Visitor
@@ -7,10 +9,10 @@ def accept object
private
- DISPATCH = Hash.new do |hash, visitor_class|
+ DISPATCH = ThreadSafe::Cache.new do |hash, visitor_class|
hash[visitor_class] =
- Hash.new do |hash, node_class|
- hash[node_class] = "visit_#{(node_class.name || '').gsub('::', '_')}"
+ ThreadSafe::Cache.new(:initial_capacity => 2) do |inner_hash, node_class|
+ inner_hash[node_class] = "visit_#{(node_class.name || '').gsub('::', '_')}"
end
end
Something went wrong with that request. Please try again.