atomic 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1 +1,5 @@
1
1
  lib/atomic_reference.jar
2
+ /nbproject
3
+ ext/*.bundle
4
+ ext/*.so
5
+ ext/*.jar
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.8.7
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- atomic: An atomic reference implementation for JRuby and green or GIL-threaded
2
- Ruby implementations (MRI 1.8/1.9, Rubinius)
1
+ atomic: An atomic reference implementation for JRuby, Rubinius, and MRI.
3
2
 
4
3
  Summary
5
4
  =======
@@ -56,4 +55,11 @@ my_atomic.value = 1
56
55
  my_atomic.swap(2) # => 1
57
56
  my_atomic.compare_and_swap(2, 3) # => true, updated to 3
58
57
  my_atomic.compare_and_swap(2, 3) # => false, current is not 2
59
- ````
58
+ ````
59
+
60
+ Building
61
+ ========
62
+
63
+ As of 1.1.0, JDK8 is required to build the atomic gem, since it attempts to use
64
+ the new atomic Unsafe.getAndSetObject method only in JDK8. The resulting code
65
+ should still work fine as far back as Java 5.
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ task :default => :test
18
18
  desc "Run tests"
19
19
  Rake::TestTask.new :test do |t|
20
20
  t.libs << "lib"
21
+ t.libs << "ext"
21
22
  t.test_files = FileList["test/**/*.rb"]
22
23
  end
23
24
 
@@ -50,4 +51,15 @@ if defined?(JRUBY_VERSION)
50
51
  end
51
52
 
52
53
  task :package => :jar
54
+ else
55
+ task :package do
56
+ Dir.chdir("ext") do
57
+ # this does essentially the same thing
58
+ # as what RubyGems does
59
+ ruby "extconf.rb"
60
+ sh "make"
61
+ end
62
+ end
53
63
  end
64
+
65
+ task :test => :package
data/atomic.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{atomic}
5
- s.version = "1.0.2"
5
+ s.version = "1.1.0"
6
6
  s.authors = ["Charles Oliver Nutter", "MenTaLguY"]
7
7
  s.date = Time.now.strftime('%Y-%m-%d')
8
8
  s.description = "An atomic reference implementation for JRuby, Rubinius, and MRI"
@@ -13,62 +13,67 @@
13
13
  #include <ruby.h>
14
14
 
15
15
  static void ir_mark(void *value) {
16
- rb_gc_mark_maybe((VALUE)value);
16
+ rb_gc_mark_maybe((VALUE) value);
17
17
  }
18
18
 
19
19
  static VALUE ir_alloc(VALUE klass) {
20
- return rb_data_object_alloc(klass, (void *)Qnil, ir_mark, NULL);
20
+ return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL);
21
21
  }
22
22
 
23
- static VALUE ir_initialize(VALUE self, VALUE value) {
24
- DATA_PTR(self) = (void *)value;
25
- return Qnil;
23
+ static VALUE ir_initialize(int argc, VALUE* argv, VALUE self) {
24
+ VALUE value = Qnil;
25
+ if (rb_scan_args(argc, argv, "01", &value) == 1) {
26
+ value = argv[0];
27
+ }
28
+ DATA_PTR(self) = (void *) value;
29
+ return Qnil;
26
30
  }
27
31
 
28
32
  static VALUE ir_get(VALUE self) {
29
- return (VALUE)DATA_PTR(self);
33
+ return (VALUE) DATA_PTR(self);
30
34
  }
31
35
 
32
36
  static VALUE ir_set(VALUE self, VALUE new_value) {
33
- DATA_PTR(self) = (void *)new_value;
34
- return new_value;
37
+ DATA_PTR(self) = (void *) new_value;
38
+ return new_value;
35
39
  }
36
40
 
37
41
  static VALUE ir_get_and_set(VALUE self, VALUE new_value) {
38
- VALUE old_value;
39
- old_value = (VALUE)DATA_PTR(self);
40
- DATA_PTR(self) = (void *)new_value;
41
- return old_value;
42
+ VALUE old_value;
43
+ old_value = (VALUE) DATA_PTR(self);
44
+ DATA_PTR(self) = (void *) new_value;
45
+ return old_value;
42
46
  }
43
47
 
44
48
  static VALUE ir_compare_and_set(volatile VALUE self, VALUE expect_value, VALUE new_value) {
45
49
  #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
46
- if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) {
47
- return Qtrue;
48
- }
50
+ if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) {
51
+ return Qtrue;
52
+ }
49
53
  #elif (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
50
- if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
51
- return Qtrue;
52
- }
54
+ if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
55
+ return Qtrue;
56
+ }
53
57
  #else
54
- # error No CAS operation available for this platform
58
+ #error No CAS operation available for this platform
55
59
  #endif
56
- return Qfalse;
60
+ return Qfalse;
57
61
  }
58
62
 
59
63
  void Init_atomic_reference() {
60
- VALUE cAtomic;
61
- VALUE cInternalReference;
64
+ VALUE cAtomic;
62
65
 
63
- cAtomic = rb_const_get(rb_cObject, rb_intern("Atomic"));
64
- cInternalReference = rb_define_class_under(cAtomic, "InternalReference",
65
- rb_cObject);
66
+ cAtomic = rb_define_class_under(rb_cObject, "Atomic", rb_cObject);
66
67
 
67
- rb_define_alloc_func(cInternalReference, ir_alloc);
68
+ rb_define_alloc_func(cAtomic, ir_alloc);
68
69
 
69
- rb_define_method(cInternalReference, "initialize", ir_initialize, 1);
70
- rb_define_method(cInternalReference, "get", ir_get, 0);
71
- rb_define_method(cInternalReference, "set", ir_set, 1);
72
- rb_define_method(cInternalReference, "get_and_set", ir_get_and_set, 1);
73
- rb_define_method(cInternalReference, "compare_and_set", ir_compare_and_set, 2);
70
+ rb_define_method(cAtomic, "initialize", ir_initialize, -1);
71
+ rb_define_method(cAtomic, "get", ir_get, 0);
72
+ rb_define_method(cAtomic, "value", ir_get, 0);
73
+ rb_define_method(cAtomic, "set", ir_set, 1);
74
+ rb_define_method(cAtomic, "value=", ir_set, 1);
75
+ rb_define_method(cAtomic, "get_and_set", ir_get_and_set, 1);
76
+ rb_define_method(cAtomic, "swap", ir_get_and_set, 1);
77
+ rb_define_method(cAtomic, "compare_and_set", ir_compare_and_set, 2);
78
+ rb_define_method(cAtomic, "compare_and_swap", ir_compare_and_set, 2);
74
79
  }
@@ -24,6 +24,8 @@ import org.jruby.runtime.ObjectAllocator;
24
24
  import org.jruby.runtime.ThreadContext;
25
25
  import org.jruby.runtime.builtin.IRubyObject;
26
26
  import org.jruby.runtime.load.Library;
27
+ import org.jruby.util.unsafe.UnsafeFactory;
28
+ import org.jruby.util.unsafe.UnsafeGetter;
27
29
 
28
30
  /**
29
31
  * This library adds an atomic reference type to JRuby for use in the atomic
@@ -34,10 +36,14 @@ import org.jruby.runtime.load.Library;
34
36
  */
35
37
  public class AtomicReferenceLibrary implements Library {
36
38
  public void load(Ruby runtime, boolean wrap) throws IOException {
37
- RubyModule atomicCls = runtime.getClass("Atomic");
38
- RubyClass jrubyRefClass = runtime.defineClassUnder("InternalReference", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR, atomicCls);
39
- jrubyRefClass.setAllocator(JRUBYREFERENCE_ALLOCATOR);
40
- jrubyRefClass.defineAnnotatedMethods(JRubyReference.class);
39
+ RubyClass atomicCls = runtime.defineClass("Atomic", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
40
+ try {
41
+ sun.misc.Unsafe.class.getMethod("getAndSetObject", Object.class);
42
+ atomicCls.setAllocator(JRUBYREFERENCE8_ALLOCATOR);
43
+ } catch (Exception e) {
44
+ // leave it as Java 6/7 version
45
+ }
46
+ atomicCls.defineAnnotatedMethods(JRubyReference.class);
41
47
  }
42
48
 
43
49
  private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
@@ -45,51 +51,83 @@ public class AtomicReferenceLibrary implements Library {
45
51
  return new JRubyReference(runtime, klazz);
46
52
  }
47
53
  };
54
+
55
+ private static final ObjectAllocator JRUBYREFERENCE8_ALLOCATOR = new ObjectAllocator() {
56
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
57
+ return new JRubyReference(runtime, klazz);
58
+ }
59
+ };
48
60
 
49
61
  @JRubyClass(name="JRubyReference", parent="Object")
50
62
  public static class JRubyReference extends RubyObject {
51
- private volatile IRubyObject reference;
52
- private final static AtomicReferenceFieldUpdater<JRubyReference, IRubyObject> UPDATER =
53
- AtomicReferenceFieldUpdater.newUpdater(JRubyReference.class, IRubyObject.class, "reference");
63
+ volatile IRubyObject reference;
64
+
65
+ static final sun.misc.Unsafe UNSAFE;
66
+ static final long referenceOffset;
67
+
68
+ static {
69
+ try {
70
+ UNSAFE = UnsafeGetter.getUnsafe();
71
+ Class k = JRubyReference.class;
72
+ referenceOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("reference"));
73
+ } catch (Exception e) {
74
+ throw new RuntimeException(e);
75
+ }
76
+ }
54
77
 
55
78
  public JRubyReference(Ruby runtime, RubyClass klass) {
56
79
  super(runtime, klass);
57
- reference = runtime.getNil();
58
80
  }
59
81
 
60
82
  @JRubyMethod
61
83
  public IRubyObject initialize(ThreadContext context) {
62
- Ruby runtime = context.getRuntime();
63
- UPDATER.set(this, runtime.getNil());
64
- return runtime.getNil();
84
+ UNSAFE.putObject(this, referenceOffset, context.nil);
85
+ return context.nil;
65
86
  }
66
87
 
67
88
  @JRubyMethod
68
89
  public IRubyObject initialize(ThreadContext context, IRubyObject value) {
69
- Ruby runtime = context.getRuntime();
70
- UPDATER.set(this, value);
71
- return runtime.getNil();
90
+ UNSAFE.putObject(this, referenceOffset, value);
91
+ return context.nil;
72
92
  }
73
93
 
74
94
  @JRubyMethod(name = {"get", "value"})
75
95
  public IRubyObject get() {
76
- return UPDATER.get(this);
96
+ return reference;
77
97
  }
78
98
 
79
99
  @JRubyMethod(name = {"set", "value="})
80
100
  public IRubyObject set(IRubyObject newValue) {
81
- UPDATER.set(this, newValue);
101
+ UNSAFE.putObjectVolatile(this, referenceOffset, newValue);
82
102
  return newValue;
83
103
  }
84
104
 
85
- @JRubyMethod
105
+ @JRubyMethod(name = {"compare_and_set", "compare_and_swap"})
86
106
  public IRubyObject compare_and_set(ThreadContext context, IRubyObject oldValue, IRubyObject newValue) {
87
- return context.getRuntime().newBoolean(UPDATER.compareAndSet(this, oldValue, newValue));
107
+ return context.runtime.newBoolean(UNSAFE.compareAndSwapObject(this, referenceOffset, oldValue, newValue));
88
108
  }
89
109
 
90
- @JRubyMethod
110
+ @JRubyMethod(name = {"get_and_set", "swap"})
111
+ public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) {
112
+ // less-efficient version for Java 6 and 7
113
+ while (true) {
114
+ IRubyObject oldValue = get();
115
+ if (UNSAFE.compareAndSwapObject(this, referenceOffset, oldValue, newValue)) {
116
+ return oldValue;
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ public static class JRubyReference8 extends JRubyReference {
123
+ public JRubyReference8(Ruby runtime, RubyClass klass) {
124
+ super(runtime, klass);
125
+ }
126
+
127
+ @Override
91
128
  public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) {
92
- return UPDATER.getAndSet(this, newValue);
129
+ // efficient version for Java 8
130
+ return (IRubyObject)UNSAFE.getAndSetObject(this, referenceOffset, newValue);
93
131
  }
94
132
  }
95
133
  }
data/lib/atomic.rb CHANGED
@@ -10,106 +10,9 @@
10
10
  # See the License for the specific language governing permissions and
11
11
  # limitations under the License.
12
12
 
13
- require 'thread'
14
-
15
- class Atomic
16
- class ConcurrentUpdateError < ThreadError
17
- end
18
-
19
- def initialize(value=nil)
20
- @ref = InternalReference.new(value)
21
- end
22
-
23
- def value
24
- @ref.get
25
- end
26
- alias get value
27
-
28
- def value=(new_value)
29
- @ref.set(new_value)
30
- new_value
31
- end
32
- alias set value=
33
-
34
- def swap(new_value)
35
- @ref.get_and_set(new_value)
36
- end
37
- alias get_and_set swap
38
-
39
- def compare_and_swap(old_value, new_value)
40
- @ref.compare_and_set(old_value, new_value)
41
- end
42
- alias compare_and_set compare_and_swap
43
-
44
- # Pass the current value to the given block, replacing it
45
- # with the block's result. May retry if the value changes
46
- # during the block's execution.
47
- def update
48
- true until @ref.compare_and_set(old_value = @ref.get, new_value = yield(old_value))
49
- new_value
50
- end
51
-
52
- # frozen pre-allocated backtrace to speed ConcurrentUpdateError
53
- CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze
54
-
55
- def try_update
56
- old_value = @ref.get
57
- new_value = yield old_value
58
- unless @ref.compare_and_set(old_value, new_value)
59
- if $VERBOSE
60
- raise ConcurrentUpdateError, "Update failed"
61
- else
62
- raise ConcurrentUpdateError, "Update failed", CONC_UP_ERR_BACKTRACE
63
- end
64
- end
65
- new_value
66
- end
67
- end
68
-
69
13
  begin
70
14
  ruby_engine = defined?(RUBY_ENGINE)? RUBY_ENGINE : 'ruby'
71
- case ruby_engine
72
- when 'jruby', 'ruby'
73
- require 'atomic_reference'
74
- when 'rbx'
75
- Atomic::InternalReference = Rubinius::AtomicReference
76
- else
77
- raise LoadError
78
- end
15
+ require "atomic/#{ruby_engine}"
79
16
  rescue LoadError
80
- warn 'unsupported Ruby engine, using less-efficient Atomic impl' if $VERBOSE
81
- # Portable/generic (but not very memory or scheduling-efficient) fallback
82
- class Atomic::InternalReference #:nodoc: all
83
- def initialize(value)
84
- @mutex = Mutex.new
85
- @value = value
86
- end
87
-
88
- def get
89
- @mutex.synchronize { @value }
90
- end
91
-
92
- def set(new_value)
93
- @mutex.synchronize { @value = new_value }
94
- end
95
-
96
- def get_and_set(new_value)
97
- @mutex.synchronize do
98
- old_value = @value
99
- @value = new_value
100
- old_value
101
- end
102
- end
103
-
104
- def compare_and_set(old_value, new_value)
105
- return false unless @mutex.try_lock
106
- begin
107
- return false unless @value.equal? old_value
108
- @value = new_value
109
- ensure
110
- @mutex.unlock
111
- end
112
- true
113
- end
114
- end
115
- end
17
+ require 'atomic/fallback'
18
+ end
@@ -0,0 +1,6 @@
1
+ class Atomic
2
+ class ConcurrentUpdateError < ThreadError
3
+ # frozen pre-allocated backtrace to speed ConcurrentUpdateError
4
+ CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze
5
+ end
6
+ end
@@ -0,0 +1,25 @@
1
+ require 'atomic/concurrent_update_error'
2
+
3
+ # Define update methods that delegate to @ref field
4
+ class Atomic
5
+ # Pass the current value to the given block, replacing it
6
+ # with the block's result. May retry if the value changes
7
+ # during the block's execution.
8
+ def update
9
+ true until @ref.compare_and_set(old_value = @ref.get, new_value = yield(old_value))
10
+ new_value
11
+ end
12
+
13
+ def try_update
14
+ old_value = @ref.get
15
+ new_value = yield old_value
16
+ unless @ref.compare_and_set(old_value, new_value)
17
+ if $VERBOSE
18
+ raise ConcurrentUpdateError, "Update failed"
19
+ else
20
+ raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
21
+ end
22
+ end
23
+ new_value
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'atomic/concurrent_update_error'
2
+
3
+ # Define update methods that use direct paths
4
+ class Atomic
5
+ # Pass the current value to the given block, replacing it
6
+ # with the block's result. May retry if the value changes
7
+ # during the block's execution.
8
+ def update
9
+ true until compare_and_set(old_value = get, new_value = yield(old_value))
10
+ new_value
11
+ end
12
+
13
+ def try_update
14
+ old_value = get
15
+ new_value = yield old_value
16
+ unless compare_and_set(old_value, new_value)
17
+ if $VERBOSE
18
+ raise ConcurrentUpdateError, "Update failed"
19
+ else
20
+ raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
21
+ end
22
+ end
23
+ new_value
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ warn "#{__FILE__}:#{__LINE__}: unsupported Ruby engine `#{RUBY_ENGINE}', using less-efficient Atomic impl"
2
+
3
+ require 'thread'
4
+ require 'atomic/direct_update'
5
+
6
+ # Portable/generic (but not very memory or scheduling-efficient) fallback
7
+ class Atomic #:nodoc: all
8
+ def initialize(value = nil)
9
+ @mutex = Mutex.new
10
+ @value = value
11
+ end
12
+
13
+ def get
14
+ @mutex.synchronize { @value }
15
+ end
16
+ alias value get
17
+
18
+ def set(new_value)
19
+ @mutex.synchronize { @value = new_value }
20
+ end
21
+ alias value= set
22
+
23
+ def get_and_set(new_value)
24
+ @mutex.synchronize do
25
+ old_value = @value
26
+ @value = new_value
27
+ old_value
28
+ end
29
+ end
30
+ alias swap get_and_set
31
+
32
+ def compare_and_set(old_value, new_value)
33
+ return false unless @mutex.try_lock
34
+ begin
35
+ return false unless @value.equal? old_value
36
+ @value = new_value
37
+ ensure
38
+ @mutex.unlock
39
+ end
40
+ true
41
+ end
42
+ alias compare_and_swap compare_and_set
43
+ end
@@ -0,0 +1,2 @@
1
+ require 'atomic_reference'
2
+ require 'atomic/direct_update'
data/lib/atomic/rbx.rb ADDED
@@ -0,0 +1,11 @@
1
+ Atomic = Rubinius::AtomicReference
2
+
3
+ require 'atomic/direct_update'
4
+
5
+ # define additional aliases
6
+ class Atomic
7
+ alias value get
8
+ alias value= set
9
+ alias compare_and_swap compare_and_set
10
+ alias swap get_and_set
11
+ end
@@ -0,0 +1,2 @@
1
+ require 'atomic_reference'
2
+ require 'atomic/direct_update'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-03-29 00:00:00.000000000 Z
13
+ date: 2013-04-04 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: An atomic reference implementation for JRuby, Rubinius, and MRI
16
16
  email:
@@ -24,6 +24,7 @@ files:
24
24
  - test/test_atomic.rb
25
25
  - ext/extconf.rb
26
26
  - .gitignore
27
+ - .travis.yml
27
28
  - LICENSE
28
29
  - README.md
29
30
  - Rakefile
@@ -36,6 +37,13 @@ files:
36
37
  - ext/atomic_reference.c
37
38
  - ext/org/jruby/ext/atomic/AtomicReferenceLibrary.java
38
39
  - lib/atomic.rb
40
+ - lib/atomic/concurrent_update_error.rb
41
+ - lib/atomic/delegated_update.rb
42
+ - lib/atomic/direct_update.rb
43
+ - lib/atomic/fallback.rb
44
+ - lib/atomic/jruby.rb
45
+ - lib/atomic/rbx.rb
46
+ - lib/atomic/ruby.rb
39
47
  homepage: http://github.com/headius/ruby-atomic
40
48
  licenses: []
41
49
  post_install_message: