I am getting an error that states:
Detected invalid array contents due to unsynchronized modifications
with concurrent users
I get this error on a Rails 3 app running JRuby 1.6.3. Here is the code
that causes the issue:
In a controller method:
[
Thread.new {
@integration_accounts = IntegrationAccount.list(@user_id)
},
Thread.new {
@notification_settings = NotificationSetting.find(@user_id) ||
NotificationSetting.new
}
].each {|thread| thread.join}
Here is the IntegrationAccount list method:
def self.list(user_id)
IntegrationAccount.root_resource.profile.listIntegrationAccounts(user_id).reduce({})
do |integration_accounts, integration_account|
integration_accounts[integration_account.service_name] =
integration_account
integration_accounts
end
end
The problem only occurs when I have the code in the controller method
wrapped in threads. When I remove the threads it works without issue.
Here is the full stack trace:
org/jruby/RubyArray.java:1451:in concat' activesupport (3.0.7) lib/active_support/dependencies.rb:108:in
new_constants’
org/jruby/RubyArray.java:1603:in each' activesupport (3.0.7) lib/active_support/dependencies.rb:107:in
new_constants’
org/jruby/RubyArray.java:1603:in each' activesupport (3.0.7) lib/active_support/dependencies.rb:91:in
new_constants’
activesupport (3.0.7) lib/active_support/dependencies.rb:599:in
new_constants_in' activesupport (3.0.7) lib/active_support/dependencies.rb:595:in
new_constants_in’
activesupport (3.0.7) lib/active_support/dependencies.rb:453:in
load_file' activesupport (3.0.7) lib/active_support/dependencies.rb:340:in
require_or_load’
activesupport (3.0.7) lib/active_support/dependencies.rb:491:in
load_missing_constant' activesupport (3.0.7) lib/active_support/dependencies.rb:183:in
const_missing’
org/jruby/RubyArray.java:1603:in each' activesupport (3.0.7) lib/active_support/dependencies.rb:181:in
const_missing’
rake (0.8.7) lib/rake.rb:2503:in const_missing' activesupport (3.0.7) lib/active_support/dependencies.rb:503:in
load_missing_constant’
activesupport (3.0.7) lib/active_support/dependencies.rb:183:in
const_missing' org/jruby/RubyArray.java:1603:in
each’
activesupport (3.0.7) lib/active_support/dependencies.rb:181:in
const_missing' rake (0.8.7) lib/rake.rb:2503:in
const_missing’
app/controllers/profiles_controller.rb:149:in settings' org/jruby/RubyProc.java:268:in
call’
org/jruby/RubyProc.java:232:in `call’
The core Ruby classes are not normally threadsafe. We do our best to
present a “nice” error (the ConcurrencyError you got) but in general
you can’t mutate them concurrently with other mutations or reads.
There’s a few ways to work around it:
- Only ever replace the array. If it’s not a lot of data, just
treating it as read-only and swapping in the new version can be very
clean
- Wrap accesses in Mutex#synchronize calls. That’s what Rails does for
similar collections internally.
- Use a JRuby-specific feature to synchronize all access to that
array. It’s NON STANDARD, of course:
require ‘jruby/synchronized’
class MyArray < Array
include JRuby::Synchronized
end
Instances of this new Array subclass will have all methods
synchronized, so you can’t mutate concurrent with other reads or
mutations.
Does that answer your question? Perhaps this deserves a wiki entry?
As a newcomer to JRuby, I would say that threading and concurrency are
one of the areas where the documentation might be improved, both in the
javadocs, the wiki and the book (excellent in all other regards, btw).
The only effective way to gain knowledge of what’s going on is to try
lots of different approaches and see what happens…
For example, here is a quick list of issues for which I could find no
answer in the documentation:
Let’s assume for performance reasons I need to share the same runtime
across many Java threads, with each calling guaranteed threadsafe Ruby
scripts.
-
Is container.runScriptlet() itself threadsafe? In other words, what
would happen if two threads tried to load the same script
simultaneously? If I synchronized externally on container then the lock
would last not just for the loading of the script, but also its
execution (which is a waste because the scripts themselves are always
threadsafe)
-
How does container.runScriptlet() handle loading a file which has
already been loaded? Does it check the timestamps or load the script
regardless? What would happen if one thread was running the original
script and another thread attempted to reload it?
-
How are dependencies handled? If a loaded dependency changes on file,
how does one get the new version into the runtime? As in point (2), how
is the synchronization handled?
I’ve got many more questions but I just wanted to give a flavour.
Best,
D.
Thanks Charlie, I will try this out today. It probably would be a good
thing to put on the wiki, I can attempt to update it based on the
information you provided below unless you would rather write about it in
further detail.
On Wed, Aug 17, 2011 at 3:14 AM, Charles Oliver N.