atomic 0.0.6-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.txt +33 -0
- data/Rakefile +41 -0
- data/atomic.gemspec +21 -0
- data/examples/atomic_example.rb +12 -0
- data/examples/bench_atomic.rb +109 -0
- data/lib/atomic.rb +94 -0
- data/lib/atomic_reference.jar +0 -0
- data/test/test_atomic.rb +63 -0
- metadata +64 -0
data/README.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
atomic: An atomic reference implementation for JRuby and green or GIL-threaded
|
2
|
+
Ruby implementations (MRI 1.8/1.9, Rubinius)
|
3
|
+
|
4
|
+
== Summary ==
|
5
|
+
|
6
|
+
This library provides:
|
7
|
+
|
8
|
+
* an Atomic class that guarantees atomic updates to its contained value
|
9
|
+
|
10
|
+
The Atomic class provides accessors for the contained "value" plus two update
|
11
|
+
methods:
|
12
|
+
|
13
|
+
* update will run the provided block, passing the current value and replacing
|
14
|
+
it with the block result iff the value has not been changed in the mean time.
|
15
|
+
It may run the block repeatedly if there are other concurrent updates in
|
16
|
+
progress.
|
17
|
+
* try_update will run the provided block, passing the current value and
|
18
|
+
replacing it with the block result. If the value changes before the update
|
19
|
+
can happen, it will throw Atomic::ConcurrentUpdateError.
|
20
|
+
|
21
|
+
The atomic repository is at http://github.com/headius/ruby-atomic.
|
22
|
+
|
23
|
+
== Usage ==
|
24
|
+
|
25
|
+
require 'atomic'
|
26
|
+
|
27
|
+
my_atomic = Atomic.new(0)
|
28
|
+
my_atomic.update {|v| v + 1}
|
29
|
+
begin
|
30
|
+
my_atomic.try_update {|v| v + 1}
|
31
|
+
rescue Atomic::ConcurrentUpdateError => cue
|
32
|
+
# deal with it (retry, propagate, etc)
|
33
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
desc "Run tests"
|
7
|
+
Rake::TestTask.new :test do |t|
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Run benchmarks"
|
13
|
+
task :bench do
|
14
|
+
exec "ruby -Ilib -Iext test/bench_atomic.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
if defined?(JRUBY_VERSION)
|
18
|
+
require 'ant'
|
19
|
+
|
20
|
+
directory "pkg/classes"
|
21
|
+
|
22
|
+
desc "Clean up build artifacts"
|
23
|
+
task :clean do
|
24
|
+
rm_rf "pkg/classes"
|
25
|
+
rm_rf "lib/refqueue.jar"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Compile the extension"
|
29
|
+
task :compile => "pkg/classes" do |t|
|
30
|
+
ant.javac :srcdir => "ext", :destdir => t.prerequisites.first,
|
31
|
+
:source => "1.5", :target => "1.5", :debug => true,
|
32
|
+
:classpath => "${java.class.path}:${sun.boot.class.path}"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Build the jar"
|
36
|
+
task :jar => :compile do
|
37
|
+
ant.jar :basedir => "pkg/classes", :destfile => "lib/atomic_reference.jar", :includes => "**/*.class"
|
38
|
+
end
|
39
|
+
|
40
|
+
task :package => :jar
|
41
|
+
end
|
data/atomic.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{atomic}
|
5
|
+
s.version = "0.0.6"
|
6
|
+
s.authors = ["Charles Oliver Nutter", "MenTaLguY"]
|
7
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
8
|
+
s.description = "An atomic reference implementation for JRuby and green or GIL-threaded impls"
|
9
|
+
s.email = ["headius@headius.com", "mental@rydia.net"]
|
10
|
+
s.homepage = "http://github.com/headius/ruby-atomic"
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
s.summary = "An atomic reference implementation for JRuby and green or GIL-threaded impls"
|
13
|
+
s.test_files = Dir["test/test*.rb"]
|
14
|
+
if defined?(JRUBY_VERSION)
|
15
|
+
s.files = Dir['{lib,examples,test}/**/*'] + Dir['{*.txt,*.gemspec,Rakefile}']
|
16
|
+
s.platform = 'java'
|
17
|
+
else
|
18
|
+
s.files = Dir['{lib,examples,test,ext}/**/*'] + Dir['{*.txt,*.gemspec,Rakefile}']
|
19
|
+
s.extensions = 'ext/extconf.rb'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
my_atomic = Atomic.new(0)
|
4
|
+
my_atomic.update {|v| v + 1}
|
5
|
+
puts "new value: #{my_atomic.value}"
|
6
|
+
|
7
|
+
begin
|
8
|
+
my_atomic.try_update {|v| v + 1}
|
9
|
+
rescue Atomic::ConcurrentUpdateError => cue
|
10
|
+
# deal with it (retry, propagate, etc)
|
11
|
+
end
|
12
|
+
puts "new value: #{my_atomic.value}"
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'atomic'
|
3
|
+
require 'thread'
|
4
|
+
Thread.abort_on_exception = true
|
5
|
+
|
6
|
+
$go = false # for synchronizing parallel threads
|
7
|
+
|
8
|
+
# number of updates on the value
|
9
|
+
N = ARGV[1] ? ARGV[1].to_i : 100_000
|
10
|
+
|
11
|
+
# number of threads for parallel test
|
12
|
+
M = ARGV[0] ? ARGV[0].to_i : 100
|
13
|
+
|
14
|
+
|
15
|
+
puts "*** Sequential updates ***"
|
16
|
+
Benchmark.bm(10) do |x|
|
17
|
+
value = 0
|
18
|
+
x.report "no lock" do
|
19
|
+
N.times do
|
20
|
+
value += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@lock = Mutex.new
|
25
|
+
x.report "mutex" do
|
26
|
+
value = 0
|
27
|
+
N.times do
|
28
|
+
@lock.synchronize do
|
29
|
+
value += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@atom = Atomic.new(0)
|
35
|
+
x.report "atomic" do
|
36
|
+
N.times do
|
37
|
+
@atom.update{|x| x += 1}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def para_setup(num_threads, count, &block)
|
43
|
+
if num_threads % 2 > 0
|
44
|
+
raise ArgumentError, "num_threads must be a multiple of two"
|
45
|
+
end
|
46
|
+
raise ArgumentError, "need block" unless block_given?
|
47
|
+
|
48
|
+
# Keep those threads together
|
49
|
+
tg = ThreadGroup.new
|
50
|
+
|
51
|
+
num_threads.times do |i|
|
52
|
+
diff = (i % 2 == 0) ? 1 : -1
|
53
|
+
|
54
|
+
t = Thread.new do
|
55
|
+
nil until $go
|
56
|
+
count.times do
|
57
|
+
yield diff
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
tg.add(t)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Make sure all threads are started
|
65
|
+
while tg.list.find{|t| t.status != "run"}
|
66
|
+
Thread.pass
|
67
|
+
end
|
68
|
+
|
69
|
+
# For good measure
|
70
|
+
GC.start
|
71
|
+
|
72
|
+
tg
|
73
|
+
end
|
74
|
+
|
75
|
+
def para_run(tg)
|
76
|
+
$go = true
|
77
|
+
tg.list.each{|t| t.join}
|
78
|
+
$go = false
|
79
|
+
end
|
80
|
+
|
81
|
+
puts "*** Parallel updates ***"
|
82
|
+
Benchmark.bm(10) do |bm|
|
83
|
+
# This is not secure
|
84
|
+
value = 0
|
85
|
+
tg = para_setup(M, N/M) do |diff|
|
86
|
+
value += diff
|
87
|
+
end
|
88
|
+
bm.report("no lock"){ para_run(tg) }
|
89
|
+
|
90
|
+
|
91
|
+
value = 0
|
92
|
+
@lock = Mutex.new
|
93
|
+
tg = para_setup(M, N/M) do |diff|
|
94
|
+
@lock.synchronize do
|
95
|
+
value += diff
|
96
|
+
end
|
97
|
+
end
|
98
|
+
bm.report("mutex"){ para_run(tg) }
|
99
|
+
raise unless value == 0
|
100
|
+
|
101
|
+
|
102
|
+
@atom = Atomic.new(0)
|
103
|
+
tg = para_setup(M, N/M) do |diff|
|
104
|
+
@atom.update{|x| x + diff}
|
105
|
+
end
|
106
|
+
bm.report("atomic"){ para_run(tg) }
|
107
|
+
raise unless @atom.value == 0
|
108
|
+
|
109
|
+
end
|
data/lib/atomic.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Atomic
|
4
|
+
class ConcurrentUpdateError < ThreadError
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(value=nil)
|
8
|
+
@ref = InternalReference.new(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
@ref.get
|
13
|
+
end
|
14
|
+
alias get value
|
15
|
+
|
16
|
+
def value=(new_value)
|
17
|
+
@ref.set(new_value)
|
18
|
+
new_value
|
19
|
+
end
|
20
|
+
alias set value=
|
21
|
+
|
22
|
+
def swap(new_value)
|
23
|
+
@ref.get_and_set(new_value)
|
24
|
+
end
|
25
|
+
alias get_and_set swap
|
26
|
+
|
27
|
+
def compare_and_swap(old_value, new_value)
|
28
|
+
@ref.compare_and_set(old_value, new_value)
|
29
|
+
end
|
30
|
+
alias compare_and_set compare_and_swap
|
31
|
+
|
32
|
+
# Pass the current value to the given block, replacing it
|
33
|
+
# with the block's result. May retry if the value changes
|
34
|
+
# during the block's execution.
|
35
|
+
def update
|
36
|
+
true until @ref.compare_and_set(old_value = @ref.get, new_value = yield(old_value))
|
37
|
+
new_value
|
38
|
+
end
|
39
|
+
|
40
|
+
# frozen pre-allocated backtrace to speed ConcurrentUpdateError
|
41
|
+
CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze
|
42
|
+
|
43
|
+
def try_update
|
44
|
+
old_value = @ref.get
|
45
|
+
new_value = yield old_value
|
46
|
+
unless @ref.compare_and_set(old_value, new_value)
|
47
|
+
if $VERBOSE
|
48
|
+
raise ConcurrentUpdateError, "Update failed"
|
49
|
+
else
|
50
|
+
raise ConcurrentUpdateError, "Update failed", CONC_UP_ERR_BACKTRACE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
new_value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
begin
|
58
|
+
require 'atomic_reference'
|
59
|
+
rescue LoadError
|
60
|
+
# Portable/generic (but not very memory or scheduling-efficient) fallback
|
61
|
+
class Atomic::InternalReference #:nodoc: all
|
62
|
+
def initialize(value)
|
63
|
+
@mutex = Mutex.new
|
64
|
+
@value = value
|
65
|
+
end
|
66
|
+
|
67
|
+
def get
|
68
|
+
@mutex.synchronize { @value }
|
69
|
+
end
|
70
|
+
|
71
|
+
def set(new_value)
|
72
|
+
@mutex.synchronize { @value = new_value }
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_and_set(new_value)
|
76
|
+
@mutex.synchronize do
|
77
|
+
old_value = @value
|
78
|
+
@value = new_value
|
79
|
+
old_value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def compare_and_set(old_value, new_value)
|
84
|
+
return false unless @mutex.try_lock
|
85
|
+
begin
|
86
|
+
return false unless @value.equal? old_value
|
87
|
+
@value = new_value
|
88
|
+
ensure
|
89
|
+
@mutex.unlock
|
90
|
+
end
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
Binary file
|
data/test/test_atomic.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'atomic'
|
3
|
+
|
4
|
+
class TestAtomic < Test::Unit::TestCase
|
5
|
+
def test_construct
|
6
|
+
atomic = Atomic.new
|
7
|
+
assert_equal nil, atomic.value
|
8
|
+
|
9
|
+
atomic = Atomic.new(0)
|
10
|
+
assert_equal 0, atomic.value
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_value
|
14
|
+
atomic = Atomic.new(0)
|
15
|
+
atomic.value = 1
|
16
|
+
|
17
|
+
assert_equal 1, atomic.value
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_update
|
21
|
+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
|
22
|
+
atomic = Atomic.new(1000)
|
23
|
+
res = atomic.update {|v| v + 1}
|
24
|
+
|
25
|
+
assert_equal 1001, atomic.value
|
26
|
+
assert_equal 1001, res
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_try_update
|
30
|
+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
|
31
|
+
atomic = Atomic.new(1000)
|
32
|
+
res = atomic.try_update {|v| v + 1}
|
33
|
+
|
34
|
+
assert_equal 1001, atomic.value
|
35
|
+
assert_equal 1001, res
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_swap
|
39
|
+
atomic = Atomic.new(1000)
|
40
|
+
res = atomic.swap(1001)
|
41
|
+
|
42
|
+
assert_equal 1001, atomic.value
|
43
|
+
assert_equal 1000, res
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_try_update_fails
|
47
|
+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
|
48
|
+
atomic = Atomic.new(1000)
|
49
|
+
assert_raise Atomic::ConcurrentUpdateError do
|
50
|
+
# assigning within block exploits implementation detail for test
|
51
|
+
atomic.try_update{|v| atomic.value = 1001 ; v + 1}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_update_retries
|
56
|
+
tries = 0
|
57
|
+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
|
58
|
+
atomic = Atomic.new(1000)
|
59
|
+
# assigning within block exploits implementation detail for test
|
60
|
+
atomic.update{|v| tries += 1 ; atomic.value = 1001 ; v + 1}
|
61
|
+
assert_equal 2, tries
|
62
|
+
end
|
63
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: atomic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.6
|
6
|
+
platform: java
|
7
|
+
authors:
|
8
|
+
- Charles Oliver Nutter
|
9
|
+
- MenTaLguY
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-09-06 00:00:00 Z
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: An atomic reference implementation for JRuby and green or GIL-threaded impls
|
18
|
+
email:
|
19
|
+
- headius@headius.com
|
20
|
+
- mental@rydia.net
|
21
|
+
executables: []
|
22
|
+
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files: []
|
26
|
+
|
27
|
+
files:
|
28
|
+
- lib/atomic.rb
|
29
|
+
- lib/atomic_reference.jar
|
30
|
+
- examples/atomic_example.rb
|
31
|
+
- examples/bench_atomic.rb
|
32
|
+
- test/test_atomic.rb
|
33
|
+
- README.txt
|
34
|
+
- atomic.gemspec
|
35
|
+
- Rakefile
|
36
|
+
homepage: http://github.com/headius/ruby-atomic
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.9
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: An atomic reference implementation for JRuby and green or GIL-threaded impls
|
63
|
+
test_files:
|
64
|
+
- test/test_atomic.rb
|