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 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
@@ -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