Skip to content

Commit

Permalink
Refactor Java invoker cache logic to allow encapsulation, sync.
Browse files Browse the repository at this point in the history
The move to IntHashMap in RubyToJavaInvoker improved throughput
for multiple-arity calls, but since IntHashMap is not synchronized
this could cause corrupted data under concurrent load as seen in
CallableSelector to encapsulate the cache and synchronize access
to it. I believe this is a minimum effort to make the cache access
safe and fix #3394.

I have not measured the impact of this synchronization in a high
concurrency setting. It may indeed turn out to be more overhead
and a worse bottleneck than using ConcurrentHashMap with Integer
objects. Unfortunately there's no middle ground here unless we
build or adopt some concurrency-friendly int-based map.

Another heavy option would be to maintain two references to
IntHashMap in RubyToJavaInvoker, copying from a synchronized
writable copy to a read-only copy after every write. This would
double the size of the cache structures in memory but would provide
a fast, unsynchronized cache for read-mostly signature lookups.
This pattern is also easier to support with the refactoring in
this commit.
headius committed Oct 14, 2015
1 parent 26c4d68 commit b52e483
Showing 3 changed files with 164 additions and 133 deletions.
160 changes: 88 additions & 72 deletions core/src/main/java/org/jruby/java/dispatch/CallableSelector.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
import org.jruby.RubyInteger;
import org.jruby.RubyProc;
import org.jruby.RubyString;
import org.jruby.java.invokers.RubyToJavaInvoker;
import org.jruby.javasupport.JavaCallable;
import org.jruby.javasupport.JavaClass;
import org.jruby.javasupport.JavaUtil;
@@ -35,117 +36,62 @@ private CallableSelector() { /* no-instances */ }

//private static final boolean DEBUG = true;

@SuppressWarnings("unchecked")
public static ParameterTypes matchingCallableArityN(Ruby runtime, Map cache, ParameterTypes[] methods, IRubyObject[] args) {
public static <T extends ParameterTypes> T matchingCallableArityN(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject[] args) {
final int signatureCode = argsHashCode(args);
ParameterTypes method = (ParameterTypes) cache.get(signatureCode);
T method = cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, args);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

// NOTE: The five match methods are arity-split to avoid the cost of boxing arguments
// when there's already a cached match. Do not condense them into a single
// method.
@SuppressWarnings("unchecked")
public static JavaCallable matchingCallableArityN(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject[] args) {
public static <T extends JavaCallable> T matchingCallableArityN(Ruby runtime, RubyToJavaInvoker<T> invoker, T[] methods, IRubyObject[] args) {
final int signatureCode = argsHashCode(args);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
T method = invoker.getSignature(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, args);
if (method != null) cache.put(signatureCode, method);
if (method != null) invoker.putSignature(signatureCode, method);
}
return method;
}

public static JavaCallable matchingCallableArityOne(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0) {
public static <T extends JavaCallable> T matchingCallableArityOne(Ruby runtime, RubyToJavaInvoker<T> invoker, T[] methods, IRubyObject arg0) {
final int signatureCode = argsHashCode(arg0);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
T method = invoker.getSignature(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0);
if (method != null) cache.put(signatureCode, method);
if (method != null) invoker.putSignature(signatureCode, method);
}
return method;
}

public static JavaCallable matchingCallableArityTwo(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1) {
public static <T extends JavaCallable> T matchingCallableArityTwo(Ruby runtime, RubyToJavaInvoker<T> invoker, T[] methods, IRubyObject arg0, IRubyObject arg1) {
final int signatureCode = argsHashCode(arg0, arg1);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
T method = invoker.getSignature(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1);
if (method != null) cache.put(signatureCode, method);
if (method != null) invoker.putSignature(signatureCode, method);
}
return method;
}

public static JavaCallable matchingCallableArityThree(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
public static <T extends JavaCallable> T matchingCallableArityThree(Ruby runtime, RubyToJavaInvoker<T> invoker, T[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
final int signatureCode = argsHashCode(arg0, arg1, arg2);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

public static JavaCallable matchingCallableArityFour(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
final int signatureCode = argsHashCode(arg0, arg1, arg2, arg3);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2, arg3);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

public static <T extends ParameterTypes> T matchingCallableArityN(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject[] args) {
final int signatureCode = argsHashCode(args);
T method = cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, args);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

public static <T extends ParameterTypes> T matchingCallableArityOne(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject arg0) {
final int signatureCode = argsHashCode(arg0);
T method = cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

public static <T extends ParameterTypes> T matchingCallableArityTwo(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject arg0, IRubyObject arg1) {
final int signatureCode = argsHashCode(arg0, arg1);
T method = cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

public static <T extends ParameterTypes> T matchingCallableArityThree(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
final int signatureCode = argsHashCode(arg0, arg1, arg2);
T method = cache.get(signatureCode);
T method = invoker.getSignature(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2);
if (method != null) cache.put(signatureCode, method);
if (method != null) invoker.putSignature(signatureCode, method);
}
return method;
}

public static <T extends ParameterTypes> T matchingCallableArityFour(Ruby runtime, IntHashMap<T> cache, T[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
public static <T extends JavaCallable> T matchingCallableArityFour(Ruby runtime, RubyToJavaInvoker<T> invoker, T[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
final int signatureCode = argsHashCode(arg0, arg1, arg2, arg3);
T method = cache.get(signatureCode);
T method = invoker.getSignature(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2, arg3);
if (method != null) cache.put(signatureCode, method);
if (method != null) invoker.putSignature(signatureCode, method);
}
return method;
}
@@ -762,4 +708,74 @@ public static <T extends ParameterTypes> IntHashMap<T> newCallableCache() {
return new IntHashMap<T>(8);
}

@SuppressWarnings("unchecked")
@Deprecated
public static ParameterTypes matchingCallableArityN(Ruby runtime, Map cache, ParameterTypes[] methods, IRubyObject[] args) {
final int signatureCode = argsHashCode(args);
ParameterTypes method = (ParameterTypes) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, args);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

// NOTE: The five match methods are arity-split to avoid the cost of boxing arguments
// when there's already a cached match. Do not condense them into a single
// method.
@SuppressWarnings("unchecked")
@Deprecated
public static JavaCallable matchingCallableArityN(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject[] args) {
final int signatureCode = argsHashCode(args);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, args);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

@Deprecated
public static JavaCallable matchingCallableArityOne(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0) {
final int signatureCode = argsHashCode(arg0);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

@Deprecated
public static JavaCallable matchingCallableArityTwo(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1) {
final int signatureCode = argsHashCode(arg0, arg1);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

@Deprecated
public static JavaCallable matchingCallableArityThree(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
final int signatureCode = argsHashCode(arg0, arg1, arg2);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2);
if (method != null) cache.put(signatureCode, method);
}
return method;
}

@Deprecated
public static JavaCallable matchingCallableArityFour(Ruby runtime, Map cache, JavaCallable[] methods, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
final int signatureCode = argsHashCode(arg0, arg1, arg2, arg3);
JavaCallable method = (JavaCallable) cache.get(signatureCode);
if (method == null) {
method = findMatchingCallableForArgs(runtime, methods, arg0, arg1, arg2, arg3);
if (method != null) cache.put(signatureCode, method);
}
return method;
}
}
126 changes: 69 additions & 57 deletions core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java
Original file line number Diff line number Diff line change
@@ -25,26 +25,26 @@
import static org.jruby.java.dispatch.CallableSelector.newCallableCache;
import static org.jruby.util.CodegenUtils.prettyParams;

public abstract class RubyToJavaInvoker extends JavaMethod {
public abstract class RubyToJavaInvoker<T extends JavaCallable> extends JavaMethod {

static final IntHashMap<JavaCallable> NULL_CACHE = IntHashMap.nullMap();
static final IntHashMap NULL_CACHE = IntHashMap.nullMap();

protected final JavaCallable javaCallable; /* null if multiple callable members */
protected final JavaCallable[][] javaCallables; /* != null if javaCallable == null */
protected final JavaCallable[] javaVarargsCallables; /* != null if any var args callables */
protected final T javaCallable; /* null if multiple callable members */
protected final T[][] javaCallables; /* != null if javaCallable == null */
protected final T[] javaVarargsCallables; /* != null if any var args callables */

// in case multiple callables (overloaded Java method - same name different args)
// for the invoker exists CallableSelector caches resolution based on args here
final IntHashMap<JavaCallable> cache;
final IntHashMap<T> cache;

private final Ruby runtime;

RubyToJavaInvoker(RubyModule host, Member member) {
super(host, Visibility.PUBLIC);
this.runtime = host.getRuntime();

final JavaCallable callable;
JavaCallable[] varargsCallables = null;
final T callable;
T[] varargsCallables = null;
int minVarArgsArity = -1;

callable = createCallable(runtime, member);
@@ -69,9 +69,9 @@ public abstract class RubyToJavaInvoker extends JavaMethod {
this.runtime = host.getRuntime();

// initialize all the callables for this method
final JavaCallable callable;
final JavaCallable[][] callables;
JavaCallable[] varargsCallables = null;
final T callable;
final T[][] callables;
T[] varargsCallables = null;
int minVarArgsArity = -1; int maxArity, minArity;

final int length = members.length;
@@ -89,23 +89,23 @@ public abstract class RubyToJavaInvoker extends JavaMethod {
else {
callable = null; maxArity = -1; minArity = Integer.MAX_VALUE;

IntHashMap<ArrayList<JavaCallable>> arityMap = new IntHashMap<ArrayList<JavaCallable>>(length, 1);
IntHashMap<ArrayList<T>> arityMap = new IntHashMap<ArrayList<T>>(length, 1);

ArrayList<JavaCallable> varArgs = null;
ArrayList<T> varArgs = null;
for ( int i = 0; i < length; i++ ) {
final Member method = members[i];
final int currentArity = getMemberArity(method);
maxArity = Math.max(currentArity, maxArity);
minArity = Math.min(currentArity, minArity);

final JavaCallable javaMethod = createCallable(runtime, method);
final T javaMethod = createCallable(runtime, method);

ArrayList<JavaCallable> methodsForArity = arityMap.get(currentArity);
ArrayList<T> methodsForArity = arityMap.get(currentArity);
if (methodsForArity == null) {
// most calls have 2-3 callables length (a.k.a. overrides)
// using capacity of length is a win-win here - will (likely)
// use small internal [len] + no resizing even in worst case
methodsForArity = new ArrayList<JavaCallable>(length);
methodsForArity = new ArrayList<T>(length);
arityMap.put(currentArity, methodsForArity);
}
methodsForArity.add(javaMethod);
@@ -114,12 +114,12 @@ public abstract class RubyToJavaInvoker extends JavaMethod {
final int usableArity = currentArity - 1;
// (String, Object...) has usable arity == 1 ... (String)
if ((methodsForArity = arityMap.get(usableArity)) == null) {
methodsForArity = new ArrayList<JavaCallable>(length);
methodsForArity = new ArrayList<T>(length);
arityMap.put(usableArity, methodsForArity);
}
methodsForArity.add(javaMethod);

if (varArgs == null) varArgs = new ArrayList<JavaCallable>(length);
if (varArgs == null) varArgs = new ArrayList<T>(length);
varArgs.add(javaMethod);

if ( minVarArgsArity == -1 ) minVarArgsArity = Integer.MAX_VALUE;
@@ -128,10 +128,10 @@ public abstract class RubyToJavaInvoker extends JavaMethod {
}

callables = createCallableArrayArray(maxArity + 1);
for (IntHashMap.Entry<ArrayList<JavaCallable>> entry : arityMap.entrySet()) {
ArrayList<JavaCallable> methodsForArity = entry.getValue();
for (IntHashMap.Entry<ArrayList<T>> entry : arityMap.entrySet()) {
ArrayList<T> methodsForArity = entry.getValue();

JavaCallable[] methodsArray = methodsForArity.toArray(createCallableArray(methodsForArity.size()));
T[] methodsArray = methodsForArity.toArray(createCallableArray(methodsForArity.size()));
callables[ entry.getKey() /* int */ ] = methodsArray;
}

@@ -156,7 +156,7 @@ private void setArity(final int minArity, final int maxArity, final int minVarA
setArity( Arity.fixed(minArity) );
}
else { // multiple overloads
setArity( Arity.required(minArity) ); // but <= maxArity
setArity(Arity.required(minArity)); // but <= maxArity
}
}
else {
@@ -168,7 +168,7 @@ final void setupNativeCall() { // if it's not overloaded, set up a NativeCall
if (javaCallable != null) {
// no constructor support yet
if (javaCallable instanceof org.jruby.javasupport.JavaMethod) {
setNativeCallIfPublic( ((org.jruby.javasupport.JavaMethod) javaCallable).getValue() );
setNativeCallIfPublic(((org.jruby.javasupport.JavaMethod) javaCallable).getValue());
}
} else { // use the lowest-arity non-overload
for ( int i = 0; i< javaCallables.length; i++ ) {
@@ -191,13 +191,25 @@ private boolean setNativeCallIfPublic(final Method method) {
return false;
}

protected abstract JavaCallable createCallable(Ruby runtime, Member member);
public T getSignature(int signatureCode) {
synchronized (cache) {
return cache.get(signatureCode);
}
}

public void putSignature(int signatureCode, T callable) {
synchronized (cache) {
cache.put(signatureCode, callable);
}
}

protected abstract T createCallable(Ruby runtime, Member member);

protected abstract JavaCallable[] createCallableArray(JavaCallable callable);
protected abstract T[] createCallableArray(T callable);

protected abstract JavaCallable[] createCallableArray(int size);
protected abstract T[] createCallableArray(int size);

protected abstract JavaCallable[][] createCallableArrayArray(int size);
protected abstract T[][] createCallableArrayArray(int size);

protected abstract Class[] getMemberParameterTypes(Member member);

@@ -271,17 +283,17 @@ static <T extends AccessibleObject> T[] setAccessible(T[] accessibles) {
return accessibles;
}

protected JavaCallable findCallable(IRubyObject self, String name, IRubyObject[] args, final int arity) {
JavaCallable callable = this.javaCallable;
protected T findCallable(IRubyObject self, String name, IRubyObject[] args, final int arity) {
T callable = this.javaCallable;
if ( callable == null ) {
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( arity >= javaCallables.length || (callablesForArity = javaCallables[arity]) == null ) {
if ( ( callable = matchVarArgsCallableArityN(self, args) ) == null ) {
throw runtime.newArgumentError(args.length, javaCallables.length - 1);
}
return callable;
}
callable = CallableSelector.matchingCallableArityN(runtime, cache, callablesForArity, args);
callable = CallableSelector.matchingCallableArityN(runtime, this, callablesForArity, args);
if ( callable == null ) {
if ( ( callable = matchVarArgsCallableArityN(self, args) ) == null ) {
throw newErrorDueArgumentTypeMismatch(self, callablesForArity, args);
@@ -294,10 +306,10 @@ protected JavaCallable findCallable(IRubyObject self, String name, IRubyObject[]
return callable;
}

private JavaCallable matchVarArgsCallableArityN(IRubyObject self, IRubyObject[] args) {
final JavaCallable[] varArgsCallables = this.javaVarargsCallables;
private T matchVarArgsCallableArityN(IRubyObject self, IRubyObject[] args) {
final T[] varArgsCallables = this.javaVarargsCallables;
if ( varArgsCallables != null ) {
JavaCallable callable = CallableSelector.matchingCallableArityN(runtime, cache, varArgsCallables, args);
T callable = CallableSelector.matchingCallableArityN(runtime, this, varArgsCallables, args);
if ( callable == null ) {
throw newErrorDueArgumentTypeMismatch(self, varArgsCallables, args);
}
@@ -306,11 +318,11 @@ private JavaCallable matchVarArgsCallableArityN(IRubyObject self, IRubyObject[]
return null;
}

protected final JavaCallable findCallableArityZero(IRubyObject self, String name) {
JavaCallable callable = this.javaCallable;
protected final T findCallableArityZero(IRubyObject self, String name) {
T callable = this.javaCallable;
if ( callable == null ) {
// TODO: varargs?
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( javaCallables.length == 0 || (callablesForArity = javaCallables[0]) == null ) {
throw newErrorDueNoMatchingCallable(self, name);
}
@@ -322,15 +334,15 @@ protected final JavaCallable findCallableArityZero(IRubyObject self, String name
return callable;
}

protected final JavaCallable findCallableArityOne(IRubyObject self, String name, IRubyObject arg0) {
JavaCallable callable = this.javaCallable;
protected final T findCallableArityOne(IRubyObject self, String name, IRubyObject arg0) {
T callable = this.javaCallable;
if ( callable == null ) {
// TODO: varargs?
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( javaCallables.length <= 1 || (callablesForArity = javaCallables[1]) == null ) {
throw runtime.newArgumentError(1, javaCallables.length - 1);
}
callable = CallableSelector.matchingCallableArityOne(runtime, cache, callablesForArity, arg0);
callable = CallableSelector.matchingCallableArityOne(runtime, this, callablesForArity, arg0);
if ( callable == null ) {
throw newErrorDueArgumentTypeMismatch(self, callablesForArity, arg0);
}
@@ -341,15 +353,15 @@ protected final JavaCallable findCallableArityOne(IRubyObject self, String name,
return callable;
}

protected final JavaCallable findCallableArityTwo(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1) {
JavaCallable callable = this.javaCallable;
protected final T findCallableArityTwo(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1) {
T callable = this.javaCallable;
if ( callable == null ) {
// TODO: varargs?
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( javaCallables.length <= 2 || (callablesForArity = javaCallables[2]) == null ) {
throw runtime.newArgumentError(2, javaCallables.length - 1);
}
callable = CallableSelector.matchingCallableArityTwo(runtime, cache, callablesForArity, arg0, arg1);
callable = CallableSelector.matchingCallableArityTwo(runtime, this, callablesForArity, arg0, arg1);
if ( callable == null ) {
throw newErrorDueArgumentTypeMismatch(self, callablesForArity, arg0, arg1);
}
@@ -360,15 +372,15 @@ protected final JavaCallable findCallableArityTwo(IRubyObject self, String name,
return callable;
}

protected final JavaCallable findCallableArityThree(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
JavaCallable callable = this.javaCallable;
protected final T findCallableArityThree(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
T callable = this.javaCallable;
if ( callable == null ) {
// TODO: varargs?
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( javaCallables.length <= 3 || (callablesForArity = javaCallables[3]) == null ) {
throw runtime.newArgumentError(3, javaCallables.length - 1);
}
callable = CallableSelector.matchingCallableArityThree(runtime, cache, callablesForArity, arg0, arg1, arg2);
callable = CallableSelector.matchingCallableArityThree(runtime, this, callablesForArity, arg0, arg1, arg2);
if ( callable == null ) {
throw newErrorDueArgumentTypeMismatch(self, callablesForArity, arg0, arg1, arg2);
}
@@ -379,15 +391,15 @@ protected final JavaCallable findCallableArityThree(IRubyObject self, String nam
return callable;
}

protected final JavaCallable findCallableArityFour(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
JavaCallable callable = this.javaCallable;
protected final T findCallableArityFour(IRubyObject self, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
T callable = this.javaCallable;
if ( callable == null ) {
// TODO: varargs?
final JavaCallable[] callablesForArity;
final T[] callablesForArity;
if ( javaCallables.length <= 4 || (callablesForArity = javaCallables[4]) == null ) {
throw runtime.newArgumentError(4, javaCallables.length - 1);
}
callable = CallableSelector.matchingCallableArityFour(runtime, cache, callablesForArity, arg0, arg1, arg2, arg3);
callable = CallableSelector.matchingCallableArityFour(runtime, this, callablesForArity, arg0, arg1, arg2, arg3);
if ( callable == null ) {
throw newErrorDueArgumentTypeMismatch(self, callablesForArity, arg0, arg1, arg2, arg3);
}
@@ -398,15 +410,15 @@ protected final JavaCallable findCallableArityFour(IRubyObject self, String name
return callable;
}

private void checkCallableArity(final JavaCallable callable, final int expected) {
private void checkCallableArity(final T callable, final int expected) {
final int arity = callable.getArity();
if ( arity != expected ) throw runtime.newArgumentError(expected, arity);
}

private JavaCallable someCallable() {
private T someCallable() {
if ( javaCallable == null ) {
for ( int i = 0; i < javaCallables.length; i++ ) {
JavaCallable[] callables = javaCallables[i];
T[] callables = javaCallables[i];
if ( callables != null && callables.length > 0 ) {
for ( int j = 0; j < callables.length; j++ ) {
if ( callables[j] != null ) return callables[j];
@@ -423,7 +435,7 @@ private boolean isConstructor() {
}

RaiseException newErrorDueArgumentTypeMismatch(final IRubyObject receiver,
final JavaCallable[] methods, IRubyObject... args) {
final T[] methods, IRubyObject... args) {

final Class[] argTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
11 changes: 7 additions & 4 deletions core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
@@ -625,10 +625,13 @@ public IRubyObject call(final ThreadContext context, IRubyObject self, RubyModul
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}

final JavaProxyConstructor matching = CallableSelector.matchingCallableArityN(
context.runtime, cache,
forArity.toArray(new JavaProxyConstructor[forArity.size()]), args
);
final JavaProxyConstructor matching;
synchronized (cache) {
matching = CallableSelector.matchingCallableArityN(
context.runtime, cache,
forArity.toArray(new JavaProxyConstructor[forArity.size()]), args
);
}

if ( matching == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");

0 comments on commit b52e483

Please sign in to comment.