Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread safety problem in template loading: "can't add a new key into hash during iteration" #24627

Closed
jnicklas opened this issue Apr 19, 2016 · 43 comments

Comments

@jnicklas
Copy link
Contributor

jnicklas commented Apr 19, 2016

Steps to reproduce

This is a thread safety, as such it cannot be reliably reproduced.

Expected behavior

Template loading and compilation is performed lazily, even when config.eager_load = true. Since this needs to work in production where multiple threads might try to load a template at the same time, it needs to be thread safe.

Actual behavior

Template loading is not thread safe, occasionally causing the following error:

#<ActionView::Template::Error: can't add a new key into hash during iteration>

See full trace here: https://gist.github.com/jnicklas/21b570f19c50d958f5024566dfd052f0

System configuration

Rails version: 5.0.0.beta3 (rev db9bc80)

Ruby version: ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]

@jnicklas
Copy link
Contributor Author

Looking at the code here, I'm wondering if the issue has something to do with the rather clever nested hashmap thing that's going on there. It seems that the series of proc defaults is constructing an ever deeper set of nested maps.

Since @data seems to be entirely local to Cache and we're always accessing @data[key][name][prefix][partial][locals] it should be possible to switch to using an array as key instead. The additional object allocation on lookup might be prohibitive though; I don't know how optimized this code is. Basically we'd change this:

@data[key][name][prefix][partial][locals]

To this:

@data[[key, name, prefix, partial, locals]]

It's also possible that this is actually a bug in concurrent-ruby.

@jnicklas
Copy link
Contributor Author

Also of note: it seems that this is a regression. This issue did not occur with earlier versions of Rails, it might be due to the switch from ThreadSafe::Cache to Concurrent::Map, but the project that I'm seeing this error in tracks Rails master and it is unclear when exactly this issue started happening (yay non-determinism).

@vipulnsward
Copy link
Member

cc @matthewd

@jeremy
Copy link
Member

jeremy commented Apr 19, 2016

Looks like those cache[…] ||= … should be cache.put_if_absent …

@jeremy
Copy link
Member

jeremy commented Apr 19, 2016

No, looks like the default blocks should be protected by the write mutex.

@matthewd
Copy link
Member

I think the iteration is occurring on a different thread, which I imagine would make this a concurrent-ruby issue.

@jdantonio @thedarkone should C::Map protect us from this problem (one thread manipulating it while another iterates)? It feels like it should.. but I also may be completely misreading what's going on.

@thedarkone
Copy link
Contributor

This shouldn't be happening (obviously 😢).

@matthewd you are correct in assuming the iteration must be happening on a diff thread, I can't image it to be an in issue of nested @data[key][name][prefix][partial][locals] look ups or anything like that.

On the other hand, the iteration from the other thread should also never cause this! Since the reported issue is happening on MRI, where C::Map is just a glorified thin wrapper over native Hash, it is quite obvious that a thread iterating over a hash (e.g. Hash#each) will trigger a can't add a new key into hash during iteration error on all other threads attempting to insert new keys. However concurrent-ruby is aware of that: on MRI all iterative methods of C::Map pre-dup the underlying Hash when iterating (this is obviously inefficient, especially for large maps, but we more than make up for this by fully relying on MRI's C-based Hash).

I also just torture tested iteration with insertion on MRI and couldn't reproduce 😿.


This issue did not occur with earlier versions of Rails, it might be due to the switch from ThreadSafe::Cache to Concurrent::Map.

Concurrent::Map is a copy-pasted and renamed ThreadSafe::Cache. Additionally, there were almost no changes to the code in the last 3-4 years, so this shouldn't be a version thing either.

@jnicklas can you make sure you're not using some code that perhaps pries open Concurrent::Map accessing its underlying @backend i-var? You could maybe test that by manually changing concurrent-ruby/lib/concurrent/collection/map/non_concurrent_map_backend.rb and replacing @backend with @__backend__.

PS: Rails doesn't iterate the said C::Maps, does it?

@jeremy
Copy link
Member

jeremy commented Apr 19, 2016

Only obvious spot where we iterate is when we #clear the cache. That uses rb_hash_foreach which bumps iter_level. But we only use that in our test suite.

The other option is that we're being rehashed (that also iterates). That happens when we're duped, so probably e.g. PathSet#initialize_copy. If we dupe the cache (say, due to view path changes) and it's concurrently accessed while it's rehashing → boom.

(Confirm, C::Map should give the iterating thread something like a share lock to protect iteration.)

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Apr 19, 2016
@thedarkone
Copy link
Contributor

@jeremy wow, thanks, I did not know that. I should dig into MRI's sources to investigate. Share lock is problematic - one of the design goals of C::Map is that threads don't block each other out, a thread iterating a concurrent map is not allowed to block anybody.

I always assumed that Hash#clear just nulls/clears out internal storage and never leaves the C-code (thus never releasing GVL). I'll maybe fix that by swapping @backend.clear for @backend = {}.

The other option is that we're being rehashed (that also iterates). That happens when we're duped.

This is indeed a disaster as I also assumed that dup also never leaves C.

@rafaelfranca
Copy link
Member

Maybe it is the same problem with rails/sprockets#242?

@jeremy
Copy link
Member

jeremy commented Apr 19, 2016

a thread iterating a concurrent map is not allowed to block anybody.

👍

Looks like Map#value? hits NonConcurrentMapBackend#value? (which iterates) without synchronizing access in MriMapBackend. #clear is covered.

I always assumed that Hash#clear just nulls/clears out internal storage and never leaves the C-code (thus never releasing GVL).

If nobody else is iterating, it does just clear the table. Otherwise it also iterates and marks each entry deleted.

This is indeed a disaster as I also assumed that dup also never leaves C.

I'm not sure the disaster is occurring 😁 Just looking for points where we could have someone else iterating since there are so few possibilities here.

@jeremy
Copy link
Member

jeremy commented Apr 19, 2016

@rafaelfranca Looks like we aren't handling concurrent cache access at all in the Sprockets loader :)

@thedarkone
Copy link
Contributor

thedarkone commented Apr 19, 2016

I've created a ticket over at concurrent-ruby ruby-concurrency/concurrent-ruby#528.

@jeremy, if you have the time, can you take a look at what I have there?

@jdantonio
Copy link
Contributor

@thedarkone Thank you very much for jumping on this so quickly! Your insight is always extremely valuable. I'll continue the conversation on the c-r thread.

@jeremy
Copy link
Member

jeremy commented Apr 24, 2016

@thedarkone Looking good @ ruby-concurrency/concurrent-ruby#529

We can bump concurrent-ruby dep to 1.0.2 once that's merged & released.

@jeremy
Copy link
Member

jeremy commented Apr 24, 2016

This needn't block 5.0.0 release candidate, though it is highly desirable to coincide with release since it's a regression from apps' point of view. Removing milestone.

@jeremy jeremy removed this from the 5.0.0 milestone Apr 24, 2016
@jnicklas
Copy link
Contributor Author

It seems that the fix pushed to concurrent-ruby did resolve this issue for us.

@jdantonio
Copy link
Contributor

Good to hear! I'm out of town for work right now. I'll publish a patch release this weekend.

@jdantonio
Copy link
Contributor

c-r 1.0.2 has been released. It has the aforementioned fix.

@jeremy
Copy link
Member

jeremy commented May 2, 2016

Bumped to c-r 1.0.2 @ 06dc3fb

@arktisklada
Copy link
Contributor

Thanks for the hard work on this fix! I see it's been folded into the Rails 5 release, but with a similar upgrade to c-r 1.0.2 with Rails 4.2, I'm still experiencing the same issue. Any guidance?

@jeremy
Copy link
Member

jeremy commented May 3, 2016

Guidance: troubleshooting continues 😊 We're doing s bit of shadow boxing
here, tackling possible sources of concurrent iteration. If you're still
seeing it, then we've missed one.
On Tue, May 3, 2016 at 12:04 Clayton Liggitt notifications@github.com
wrote:

Thanks for the hard work on this fix! I see it's been folded into the
Rails 5 release, but with a similar upgrade to c-r 1.0.2 with Rails 4.2,
I'm still experiencing the same issue. Any guidance?


You are receiving this because you modified the open/close state.

Reply to this email directly or view it on GitHub
#24627 (comment)

@rafaelfranca
Copy link
Member

@arktisklada if you are talking about the sprockets issue, it is not related to this one.

@thedarkone
Copy link
Contributor

@rafaelfranca thank you ❤️! I just spent an hour digging through MRI's hash.c (didn't find anything new).

@arktisklada rails/sprockets#242 has nothing to do with this issue or concurrent-ruby. As per backtrace, are you seeing errors from sprockets or Rails?

@arktisklada
Copy link
Contributor

arktisklada commented May 3, 2016

@thedarkone & @rafaelfranca you're absolutely right, it's the exact same error message but a different backtrace. It's from Sprockets, and will continue the dialog there. Need to pay a little more attention to detail before sending someone off into a code hole

@jeremy
Copy link
Member

jeremy commented May 5, 2016

We're still seeing this a ~dozen times a day. Reopening pending 0 exceptions.

@jeremy jeremy reopened this May 5, 2016
@thedarkone
Copy link
Contributor

@jeremy to confirm, you are seeing this (non-sprocket) error on a Rails app with c-r 1.0.2?

@jeremy
Copy link
Member

jeremy commented May 6, 2016

@thedarkone Correct. I'll narrow it down.

@thedarkone
Copy link
Contributor

@jeremy if you are able to deploy some temporary logging in the form of:

rescue RuntimeError => e # "can't add a new key into hash during iteration" is a RuntimeError
  if e.message == "can't add a new key into hash during iteration"
    # lets see if we are able to pin down the culprit
    Thread.list.each do |t|
      puts t.backtrace.inspect
    end
  end
  raise

this could help track down the "iterator" thread?

@jeremy
Copy link
Member

jeremy commented May 27, 2016

Tracked this down to a separate issue with hash modification during iteration in Jbuilder. All good, @thedarkone.

@jeremy jeremy closed this as completed May 27, 2016
@bluestorm21
Copy link

bluestorm21 commented Mar 23, 2018

This issue still occurs randomly from time to time, I don't know why but everything work fine until it happens without any reason ! it's rare but it happens

@emerak
Copy link

emerak commented Apr 3, 2018

@jimchild49 have you been able to workaround it?

@emerak
Copy link

emerak commented Apr 3, 2018

I've seen a similar error, but on activemodel, I've been trying to repro but no luck

activemodel-5.0.6/lib/active_model/errors.rb:545→ block in apply_default_array
activemodel-5.0.6/lib/active_model/errors.rb:338→ add

currently in rails 5.0.6
ruby 2.4

@drale2k
Copy link

drale2k commented Apr 12, 2018

Got the same issue on on Rails 5.2.0.rc2

It happens randomly and a page reload fixes it until it happens again.

@enrico
Copy link

enrico commented May 22, 2018

@thedarkone : I can reproduce this pretty regularly.

It requires 2 simultaneous connections requiring asset recompilation (indicated by threads number 3 and 5 in my stacktrace file)

Here's the stacktrace after applying your temporary logging:

stacktrace.txt

@thedarkone
Copy link
Contributor

@enrico, seems my logging isn't enogh :(. I can't tell from your stacktrace.txt which of the Hashes involved is responsible for the conc. error.

Could you please try additionally capturing the Exception's backtrace:

rescue RuntimeError => e # "can't add a new key into hash during iteration" is a RuntimeError
  if e.message == "can't add a new key into hash during iteration"
    # lets see if we are able to pin down the culprit
    puts 'e.backtrace:'
    puts e.backtrace.inspect
    puts 'Threads:'
    Thread.list.each do |t|
      puts t.backtrace.inspect
    end
  end
  raise

@enrico
Copy link

enrico commented May 24, 2018

@thedarkone , here's the new exception.txt

I changed the rescue clause like this (wrote this to a file in addition to console, since I was afraid of missing the console log):

  rescue_from RuntimeError do |e|
    if e.message == "can't add a new key into hash during iteration"
      File.open('exception.txt','a') do |line|
        # lets see if we are able to pin down this exception's culprit
        line.puts '*' * 128
        line.puts 'e.backtrace:'
        line.puts e.backtrace.inspect
        line.puts '*' * 128
        line.puts 'Threads:'

        puts '*' * 128
        puts 'e.backtrace:'
        puts e.backtrace.inspect
        puts '*' * 128
        puts 'Threads:'
        Thread.list.each do |t|
          line.puts "Thread #{t.name}:"
          line.puts t.backtrace.inspect
          line.puts '*' * 128

          puts "Thread #{t.name}:"
          puts t.backtrace.inspect
          puts '*' * 128
        end
      end
      exception = true
    end
    raise
  end

@enrico
Copy link

enrico commented May 25, 2018

@thedarkone, I have one more stacktrace for you . I changed the code to get a newline format of the backtrace.

      File.open('exception.txt','a') do |line|
        # lets see if we are able to pin down this exception's culprit
        line.puts '*' * 40
        line.puts 'e.backtrace:'
        e.backtrace.each do |l|
          line.puts l
        end
        line.puts ''
        line.puts '****** Threads:'
        Thread.list.each do |t|
          line.puts "******* Thread #{t.name}:"
          t.backtrace.each do |l|
            line.puts l
          end
        end
      end

exception.txt

It looks to me like these are the 2 threads you should focus on:

e.backtrace:
/Users/enrico/.rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:171:in `replace'
/Users/enrico/.rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:171:in `replace'
/Users/enrico/.rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:381:in `collect!'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/loader.rb:85:in `asset_from_cache'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/loader.rb:35:in `load'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cached_environment.rb:20:in `block in initialize'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cached_environment.rb:47:in `load'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/legacy.rb:195:in `block in to_a'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/legacy.rb:195:in `map'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/legacy.rb:195:in `to_a'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:172:in `block in stylesheet_link_tag'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `map'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `stylesheet_link_tag'
/Users/enrico/src/mercury/frontend/app/views/layouts/doorkeeper.html.erb:10:in `_app_views_layouts_doorkeeper_html_erb___2057274144428322629_70210253350640'

and

******* Thread puma 004:
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache/file_store.rb:56:in `read'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache/file_store.rb:56:in `block in get'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache/file_store.rb:156:in `open'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache/file_store.rb:156:in `safe_open'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache/file_store.rb:54:in `get'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache.rb:208:in `get'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cache.rb:115:in `get'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/loader.rb:306:in `fetch_asset_from_dependency_cache'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/loader.rb:44:in `load'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cached_environment.rb:20:in `block in initialize'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/cached_environment.rb:47:in `load'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/base.rb:66:in `find_asset'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-3.7.1/lib/sprockets/base.rb:92:in `[]'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:355:in `find_asset'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:336:in `digest_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:325:in `asset_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:102:in `block in resolve_asset_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:242:in `block in resolve_asset'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `each'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `detect'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `resolve_asset'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:101:in `resolve_asset_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:79:in `compute_asset_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/actionview-5.1.5/lib/action_view/helpers/asset_url_helper.rb:198:in `asset_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/actionview-5.1.5/lib/action_view/helpers/asset_url_helper.rb:343:in `stylesheet_path'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:173:in `block (2 levels) in stylesheet_link_tag'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:172:in `map'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:172:in `block in stylesheet_link_tag'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `map'
/Users/enrico/.rvm/gems/ruby-2.4.2@frontend/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `stylesheet_link_tag'
/Users/enrico/src/mercury/frontend/app/views/layouts/doorkeeper.html.erb:10:in `_app_views_layouts_doorkeeper_html_erb___2057274144428322629_70210253350640'

@thedarkone
Copy link
Contributor

@enrico I've had a look at the backtraces, I think the error stems from multiple threads simultaneously going into Loader#asset_from_cache and doing concurrent map! on the same Set instances. This happens whenever an asset is cached in Sprockets::Cache's LRU @fetch_cache. All other backtraces are a red herring (ie the error has nothing to do with FileStore or EncodingUtils etc). I'm not sure why other threads are always able to make progress (by leaving Loader#asset_from_cache) between the time when exception is thrown and backtrace is logged (so we're never able to catch other threads red-handed while they are still in Loader#asset_from_cache).

The conflict can also stem from other accesses to asset[:metadata][key], ie if a different thread does Set.new(asset[:metadata][:dependecies]) (like here: https://github.com/rails/sprockets/blob/10dada6c30016ca89662c0aa48591f071b069420/lib/sprockets/context.rb#L37-L40) or iterates it like this: https://github.com/rails/sprockets/blob/10dada6c30016ca89662c0aa48591f071b069420/lib/sprockets/context.rb#L100, this all could be throwing the iteration errors.

The whole sprockets code base is not very thread safe.

I'm not sure how you'd want to proceed from here, as a simple band-aid I'd suggest a simple mutex on Sprockets compilation, by changing CachedEnviroment:

diff --git a/lib/sprockets/cached_environment.rb b/lib/sprockets/cached_environment.rb
index a6ac3690..5c025282 100644
--- a/lib/sprockets/cached_environment.rb
+++ b/lib/sprockets/cached_environment.rb
@@ -14,10 +14,12 @@ module Sprockets
     def initialize(environment)
       initialize_configuration(environment)
 
+      @load_monitor = Monitor.new
+
       @cache   = environment.cache
       @stats   = Hash.new { |h, k| h[k] = _stat(k) }
       @entries = Hash.new { |h, k| h[k] = _entries(k) }
-      @uris    = Hash.new { |h, k| h[k] = _load(k) }
+      @uris    = Hash.new { |h, k| @load_monitor.synchronized { h[k] = _load(k) } }
 
       @processor_cache_keys  = Hash.new { |h, k| h[k] = _processor_cache_key(k) }
       @resolved_dependencies = Hash.new { |h, k| h[k] = _resolve_dependency(k) }

(this only solves some issues and only for cached env that is supposed to be used in production)

@enrico
Copy link

enrico commented May 29, 2018

@thedarkone ,there's a typo, it should be @load_monitor.synchronize , not @load_monitor.synchronized

In any case, this is not really an issue for me, since I do precompile all of my assets before a deploy.

I just happen to see this issue often in development since I have some JS code that will cause a simultaneous page refresh from multiple browser tabs, increasing the likelihood of this bug showing up on my dev box after I've changed some scss files.

In the meantime, I've patched my local copy of cached_environments.rb as you suggested and will report my findings later...

I do think this bug should stay open, though.

@enrico
Copy link

enrico commented Jun 6, 2018

@thedarkone : so after patching my local copy of cached_environments.rb, I thought the problem was fixed. Normally I would see the exception on a pretty consistent basis, but it had disappeared until today, so I think it did help...I'm attaching another stack trace file in case it sheds some more light on the problem...
exception.txt

@thedarkone
Copy link
Contributor

@enrico the last backtrace doesn't help :(, notice how (per its backtrace) the other thread (Thread puma 005) is shown to be in the critical section protected by the newly introduced CachedEnvironment's @load_monitor? This, of course, should be impossible.

So what must be happening is either: the business of throwing exception, obtaining its backtrace, or getting other threads' backtraces -- allows other threads to make progress. This means that when we do get around to asking other threads for their current location via Thread#backtrace, they are no longer at the execution point where contention occurs :(.

@ubugnu
Copy link

ubugnu commented Feb 6, 2019

hope it will help:

Message: can't add a new key into hash during iteration
Ticket: 
Backtrace:
/usr/share/rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:171:in `replace'
/usr/share/rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:171:in `replace'
/usr/share/rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/set.rb:381:in `collect!'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:85:in `asset_from_cache'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:57:in `block in load'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:311:in `block in fetch_asset_from_dependency_cache'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:307:in `each'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:307:in `each_with_index'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:307:in `fetch_asset_from_dependency_cache'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/loader.rb:44:in `load'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/cached_environment.rb:20:in `block in initialize'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/cached_environment.rb:47:in `load'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/base.rb:66:in `find_asset'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-3.7.2/lib/sprockets/base.rb:92:in `[]'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:355:in `find_asset'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:336:in `digest_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:325:in `asset_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:102:in `block in resolve_asset_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:242:in `block in resolve_asset'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `each'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `detect'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:241:in `resolve_asset'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:101:in `resolve_asset_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:79:in `compute_asset_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/asset_url_helper.rb:200:in `asset_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/asset_url_helper.rb:345:in `stylesheet_path'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:173:in `block (2 levels) in stylesheet_link_tag'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:172:in `map'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:172:in `block in stylesheet_link_tag'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `map'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/helper.rb:169:in `stylesheet_link_tag'
/home/ubugnu/Dev/io.opendevices.dashboard-v2/app/views/layouts/coreui/dashboard.html.erb:17:in `block in _app_views_layouts_coreui_dashboard_html_erb___1453762901609037977_70260697373280'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/capture_helper.rb:41:in `block in capture'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/capture_helper.rb:205:in `with_output_buffer'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/capture_helper.rb:41:in `capture'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/helpers/tag_helper.rb:272:in `content_tag'
/home/ubugnu/Dev/io.opendevices.dashboard-v2/app/views/layouts/coreui/dashboard.html.erb:2:in `_app_views_layouts_coreui_dashboard_html_erb___1453762901609037977_70260697373280'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/template.rb:159:in `block in render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/notifications.rb:170:in `instrument'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/template.rb:354:in `instrument_render_template'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/template.rb:157:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/renderer/template_renderer.rb:66:in `render_with_layout'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/renderer/template_renderer.rb:52:in `render_template'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/renderer/template_renderer.rb:16:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/renderer/renderer.rb:44:in `render_template'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/renderer/renderer.rb:25:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/rendering.rb:103:in `_render_template'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/streaming.rb:219:in `_render_template'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/rendering.rb:84:in `render_to_body'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/rendering.rb:52:in `render_to_body'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/renderers.rb:142:in `render_to_body'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/abstract_controller/rendering.rb:25:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/rendering.rb:36:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:46:in `block (2 levels) in render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/core_ext/benchmark.rb:14:in `block in ms'
/usr/share/rvm/rubies/ruby-2.4.2/lib/ruby/2.4.0/benchmark.rb:308:in `realtime'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/core_ext/benchmark.rb:14:in `ms'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:46:in `block in render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:87:in `cleanup_view_runtime'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:45:in `render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/implicit_render.rb:35:in `default_render'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/basic_implicit_render.rb:6:in `block in send_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/basic_implicit_render.rb:6:in `tap'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:194:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/rendering.rb:30:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:132:in `run_callbacks'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:41:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/rescue.rb:22:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `block in instrument'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `instrument'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:134:in `process'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionview-5.2.0/lib/action_view/rendering.rb:32:in `process'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal.rb:191:in `dispatch'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_controller/metal.rb:252:in `dispatch'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:34:in `serve'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:52:in `block in serve'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `each'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `serve'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:840:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/meta_request-0.6.0/lib/meta_request/middlewares/app_request_handler.rb:13:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/meta_request-0.6.0/lib/meta_request/middlewares/meta_request_handler.rb:13:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/etag.rb:25:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/conditional_get.rb:25:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/http/content_security_policy.rb:18:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/cookies.rb:670:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:98:in `run_callbacks'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/executor.rb:14:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/web-console-3.7.0/lib/web_console/middleware.rb:135:in `call_app'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/web-console-3.7.0/lib/web_console/middleware.rb:30:in `block in call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/web-console-3.7.0/lib/web_console/middleware.rb:20:in `catch'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/web-console-3.7.0/lib/web_console/middleware.rb:20:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-contrib-2.1.0/lib/rack/contrib/response_headers.rb:17:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/meta_request-0.6.0/lib/meta_request/middlewares/headers.rb:16:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/railties-5.2.0/lib/rails/rack/logger.rb:38:in `call_app'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/railties-5.2.0/lib/rails/rack/logger.rb:26:in `block in call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:71:in `block in tagged'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:28:in `tagged'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:71:in `tagged'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/railties-5.2.0/lib/rails/rack/logger.rb:26:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/silencer-1.0.1/lib/silencer/rails/logger.rb:41:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/sprockets-rails-3.2.1/lib/sprockets/rails/quiet_assets.rb:13:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/runtime.rb:22:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/activesupport-5.2.0/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/executor.rb:14:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/actionpack-5.2.0/lib/action_dispatch/middleware/static.rb:127:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/railties-5.2.0/lib/rails/engine.rb:524:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/puma-3.12.0/lib/puma/configuration.rb:225:in `call'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/puma-3.12.0/lib/puma/server.rb:658:in `handle_request'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/puma-3.12.0/lib/puma/server.rb:472:in `process_client'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/puma-3.12.0/lib/puma/server.rb:332:in `block in run'
/home/ubugnu/.rvm/gems/ruby-2.4.2@rails5.2/gems/puma-3.12.0/lib/puma/thread_pool.rb:133:in `block in spawn_thread'

schneems added a commit to rails/sprockets that referenced this issue May 20, 2019
I'm honestly still not sure how to trigger this error, however the root cause looks like two threads trying to mutate the same values from the cache.

rails/rails#24627

The issue should be mitigated by copying the set to a local copy via `dup` and then mutating that. 

To accomplish this, two helper methods have been added.
schneems added a commit to rails/sprockets that referenced this issue May 20, 2019
I'm honestly still not sure how to trigger this error, however the root cause looks like two threads trying to mutate the same values from the cache.

rails/rails#24627

The issue should be mitigated by copying the set to a local copy via `dup` and then mutating that. 

To accomplish this, two helper methods have been added.
schneems added a commit to rails/sprockets that referenced this issue May 20, 2019
I'm honestly still not sure how to trigger this error, however the root cause looks like two threads trying to mutate the same values from the cache.

rails/rails#24627

The issue should be mitigated by copying the set to a local copy via `dup` and then mutating that. 

To accomplish this, two helper methods have been added.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests