ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java
package org.jruby.ext.thread_safe;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMap;
import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMapV8;
import org.jruby.ext.thread_safe.jsr166e.nounsafe.*;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import java.io.IOException;
import java.util.Map;
import static org.jruby.runtime.Visibility.PRIVATE;
/**
* Native Java implementation to avoid the JI overhead.
*
* @author thedarkone
*/
public class JRubyCacheBackendLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyClass jrubyRefClass = runtime.defineClassUnder("JRubyCacheBackend", runtime.getObject(), BACKEND_ALLOCATOR, runtime.getModule("ThreadSafe"));
jrubyRefClass.setAllocator(BACKEND_ALLOCATOR);
jrubyRefClass.defineAnnotatedMethods(JRubyCacheBackend.class);
}
private static final ObjectAllocator BACKEND_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyCacheBackend(runtime, klazz);
}
};
@JRubyClass(name="JRubyCacheBackend", parent="Object")
public static class JRubyCacheBackend extends RubyObject {
// Defaults used by the CHM
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public static final boolean CAN_USE_UNSAFE_CHM = canUseUnsafeCHM();
private ConcurrentHashMap<IRubyObject, IRubyObject> map;
private static ConcurrentHashMap<IRubyObject, IRubyObject> newCHM(int initialCapacity, float loadFactor) {
if (CAN_USE_UNSAFE_CHM) {
return new ConcurrentHashMapV8<IRubyObject, IRubyObject>(initialCapacity, loadFactor);
} else {
return new org.jruby.ext.thread_safe.jsr166e.nounsafe.ConcurrentHashMapV8<IRubyObject, IRubyObject>(initialCapacity, loadFactor);
}
}
private static ConcurrentHashMap<IRubyObject, IRubyObject> newCHM() {
return newCHM(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
private static boolean canUseUnsafeCHM() {
try {
new org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMapV8(); // force class load and initialization
return true;
} catch (Throwable t) { // ensuring we really do catch everything
// Doug's Unsafe setup errors always have this "Could not ini.." message
if (isCausedBySecurityException(t)) {
return false;
}
throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t));
}
}
private static boolean isCausedBySecurityException(Throwable t) {
while (t != null) {
if ((t.getMessage() != null && t.getMessage().contains("Could not initialize intrinsics")) || t instanceof SecurityException) {
return true;
}
t = t.getCause();
}
return false;
}
public JRubyCacheBackend(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
map = newCHM();
return context.getRuntime().getNil();
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject options) {
map = toCHM(context, options);
return context.getRuntime().getNil();
}
private ConcurrentHashMap<IRubyObject, IRubyObject> toCHM(ThreadContext context, IRubyObject options) {
Ruby runtime = context.getRuntime();
if (!options.isNil() && options.respondsTo("[]")) {
IRubyObject rInitialCapacity = options.callMethod(context, "[]", runtime.newSymbol("initial_capacity"));
IRubyObject rLoadFactor = options.callMethod(context, "[]", runtime.newSymbol("load_factor"));
int initialCapacity = !rInitialCapacity.isNil() ? RubyNumeric.num2int(rInitialCapacity.convertToInteger()) : DEFAULT_INITIAL_CAPACITY;
float loadFactor = !rLoadFactor.isNil() ? (float)RubyNumeric.num2dbl(rLoadFactor.convertToFloat()) : DEFAULT_LOAD_FACTOR;
return newCHM(initialCapacity, loadFactor);
} else {
return newCHM();
}
}
@JRubyMethod(name = "[]", required = 1)
public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
IRubyObject value;
return ((value = map.get(key)) == null) ? context.getRuntime().getNil() : value;
}
@JRubyMethod(name = {"[]="}, required = 2)
public IRubyObject op_aset(IRubyObject key, IRubyObject value) {
map.put(key, value);
return value;
}
@JRubyMethod
public IRubyObject put_if_absent(IRubyObject key, IRubyObject value) {
IRubyObject result = map.putIfAbsent(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject compute_if_absent(final ThreadContext context, final IRubyObject key, final Block block) {
return map.computeIfAbsent(key, new ConcurrentHashMap.Fun<IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key) {
return block.yieldSpecific(context);
}
});
}
@JRubyMethod
public IRubyObject compute_if_present(final ThreadContext context, final IRubyObject key, final Block block) {
IRubyObject result = map.computeIfPresent(key, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key, IRubyObject oldValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject compute(final ThreadContext context, final IRubyObject key, final Block block) {
IRubyObject result = map.compute(key, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key, IRubyObject oldValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject merge_pair(final ThreadContext context, final IRubyObject key, final IRubyObject value, final Block block) {
IRubyObject result = map.merge(key, value, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject oldValue, IRubyObject newValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObject newValue) {
return getRuntime().newBoolean(map.replace(key, oldValue, newValue));
}
@JRubyMethod(name = "key?", required = 1)
public RubyBoolean has_key_p(IRubyObject key) {
return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse();
}
@JRubyMethod
public IRubyObject key(IRubyObject value) {
final IRubyObject key = map.findKey(value);
return key == null ? getRuntime().getNil() : key;
}
@JRubyMethod
public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) {
IRubyObject result = map.replace(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject get_and_set(IRubyObject key, IRubyObject value) {
IRubyObject result = map.put(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject delete(IRubyObject key) {
IRubyObject result = map.remove(key);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public RubyBoolean delete_pair(IRubyObject key, IRubyObject value) {
return getRuntime().newBoolean(map.remove(key, value));
}
@JRubyMethod
public IRubyObject clear() {
map.clear();
return this;
}
@JRubyMethod
public IRubyObject each_pair(ThreadContext context, Block block) {
for (Map.Entry<IRubyObject,IRubyObject> entry : map.entrySet()) {
block.yieldSpecific(context, entry.getKey(), entry.getValue());
}
return this;
}
@JRubyMethod
public RubyFixnum size(ThreadContext context) {
return context.getRuntime().newFixnum(map.size());
}
@JRubyMethod
public IRubyObject get_or_default(IRubyObject key, IRubyObject defaultValue) {
return map.getValueOrDefault(key, defaultValue);
}
@JRubyMethod(visibility = PRIVATE)
public JRubyCacheBackend initialize_copy(ThreadContext context, IRubyObject other) {
map = newCHM();
return this;
}
}
}