concurrent-ruby 1.1.5
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +478 -0
- data/Gemfile +41 -0
- data/LICENSE.md +23 -0
- data/README.md +381 -0
- data/Rakefile +327 -0
- data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
- data/lib/concurrent-ruby.rb +1 -0
- data/lib/concurrent.rb +134 -0
- data/lib/concurrent/agent.rb +587 -0
- data/lib/concurrent/array.rb +66 -0
- data/lib/concurrent/async.rb +459 -0
- data/lib/concurrent/atom.rb +222 -0
- data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
- data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent/atomic/atomic_reference.rb +204 -0
- data/lib/concurrent/atomic/count_down_latch.rb +100 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
- data/lib/concurrent/atomic/event.rb +109 -0
- data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
- data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
- data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
- data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
- data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
- data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
- data/lib/concurrent/atomic/read_write_lock.rb +254 -0
- data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
- data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
- data/lib/concurrent/atomic/semaphore.rb +145 -0
- data/lib/concurrent/atomic/thread_local_var.rb +104 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
- data/lib/concurrent/atomics.rb +10 -0
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
- data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
- data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
- data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
- data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
- data/lib/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +73 -0
- data/lib/concurrent/concern/logging.rb +32 -0
- data/lib/concurrent/concern/obligation.rb +220 -0
- data/lib/concurrent/concern/observable.rb +110 -0
- data/lib/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent/configuration.rb +184 -0
- data/lib/concurrent/constants.rb +8 -0
- data/lib/concurrent/dataflow.rb +81 -0
- data/lib/concurrent/delay.rb +199 -0
- data/lib/concurrent/errors.rb +69 -0
- data/lib/concurrent/exchanger.rb +352 -0
- data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
- data/lib/concurrent/executor/executor_service.rb +185 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
- data/lib/concurrent/executor/immediate_executor.rb +66 -0
- data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
- data/lib/concurrent/executor/java_executor_service.rb +91 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
- data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent/executor/serialized_execution.rb +107 -0
- data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent/executor/simple_executor_service.rb +100 -0
- data/lib/concurrent/executor/single_thread_executor.rb +56 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
- data/lib/concurrent/executor/timer_set.rb +173 -0
- data/lib/concurrent/executors.rb +20 -0
- data/lib/concurrent/future.rb +141 -0
- data/lib/concurrent/hash.rb +59 -0
- data/lib/concurrent/immutable_struct.rb +93 -0
- data/lib/concurrent/ivar.rb +207 -0
- data/lib/concurrent/map.rb +337 -0
- data/lib/concurrent/maybe.rb +229 -0
- data/lib/concurrent/mutable_struct.rb +229 -0
- data/lib/concurrent/mvar.rb +242 -0
- data/lib/concurrent/options.rb +42 -0
- data/lib/concurrent/promise.rb +579 -0
- data/lib/concurrent/promises.rb +2167 -0
- data/lib/concurrent/re_include.rb +58 -0
- data/lib/concurrent/scheduled_task.rb +318 -0
- data/lib/concurrent/set.rb +66 -0
- data/lib/concurrent/settable_struct.rb +129 -0
- data/lib/concurrent/synchronization.rb +30 -0
- data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent/synchronization/abstract_object.rb +24 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
- data/lib/concurrent/synchronization/condition.rb +60 -0
- data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent/synchronization/jruby_object.rb +45 -0
- data/lib/concurrent/synchronization/lock.rb +36 -0
- data/lib/concurrent/synchronization/lockable_object.rb +74 -0
- data/lib/concurrent/synchronization/mri_object.rb +44 -0
- data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
- data/lib/concurrent/synchronization/object.rb +183 -0
- data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
- data/lib/concurrent/synchronization/rbx_object.rb +49 -0
- data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/concurrent/synchronization/volatile.rb +36 -0
- data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent/thread_safe/util.rb +16 -0
- data/lib/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
- data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
- data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
- data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
- data/lib/concurrent/timer_task.rb +334 -0
- data/lib/concurrent/tuple.rb +86 -0
- data/lib/concurrent/tvar.rb +258 -0
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +56 -0
- data/lib/concurrent/utility/monotonic_time.rb +58 -0
- data/lib/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/concurrent/utility/native_integer.rb +53 -0
- data/lib/concurrent/utility/processor_counter.rb +158 -0
- data/lib/concurrent/version.rb +3 -0
- metadata +193 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
package com.concurrent_ruby.ext;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
import java.util.concurrent.atomic.AtomicLong;
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.RubyClass;
|
7
|
+
import org.jruby.RubyFixnum;
|
8
|
+
import org.jruby.RubyModule;
|
9
|
+
import org.jruby.RubyObject;
|
10
|
+
import org.jruby.anno.JRubyClass;
|
11
|
+
import org.jruby.anno.JRubyMethod;
|
12
|
+
import org.jruby.runtime.ObjectAllocator;
|
13
|
+
import org.jruby.runtime.ThreadContext;
|
14
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
15
|
+
import org.jruby.runtime.load.Library;
|
16
|
+
import org.jruby.runtime.Block;
|
17
|
+
|
18
|
+
public class JavaAtomicFixnumLibrary implements Library {
|
19
|
+
|
20
|
+
public void load(Ruby runtime, boolean wrap) throws IOException {
|
21
|
+
RubyModule concurrentMod = runtime.defineModule("Concurrent");
|
22
|
+
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicFixnum", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
|
23
|
+
|
24
|
+
atomicCls.defineAnnotatedMethods(JavaAtomicFixnum.class);
|
25
|
+
}
|
26
|
+
|
27
|
+
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
|
28
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
29
|
+
return new JavaAtomicFixnum(runtime, klazz);
|
30
|
+
}
|
31
|
+
};
|
32
|
+
|
33
|
+
@JRubyClass(name = "JavaAtomicFixnum", parent = "Object")
|
34
|
+
public static class JavaAtomicFixnum extends RubyObject {
|
35
|
+
|
36
|
+
private AtomicLong atomicLong;
|
37
|
+
|
38
|
+
public JavaAtomicFixnum(Ruby runtime, RubyClass metaClass) {
|
39
|
+
super(runtime, metaClass);
|
40
|
+
}
|
41
|
+
|
42
|
+
@JRubyMethod
|
43
|
+
public IRubyObject initialize(ThreadContext context) {
|
44
|
+
this.atomicLong = new AtomicLong(0);
|
45
|
+
return context.nil;
|
46
|
+
}
|
47
|
+
|
48
|
+
@JRubyMethod
|
49
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
|
50
|
+
this.atomicLong = new AtomicLong(rubyFixnumToLong(value));
|
51
|
+
return context.nil;
|
52
|
+
}
|
53
|
+
|
54
|
+
@JRubyMethod(name = "value")
|
55
|
+
public IRubyObject getValue() {
|
56
|
+
return getRuntime().newFixnum(atomicLong.get());
|
57
|
+
}
|
58
|
+
|
59
|
+
@JRubyMethod(name = "value=")
|
60
|
+
public IRubyObject setValue(ThreadContext context, IRubyObject newValue) {
|
61
|
+
atomicLong.set(rubyFixnumToLong(newValue));
|
62
|
+
return context.nil;
|
63
|
+
}
|
64
|
+
|
65
|
+
@JRubyMethod(name = {"increment", "up"})
|
66
|
+
public IRubyObject increment() {
|
67
|
+
return getRuntime().newFixnum(atomicLong.incrementAndGet());
|
68
|
+
}
|
69
|
+
|
70
|
+
@JRubyMethod(name = {"increment", "up"})
|
71
|
+
public IRubyObject increment(IRubyObject value) {
|
72
|
+
long delta = rubyFixnumToLong(value);
|
73
|
+
return getRuntime().newFixnum(atomicLong.addAndGet(delta));
|
74
|
+
}
|
75
|
+
|
76
|
+
@JRubyMethod(name = {"decrement", "down"})
|
77
|
+
public IRubyObject decrement() {
|
78
|
+
return getRuntime().newFixnum(atomicLong.decrementAndGet());
|
79
|
+
}
|
80
|
+
|
81
|
+
@JRubyMethod(name = {"decrement", "down"})
|
82
|
+
public IRubyObject decrement(IRubyObject value) {
|
83
|
+
long delta = rubyFixnumToLong(value);
|
84
|
+
return getRuntime().newFixnum(atomicLong.addAndGet(-delta));
|
85
|
+
}
|
86
|
+
|
87
|
+
@JRubyMethod(name = "compare_and_set")
|
88
|
+
public IRubyObject compareAndSet(ThreadContext context, IRubyObject expect, IRubyObject update) {
|
89
|
+
return getRuntime().newBoolean(atomicLong.compareAndSet(rubyFixnumToLong(expect), rubyFixnumToLong(update)));
|
90
|
+
}
|
91
|
+
|
92
|
+
@JRubyMethod
|
93
|
+
public IRubyObject update(ThreadContext context, Block block) {
|
94
|
+
for (;;) {
|
95
|
+
long _oldValue = atomicLong.get();
|
96
|
+
IRubyObject oldValue = getRuntime().newFixnum(_oldValue);
|
97
|
+
IRubyObject newValue = block.yield(context, oldValue);
|
98
|
+
if (atomicLong.compareAndSet(_oldValue, rubyFixnumToLong(newValue))) {
|
99
|
+
return newValue;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
private long rubyFixnumToLong(IRubyObject value) {
|
105
|
+
if (value instanceof RubyFixnum) {
|
106
|
+
RubyFixnum fixNum = (RubyFixnum) value;
|
107
|
+
return fixNum.getLongValue();
|
108
|
+
} else {
|
109
|
+
throw getRuntime().newArgumentError("value must be a Fixnum");
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
@@ -0,0 +1,159 @@
|
|
1
|
+
package com.concurrent_ruby.ext;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
import java.util.concurrent.Semaphore;
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.RubyClass;
|
7
|
+
import org.jruby.RubyFixnum;
|
8
|
+
import org.jruby.RubyModule;
|
9
|
+
import org.jruby.RubyNumeric;
|
10
|
+
import org.jruby.RubyObject;
|
11
|
+
import org.jruby.anno.JRubyClass;
|
12
|
+
import org.jruby.anno.JRubyMethod;
|
13
|
+
import org.jruby.runtime.ObjectAllocator;
|
14
|
+
import org.jruby.runtime.ThreadContext;
|
15
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
16
|
+
|
17
|
+
public class JavaSemaphoreLibrary {
|
18
|
+
|
19
|
+
public void load(Ruby runtime, boolean wrap) throws IOException {
|
20
|
+
RubyModule concurrentMod = runtime.defineModule("Concurrent");
|
21
|
+
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaSemaphore", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
|
22
|
+
|
23
|
+
atomicCls.defineAnnotatedMethods(JavaSemaphore.class);
|
24
|
+
}
|
25
|
+
|
26
|
+
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
|
27
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
28
|
+
return new JavaSemaphore(runtime, klazz);
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
@JRubyClass(name = "JavaSemaphore", parent = "Object")
|
33
|
+
public static class JavaSemaphore extends RubyObject {
|
34
|
+
|
35
|
+
private JRubySemaphore semaphore;
|
36
|
+
|
37
|
+
public JavaSemaphore(Ruby runtime, RubyClass metaClass) {
|
38
|
+
super(runtime, metaClass);
|
39
|
+
}
|
40
|
+
|
41
|
+
@JRubyMethod
|
42
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
|
43
|
+
this.semaphore = new JRubySemaphore(rubyFixnumInt(value, "count"));
|
44
|
+
return context.nil;
|
45
|
+
}
|
46
|
+
|
47
|
+
@JRubyMethod
|
48
|
+
public IRubyObject acquire(ThreadContext context, IRubyObject value) throws InterruptedException {
|
49
|
+
this.semaphore.acquire(rubyFixnumToPositiveInt(value, "permits"));
|
50
|
+
return context.nil;
|
51
|
+
}
|
52
|
+
|
53
|
+
@JRubyMethod(name = "available_permits")
|
54
|
+
public IRubyObject availablePermits(ThreadContext context) {
|
55
|
+
return getRuntime().newFixnum(this.semaphore.availablePermits());
|
56
|
+
}
|
57
|
+
|
58
|
+
@JRubyMethod(name = "drain_permits")
|
59
|
+
public IRubyObject drainPermits(ThreadContext context) {
|
60
|
+
return getRuntime().newFixnum(this.semaphore.drainPermits());
|
61
|
+
}
|
62
|
+
|
63
|
+
@JRubyMethod
|
64
|
+
public IRubyObject acquire(ThreadContext context) throws InterruptedException {
|
65
|
+
this.semaphore.acquire(1);
|
66
|
+
return context.nil;
|
67
|
+
}
|
68
|
+
|
69
|
+
@JRubyMethod(name = "try_acquire")
|
70
|
+
public IRubyObject tryAcquire(ThreadContext context) throws InterruptedException {
|
71
|
+
return getRuntime().newBoolean(semaphore.tryAcquire(1));
|
72
|
+
}
|
73
|
+
|
74
|
+
@JRubyMethod(name = "try_acquire")
|
75
|
+
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits) throws InterruptedException {
|
76
|
+
return getRuntime().newBoolean(semaphore.tryAcquire(rubyFixnumToPositiveInt(permits, "permits")));
|
77
|
+
}
|
78
|
+
|
79
|
+
@JRubyMethod(name = "try_acquire")
|
80
|
+
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout) throws InterruptedException {
|
81
|
+
return getRuntime().newBoolean(
|
82
|
+
semaphore.tryAcquire(
|
83
|
+
rubyFixnumToPositiveInt(permits, "permits"),
|
84
|
+
rubyNumericToLong(timeout, "timeout"),
|
85
|
+
java.util.concurrent.TimeUnit.SECONDS)
|
86
|
+
);
|
87
|
+
}
|
88
|
+
|
89
|
+
@JRubyMethod
|
90
|
+
public IRubyObject release(ThreadContext context) {
|
91
|
+
this.semaphore.release(1);
|
92
|
+
return getRuntime().newBoolean(true);
|
93
|
+
}
|
94
|
+
|
95
|
+
@JRubyMethod
|
96
|
+
public IRubyObject release(ThreadContext context, IRubyObject value) {
|
97
|
+
this.semaphore.release(rubyFixnumToPositiveInt(value, "permits"));
|
98
|
+
return getRuntime().newBoolean(true);
|
99
|
+
}
|
100
|
+
|
101
|
+
@JRubyMethod(name = "reduce_permits")
|
102
|
+
public IRubyObject reducePermits(ThreadContext context, IRubyObject reduction) throws InterruptedException {
|
103
|
+
this.semaphore.publicReducePermits(rubyFixnumToNonNegativeInt(reduction, "reduction"));
|
104
|
+
return context.nil;
|
105
|
+
}
|
106
|
+
|
107
|
+
private int rubyFixnumInt(IRubyObject value, String paramName) {
|
108
|
+
if (value instanceof RubyFixnum) {
|
109
|
+
RubyFixnum fixNum = (RubyFixnum) value;
|
110
|
+
return (int) fixNum.getLongValue();
|
111
|
+
} else {
|
112
|
+
throw getRuntime().newArgumentError(paramName + " must be integer");
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
private int rubyFixnumToNonNegativeInt(IRubyObject value, String paramName) {
|
117
|
+
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() >= 0) {
|
118
|
+
RubyFixnum fixNum = (RubyFixnum) value;
|
119
|
+
return (int) fixNum.getLongValue();
|
120
|
+
} else {
|
121
|
+
throw getRuntime().newArgumentError(paramName + " must be a non-negative integer");
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
private int rubyFixnumToPositiveInt(IRubyObject value, String paramName) {
|
126
|
+
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() > 0) {
|
127
|
+
RubyFixnum fixNum = (RubyFixnum) value;
|
128
|
+
return (int) fixNum.getLongValue();
|
129
|
+
} else {
|
130
|
+
throw getRuntime().newArgumentError(paramName + " must be an integer greater than zero");
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
private long rubyNumericToLong(IRubyObject value, String paramName) {
|
135
|
+
if (value instanceof RubyNumeric && ((RubyNumeric) value).getDoubleValue() > 0) {
|
136
|
+
RubyNumeric fixNum = (RubyNumeric) value;
|
137
|
+
return fixNum.getLongValue();
|
138
|
+
} else {
|
139
|
+
throw getRuntime().newArgumentError(paramName + " must be a float greater than zero");
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
class JRubySemaphore extends Semaphore {
|
144
|
+
|
145
|
+
public JRubySemaphore(int permits) {
|
146
|
+
super(permits);
|
147
|
+
}
|
148
|
+
|
149
|
+
public JRubySemaphore(int permits, boolean value) {
|
150
|
+
super(permits, value);
|
151
|
+
}
|
152
|
+
|
153
|
+
public void publicReducePermits(int i) {
|
154
|
+
reducePermits(i);
|
155
|
+
}
|
156
|
+
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
@@ -0,0 +1,307 @@
|
|
1
|
+
package com.concurrent_ruby.ext;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyBasicObject;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyModule;
|
7
|
+
import org.jruby.RubyObject;
|
8
|
+
import org.jruby.RubyThread;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.Block;
|
12
|
+
import org.jruby.runtime.ObjectAllocator;
|
13
|
+
import org.jruby.runtime.ThreadContext;
|
14
|
+
import org.jruby.runtime.Visibility;
|
15
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
16
|
+
import org.jruby.runtime.load.Library;
|
17
|
+
import sun.misc.Unsafe;
|
18
|
+
|
19
|
+
import java.io.IOException;
|
20
|
+
import java.lang.reflect.Field;
|
21
|
+
import java.lang.reflect.Method;
|
22
|
+
|
23
|
+
public class SynchronizationLibrary implements Library {
|
24
|
+
|
25
|
+
private static final Unsafe UNSAFE = loadUnsafe();
|
26
|
+
private static final boolean FULL_FENCE = supportsFences();
|
27
|
+
|
28
|
+
private static Unsafe loadUnsafe() {
|
29
|
+
try {
|
30
|
+
Class ncdfe = Class.forName("sun.misc.Unsafe");
|
31
|
+
Field f = ncdfe.getDeclaredField("theUnsafe");
|
32
|
+
f.setAccessible(true);
|
33
|
+
return (Unsafe) f.get((java.lang.Object) null);
|
34
|
+
} catch (Exception var2) {
|
35
|
+
return null;
|
36
|
+
} catch (NoClassDefFoundError var3) {
|
37
|
+
return null;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
private static boolean supportsFences() {
|
42
|
+
if (UNSAFE == null) {
|
43
|
+
return false;
|
44
|
+
} else {
|
45
|
+
try {
|
46
|
+
Method m = UNSAFE.getClass().getDeclaredMethod("fullFence", new Class[0]);
|
47
|
+
if (m != null) {
|
48
|
+
return true;
|
49
|
+
}
|
50
|
+
} catch (Exception var1) {
|
51
|
+
// nothing
|
52
|
+
}
|
53
|
+
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
private static final ObjectAllocator JRUBY_OBJECT_ALLOCATOR = new ObjectAllocator() {
|
59
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
60
|
+
return new JRubyObject(runtime, klazz);
|
61
|
+
}
|
62
|
+
};
|
63
|
+
|
64
|
+
private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() {
|
65
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
66
|
+
return new Object(runtime, klazz);
|
67
|
+
}
|
68
|
+
};
|
69
|
+
|
70
|
+
private static final ObjectAllocator ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() {
|
71
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
72
|
+
return new AbstractLockableObject(runtime, klazz);
|
73
|
+
}
|
74
|
+
};
|
75
|
+
|
76
|
+
private static final ObjectAllocator JRUBY_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() {
|
77
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
78
|
+
return new JRubyLockableObject(runtime, klazz);
|
79
|
+
}
|
80
|
+
};
|
81
|
+
|
82
|
+
public void load(Ruby runtime, boolean wrap) throws IOException {
|
83
|
+
RubyModule synchronizationModule = runtime.
|
84
|
+
defineModule("Concurrent").
|
85
|
+
defineModuleUnder("Synchronization");
|
86
|
+
|
87
|
+
RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile");
|
88
|
+
jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class);
|
89
|
+
|
90
|
+
defineClass(runtime, synchronizationModule, "AbstractObject", "JRubyObject",
|
91
|
+
JRubyObject.class, JRUBY_OBJECT_ALLOCATOR);
|
92
|
+
|
93
|
+
defineClass(runtime, synchronizationModule, "JRubyObject", "Object",
|
94
|
+
Object.class, OBJECT_ALLOCATOR);
|
95
|
+
|
96
|
+
defineClass(runtime, synchronizationModule, "Object", "AbstractLockableObject",
|
97
|
+
AbstractLockableObject.class, ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR);
|
98
|
+
|
99
|
+
defineClass(runtime, synchronizationModule, "AbstractLockableObject", "JRubyLockableObject",
|
100
|
+
JRubyLockableObject.class, JRUBY_LOCKABLE_OBJECT_ALLOCATOR);
|
101
|
+
|
102
|
+
defineClass(runtime, synchronizationModule, "Object", "JRuby",
|
103
|
+
JRuby.class, new ObjectAllocator() {
|
104
|
+
@Override
|
105
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
106
|
+
return new JRuby(runtime, klazz);
|
107
|
+
}
|
108
|
+
});
|
109
|
+
}
|
110
|
+
|
111
|
+
private RubyClass defineClass(
|
112
|
+
Ruby runtime,
|
113
|
+
RubyModule namespace,
|
114
|
+
String parentName,
|
115
|
+
String name,
|
116
|
+
Class javaImplementation,
|
117
|
+
ObjectAllocator allocator) {
|
118
|
+
final RubyClass parentClass = namespace.getClass(parentName);
|
119
|
+
|
120
|
+
if (parentClass == null) {
|
121
|
+
System.out.println("not found " + parentName);
|
122
|
+
throw runtime.newRuntimeError(namespace.toString() + "::" + parentName + " is missing");
|
123
|
+
}
|
124
|
+
|
125
|
+
final RubyClass newClass = namespace.defineClassUnder(name, parentClass, allocator);
|
126
|
+
newClass.defineAnnotatedMethods(javaImplementation);
|
127
|
+
return newClass;
|
128
|
+
}
|
129
|
+
|
130
|
+
// Facts:
|
131
|
+
// - all ivar reads are without any synchronisation of fences see
|
132
|
+
// https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java#L110-110
|
133
|
+
// - writes depend on UnsafeHolder.U, null -> SynchronizedVariableAccessor, !null -> StampedVariableAccessor
|
134
|
+
// SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or
|
135
|
+
// volatilePut
|
136
|
+
// TODO (pitr 16-Sep-2015): what do we do in Java 9 ?
|
137
|
+
|
138
|
+
// module JRubyAttrVolatile
|
139
|
+
public static class JRubyAttrVolatile {
|
140
|
+
|
141
|
+
// volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic
|
142
|
+
// on volatile fields. any volatile field could have been used but using the thread context is an
|
143
|
+
// attempt to avoid code elimination.
|
144
|
+
private static volatile int volatileField;
|
145
|
+
|
146
|
+
@JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC)
|
147
|
+
public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) {
|
148
|
+
// Prevent reordering of ivar writes with publication of this instance
|
149
|
+
if (!FULL_FENCE) {
|
150
|
+
// Assuming that following volatile read and write is not eliminated it simulates fullFence.
|
151
|
+
// If it's eliminated it'll cause problems only on non-x86 platforms.
|
152
|
+
// http://shipilev.net/blog/2014/jmm-pragmatics/#_happens_before_test_your_understanding
|
153
|
+
final int volatileRead = volatileField;
|
154
|
+
volatileField = context.getLine();
|
155
|
+
} else {
|
156
|
+
UNSAFE.fullFence();
|
157
|
+
}
|
158
|
+
return context.nil;
|
159
|
+
}
|
160
|
+
|
161
|
+
@JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC)
|
162
|
+
public static IRubyObject instanceVariableGetVolatile(
|
163
|
+
ThreadContext context,
|
164
|
+
IRubyObject self,
|
165
|
+
IRubyObject name) {
|
166
|
+
// Ensure we ses latest value with loadFence
|
167
|
+
if (!FULL_FENCE) {
|
168
|
+
// piggybacking on volatile read, simulating loadFence
|
169
|
+
final int volatileRead = volatileField;
|
170
|
+
return ((RubyBasicObject) self).instance_variable_get(context, name);
|
171
|
+
} else {
|
172
|
+
UNSAFE.loadFence();
|
173
|
+
return ((RubyBasicObject) self).instance_variable_get(context, name);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
@JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC)
|
178
|
+
public static IRubyObject InstanceVariableSetVolatile(
|
179
|
+
ThreadContext context,
|
180
|
+
IRubyObject self,
|
181
|
+
IRubyObject name,
|
182
|
+
IRubyObject value) {
|
183
|
+
// Ensure we make last update visible
|
184
|
+
if (!FULL_FENCE) {
|
185
|
+
// piggybacking on volatile write, simulating storeFence
|
186
|
+
final IRubyObject result = ((RubyBasicObject) self).instance_variable_set(name, value);
|
187
|
+
volatileField = context.getLine();
|
188
|
+
return result;
|
189
|
+
} else {
|
190
|
+
// JRuby uses StampedVariableAccessor which calls fullFence
|
191
|
+
// so no additional steps needed.
|
192
|
+
// See https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java#L151-L159
|
193
|
+
return ((RubyBasicObject) self).instance_variable_set(name, value);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
@JRubyClass(name = "JRubyObject", parent = "AbstractObject")
|
199
|
+
public static class JRubyObject extends RubyObject {
|
200
|
+
|
201
|
+
public JRubyObject(Ruby runtime, RubyClass metaClass) {
|
202
|
+
super(runtime, metaClass);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
@JRubyClass(name = "Object", parent = "JRubyObject")
|
207
|
+
public static class Object extends JRubyObject {
|
208
|
+
|
209
|
+
public Object(Ruby runtime, RubyClass metaClass) {
|
210
|
+
super(runtime, metaClass);
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
@JRubyClass(name = "AbstractLockableObject", parent = "Object")
|
215
|
+
public static class AbstractLockableObject extends Object {
|
216
|
+
|
217
|
+
public AbstractLockableObject(Ruby runtime, RubyClass metaClass) {
|
218
|
+
super(runtime, metaClass);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
@JRubyClass(name = "JRubyLockableObject", parent = "AbstractLockableObject")
|
223
|
+
public static class JRubyLockableObject extends JRubyObject {
|
224
|
+
|
225
|
+
public JRubyLockableObject(Ruby runtime, RubyClass metaClass) {
|
226
|
+
super(runtime, metaClass);
|
227
|
+
}
|
228
|
+
|
229
|
+
@JRubyMethod(name = "synchronize", visibility = Visibility.PROTECTED)
|
230
|
+
public IRubyObject rubySynchronize(ThreadContext context, Block block) {
|
231
|
+
synchronized (this) {
|
232
|
+
return block.yield(context, null);
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
@JRubyMethod(name = "ns_wait", optional = 1, visibility = Visibility.PROTECTED)
|
237
|
+
public IRubyObject nsWait(ThreadContext context, IRubyObject[] args) {
|
238
|
+
Ruby runtime = context.runtime;
|
239
|
+
if (args.length > 1) {
|
240
|
+
throw runtime.newArgumentError(args.length, 1);
|
241
|
+
}
|
242
|
+
Double timeout = null;
|
243
|
+
if (args.length > 0 && !args[0].isNil()) {
|
244
|
+
timeout = args[0].convertToFloat().getDoubleValue();
|
245
|
+
if (timeout < 0) {
|
246
|
+
throw runtime.newArgumentError("time interval must be positive");
|
247
|
+
}
|
248
|
+
}
|
249
|
+
if (Thread.interrupted()) {
|
250
|
+
throw runtime.newConcurrencyError("thread interrupted");
|
251
|
+
}
|
252
|
+
boolean success = false;
|
253
|
+
try {
|
254
|
+
success = context.getThread().wait_timeout(this, timeout);
|
255
|
+
} catch (InterruptedException ie) {
|
256
|
+
throw runtime.newConcurrencyError(ie.getLocalizedMessage());
|
257
|
+
} finally {
|
258
|
+
// An interrupt or timeout may have caused us to miss
|
259
|
+
// a notify that we consumed, so do another notify in
|
260
|
+
// case someone else is available to pick it up.
|
261
|
+
if (!success) {
|
262
|
+
this.notify();
|
263
|
+
}
|
264
|
+
}
|
265
|
+
return this;
|
266
|
+
}
|
267
|
+
|
268
|
+
@JRubyMethod(name = "ns_signal", visibility = Visibility.PROTECTED)
|
269
|
+
public IRubyObject nsSignal(ThreadContext context) {
|
270
|
+
notify();
|
271
|
+
return this;
|
272
|
+
}
|
273
|
+
|
274
|
+
@JRubyMethod(name = "ns_broadcast", visibility = Visibility.PROTECTED)
|
275
|
+
public IRubyObject nsBroadcast(ThreadContext context) {
|
276
|
+
notifyAll();
|
277
|
+
return this;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
@JRubyClass(name = "JRuby")
|
282
|
+
public static class JRuby extends RubyObject {
|
283
|
+
public JRuby(Ruby runtime, RubyClass metaClass) {
|
284
|
+
super(runtime, metaClass);
|
285
|
+
}
|
286
|
+
|
287
|
+
@JRubyMethod(name = "sleep_interruptibly", visibility = Visibility.PUBLIC, module = true)
|
288
|
+
public static IRubyObject sleepInterruptibly(final ThreadContext context, IRubyObject receiver, final Block block) {
|
289
|
+
try {
|
290
|
+
context.getThread().executeBlockingTask(new RubyThread.BlockingTask() {
|
291
|
+
@Override
|
292
|
+
public void run() throws InterruptedException {
|
293
|
+
block.call(context);
|
294
|
+
}
|
295
|
+
|
296
|
+
@Override
|
297
|
+
public void wakeup() {
|
298
|
+
context.getThread().getNativeThread().interrupt();
|
299
|
+
}
|
300
|
+
});
|
301
|
+
} catch (InterruptedException e) {
|
302
|
+
throw context.runtime.newThreadError("interrupted in Concurrent::Synchronization::JRuby.sleep_interruptibly");
|
303
|
+
}
|
304
|
+
return context.nil;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
}
|