Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions concurrent-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ Gem::Specification.new do |s|
end

s.required_ruby_version = '>= 1.9.3'

unless defined?(JRUBY_VERSION)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit is only viable because we create a JRuby-specific gem build. The conditional statement in the gemspec file is not processed at runtime. It is only processed when the gem is built. Therefore it should prevent the Ref gem from being installed when using the JRuby-specific build. This has not been tested, however.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍 I think we can merge this PR after you test the build.

s.add_dependency 'ref', '~> 1.0.5'
end
end
67 changes: 43 additions & 24 deletions lib/concurrent/atomic/thread_local_var.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,56 @@

module Concurrent

module ThreadLocalRubyStorage
class AbstractThreadLocalVar

def allocate_storage
@storage = Atomic.new Hash.new
end
module ThreadLocalRubyStorage

def get
@storage.get[Thread.current]
end
protected

def set(value)
@storage.update { |s| s.merge Thread.current => value }
end
unless RUBY_PLATFORM == 'java'
require 'ref'
end

end
def allocate_storage
@storage = Ref::WeakKeyMap.new
end

module ThreadLocalJavaStorage
def get
@storage[Thread.current]
end

protected
def set(value, &block)
key = Thread.current

def allocate_storage
@var = java.lang.ThreadLocal.new
end
@storage[key] = value

def get
@var.get
if block_given?
begin
block.call
ensure
@storage.delete key
end
end
end
end

def set(value)
@var.set(value)
end
module ThreadLocalJavaStorage

end
protected

class AbstractThreadLocalVar
def allocate_storage
@var = java.lang.ThreadLocal.new
end

def get
@var.get
end

def set(value)
@var.set(value)
end

end

NIL_SENTINEL = Object.new

Expand All @@ -58,13 +73,17 @@ def value
end

def value=(value)
bind value
end

def bind(value, &block)
if value.nil?
stored_value = NIL_SENTINEL
else
stored_value = value
end

set stored_value
set stored_value, &block

value
end
Expand Down
1 change: 0 additions & 1 deletion lib/concurrent/atomics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
require 'concurrent/atomic/cyclic_barrier'
require 'concurrent/atomic/count_down_latch'
require 'concurrent/atomic/event'
require 'concurrent/atomic/thread_local_var'
require 'concurrent/atomic/synchronization'
46 changes: 37 additions & 9 deletions spec/concurrent/atomic/thread_local_var_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

module Concurrent

require 'concurrent/atomic/thread_local_var'

describe ThreadLocalVar do

subject{ ThreadLocalVar.new }
subject { ThreadLocalVar.new }

context '#initialize' do

Expand All @@ -20,7 +22,7 @@ module Concurrent
end

it 'sets the same initial value for all threads' do
v = ThreadLocalVar.new(14)
v = ThreadLocalVar.new(14)
t1 = Thread.new { v.value }
t2 = Thread.new { v.value }
expect(t1.value).to eq 14
Expand All @@ -29,11 +31,37 @@ module Concurrent

if jruby?
it 'uses ThreadLocalJavaStorage' do
expect(subject.class.ancestors).to include(Concurrent::ThreadLocalJavaStorage)
expect(subject.class.ancestors).to include(Concurrent::AbstractThreadLocalVar::ThreadLocalJavaStorage)
end
else
it 'uses ThreadLocalNewStorage' do
expect(subject.class.ancestors).to include(Concurrent::ThreadLocalRubyStorage)
expect(subject.class.ancestors).to include(Concurrent::AbstractThreadLocalVar::ThreadLocalRubyStorage)
end
end
end

unless jruby?
context 'GC' do
it 'does not leave values behind when bind is used' do
var = ThreadLocalVar.new(0)
100.times.map do |i|
Thread.new { var.bind(i) { var.value } }
end.each(&:join)
var.value = 0
expect(var.instance_variable_get(:@storage).keys.size).to be == 1
end

it 'does not leave values behind when bind is not used' do
var = ThreadLocalVar.new(0)
100.times.map do |i|
Thread.new { var.value = i; var.value }
end.each(&:join)
var.value = 0
sleep 0.1
GC.start
sleep 0.1
GC.start
expect(var.instance_variable_get(:@storage).keys.size).to be == 1
end
end
end
Expand All @@ -46,7 +74,7 @@ module Concurrent
end

it 'returns the value after modification' do
v = ThreadLocalVar.new(14)
v = ThreadLocalVar.new(14)
v.value = 2
expect(v.value).to eq 2
end
Expand All @@ -56,7 +84,7 @@ module Concurrent
context '#value=' do

it 'sets a new value' do
v = ThreadLocalVar.new(14)
v = ThreadLocalVar.new(14)
v.value = 2
expect(v.value).to eq 2
end
Expand All @@ -67,14 +95,14 @@ module Concurrent
end

it 'does not modify the initial value for other threads' do
v = ThreadLocalVar.new(14)
v = ThreadLocalVar.new(14)
v.value = 2
t = Thread.new { v.value }
t = Thread.new { v.value }
expect(t.value).to eq 14
end

it 'does not modify the value for other threads' do
v = ThreadLocalVar.new(14)
v = ThreadLocalVar.new(14)
v.value = 2

b1 = CountDownLatch.new(2)
Expand Down