atomic 1.0.2 → 1.1.0

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/.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: