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 +4 -0
- data/.travis.yml +9 -0
- data/README.md +9 -3
- data/Rakefile +12 -0
- data/atomic.gemspec +1 -1
- data/ext/atomic_reference.c +36 -31
- data/ext/org/jruby/ext/atomic/AtomicReferenceLibrary.java +58 -20
- data/lib/atomic.rb +3 -100
- data/lib/atomic/concurrent_update_error.rb +6 -0
- data/lib/atomic/delegated_update.rb +25 -0
- data/lib/atomic/direct_update.rb +25 -0
- data/lib/atomic/fallback.rb +43 -0
- data/lib/atomic/jruby.rb +2 -0
- data/lib/atomic/rbx.rb +11 -0
- data/lib/atomic/ruby.rb +2 -0
- metadata +10 -2
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
atomic: An atomic reference implementation for JRuby and
|
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
|
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"
|
data/ext/atomic_reference.c
CHANGED
@@ -13,62 +13,67 @@
|
|
13
13
|
#include <ruby.h>
|
14
14
|
|
15
15
|
static void ir_mark(void *value) {
|
16
|
-
|
16
|
+
rb_gc_mark_maybe((VALUE) value);
|
17
17
|
}
|
18
18
|
|
19
19
|
static VALUE ir_alloc(VALUE klass) {
|
20
|
-
|
20
|
+
return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL);
|
21
21
|
}
|
22
22
|
|
23
|
-
static VALUE ir_initialize(VALUE
|
24
|
-
|
25
|
-
|
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
|
-
|
33
|
+
return (VALUE) DATA_PTR(self);
|
30
34
|
}
|
31
35
|
|
32
36
|
static VALUE ir_set(VALUE self, VALUE new_value) {
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
54
|
+
if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
|
55
|
+
return Qtrue;
|
56
|
+
}
|
53
57
|
#else
|
54
|
-
#
|
58
|
+
#error No CAS operation available for this platform
|
55
59
|
#endif
|
56
|
-
|
60
|
+
return Qfalse;
|
57
61
|
}
|
58
62
|
|
59
63
|
void Init_atomic_reference() {
|
60
|
-
|
61
|
-
VALUE cInternalReference;
|
64
|
+
VALUE cAtomic;
|
62
65
|
|
63
|
-
|
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
|
-
|
68
|
+
rb_define_alloc_func(cAtomic, ir_alloc);
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
70
|
-
|
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
|
96
|
+
return reference;
|
77
97
|
}
|
78
98
|
|
79
99
|
@JRubyMethod(name = {"set", "value="})
|
80
100
|
public IRubyObject set(IRubyObject newValue) {
|
81
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
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,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
|
data/lib/atomic/jruby.rb
ADDED
data/lib/atomic/rbx.rb
ADDED
data/lib/atomic/ruby.rb
ADDED
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
|
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-
|
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:
|