package com.concurrent_ruby.ext; import java.lang.reflect.Field; import java.io.IOException; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.load.Library; /** * This library adds an atomic reference type to JRuby for use in the atomic * library. We do a native version to avoid the implicit value coercion that * normally happens through JI. * * @author headius */ public class AtomicReferenceLibrary implements Library { public void load(Ruby runtime, boolean wrap) throws IOException { RubyModule concurrentMod = runtime.defineModule("Concurrent"); RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicReference", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); try { sun.misc.Unsafe.class.getMethod("getAndSetObject", Object.class); atomicCls.setAllocator(JRUBYREFERENCE8_ALLOCATOR); } catch (Exception e) { // leave it as Java 6/7 version } atomicCls.defineAnnotatedMethods(JRubyReference.class); } private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klazz) { return new JRubyReference(runtime, klazz); } }; private static final ObjectAllocator JRUBYREFERENCE8_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klazz) { return new JRubyReference8(runtime, klazz); } }; @JRubyClass(name="JRubyReference", parent="Object") public static class JRubyReference extends RubyObject { volatile IRubyObject reference; static final sun.misc.Unsafe UNSAFE; static final long referenceOffset; static { try { UNSAFE = UnsafeHolder.U; Class k = JRubyReference.class; referenceOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("reference")); } catch (Exception e) { throw new RuntimeException(e); } } public JRubyReference(Ruby runtime, RubyClass klass) { super(runtime, klass); } @JRubyMethod public IRubyObject initialize(ThreadContext context) { UNSAFE.putObject(this, referenceOffset, context.nil); return context.nil; } @JRubyMethod public IRubyObject initialize(ThreadContext context, IRubyObject value) { UNSAFE.putObject(this, referenceOffset, value); return context.nil; } @JRubyMethod(name = {"get", "value"}) public IRubyObject get() { return reference; } @JRubyMethod(name = {"set", "value="}) public IRubyObject set(IRubyObject newValue) { UNSAFE.putObjectVolatile(this, referenceOffset, newValue); return newValue; } @JRubyMethod(name = {"compare_and_set", "compare_and_swap"}) public IRubyObject compare_and_set(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) { Ruby runtime = context.runtime; if (expectedValue instanceof RubyNumeric) { // numerics are not always idempotent in Ruby, so we need to do slower logic return compareAndSetNumeric(context, expectedValue, newValue); } return runtime.newBoolean(UNSAFE.compareAndSwapObject(this, referenceOffset, expectedValue, newValue)); } @JRubyMethod(name = {"get_and_set", "swap"}) public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) { // less-efficient version for Java 6 and 7 while (true) { IRubyObject oldValue = get(); if (UNSAFE.compareAndSwapObject(this, referenceOffset, oldValue, newValue)) { return oldValue; } } } private IRubyObject compareAndSetNumeric(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) { Ruby runtime = context.runtime; // loop until: // * reference CAS would succeed for same-valued objects // * current and expected have different values as determined by #equals while (true) { IRubyObject current = reference; if (!(current instanceof RubyNumeric)) { // old value is not numeric, CAS fails return runtime.getFalse(); } RubyNumeric currentNumber = (RubyNumeric)current; if (!currentNumber.equals(expectedValue)) { // current number does not equal expected, fail CAS return runtime.getFalse(); } // check that current has not changed, or else allow loop to repeat boolean success = UNSAFE.compareAndSwapObject(this, referenceOffset, current, newValue); if (success) { // value is same and did not change in interim...success return runtime.getTrue(); } } } } private static final class UnsafeHolder { private UnsafeHolder(){} public static final sun.misc.Unsafe U = loadUnsafe(); private static sun.misc.Unsafe loadUnsafe() { try { Class unsafeClass = Class.forName("sun.misc.Unsafe"); Field f = unsafeClass.getDeclaredField("theUnsafe"); f.setAccessible(true); return (sun.misc.Unsafe) f.get(null); } catch (Exception e) { return null; } } } public static class JRubyReference8 extends JRubyReference { public JRubyReference8(Ruby runtime, RubyClass klass) { super(runtime, klass); } @Override public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) { // efficient version for Java 8 return (IRubyObject)UNSAFE.getAndSetObject(this, referenceOffset, newValue); } } }