Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of ||= and += in JRuby (jruby-lint warnings)

When I run jruby-lint on my (Rails) app, I get several of these:

Non-local operator assignment is not guaranteed to be atomic

Which point to code that looks like this:

def foo
  @foo ||= Foo.new
end

Or this:

config.assets.precompile += %w( email/email.css )

Some of which is in app/, some of which is in config/. I'm guessing that this warning is only relevant to the cases where the thing on the left is an Array, and to fix it I should use Threadsafe::Array?

Which types of these things do I need to change?

like image 359
John Bachir Avatar asked Mar 21 '23 16:03

John Bachir


1 Answers

In jruby compound expressions like ||= are not atomically executed. When you write:

foo ||= 'bar'

What is actually executed internally is something like:

1. unless foo
2.   foo = 'bar'
3. end

Because line 1 and line 2 are evaluated separately it is possible in a multi-threaded app that the state could be changed by a different thread in between these two, something like:

thread 1: foo ||= 'bar'
thread 2: foo ||= 'baz'

Which executes like:

# foo has not been set yet
1. thread 1: unless foo
2. thread 2: unless foo
3. thread 1:   foo = 'bar'
4. thread 2:   foo = 'baz' 
# ...

Note that foo will end up getting re-assigned to 'baz' by the second thread even though it already has a value. Using += is equally problematic because this:

thread 1: x += 1
thread 2: x += 1

Will be executed like this:

# x starting value of 0
1. thread1: tempvar = x + 1 # 1
2. thread2: tempvar = x + 1 # 1
3. thread1: x = tempvar     # 1
4. thread2: x = tempvar     # 1

So x should be 2 after the two operations but is in fact only incremented once.

If you are running a single-threaded app/script in jruby none of this is an issue. If you are going to be running with multiple threads then these operations are not safe to use within the executing environment of those threads if they are used on variables which are accessed by more than one thread.

In environments where thread safety matters you can fix this by wrapping the operation in a mutex or using a primitive which ensures thread safety for the specific operation.

Also the atomic gem can be used with jRuby to ensure atomicity of check & update operations. I'm not sure if it supports arrays though.

More information about managing concurrency in jRuby.

Hope this helps!

like image 85
Matt Sanders Avatar answered Apr 02 '23 00:04

Matt Sanders