Skip to content

Commit 2bdfb5a

Browse files
committed
Eliminate proxy leak by adding pre/post to unfinished proxy check.
1 parent 4752304 commit 2bdfb5a

File tree

5 files changed

+94
-56
lines changed

5 files changed

+94
-56
lines changed

core/src/main/java/org/jruby/javasupport/Java.java

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -438,50 +438,71 @@ public static RubyModule getProxyClass(Ruby runtime, JavaClass javaClass) {
438438
}
439439

440440
public static RubyModule getProxyClass(final Ruby runtime, final Class<?> clazz) {
441-
RubyModule unfinished = runtime.getJavaSupport().getUnfinishedProxyClassCache().get(clazz).get();
442-
if (unfinished != null) return unfinished;
441+
RubyModule proxy = runtime.getJavaSupport().getUnfinishedProxy(clazz);
442+
if (proxy != null) return proxy;
443443
return runtime.getJavaSupport().getProxyClassFromCache(clazz);
444444
}
445445

446446
// Only used by proxy ClassValue calculator in JavaSupport
447447
static RubyModule createProxyClassForClass(final Ruby runtime, final Class<?> clazz) {
448448
final JavaSupport javaSupport = runtime.getJavaSupport();
449449

450-
if (clazz.isInterface()) return generateInterfaceProxy(runtime, clazz);
451-
452-
return generateClassProxy(runtime, clazz, javaSupport);
453-
}
450+
RubyModule proxy;
451+
RubyClass superClass = null;
452+
if (clazz.isInterface()) {
453+
proxy = (RubyModule) runtime.getJavaSupport().getJavaInterfaceTemplate().dup();
454+
} else {
455+
if (clazz.isArray()) {
456+
superClass = javaSupport.getArrayProxyClass();
457+
} else if (clazz.isPrimitive()) {
458+
superClass = javaSupport.getConcreteProxyClass();
459+
} else if (clazz == Object.class) {
460+
superClass = javaSupport.getConcreteProxyClass();
461+
} else {
462+
// other java proxy classes added under their superclass' java proxy
463+
superClass = (RubyClass) getProxyClass(runtime, clazz.getSuperclass());
464+
}
465+
proxy = RubyClass.newClass(runtime, superClass);
466+
}
454467

455-
private static RubyModule generateInterfaceProxy(final Ruby runtime, final Class javaClass) {
456-
if (!javaClass.isInterface()) {
457-
throw runtime.newArgumentError(javaClass.toString() + " is not an interface");
468+
// ensure proxy is visible down-thread
469+
javaSupport.beginProxy(clazz, proxy);
470+
try {
471+
if (clazz.isInterface()) {
472+
generateInterfaceProxy(runtime, clazz, proxy);
473+
} else {
474+
generateClassProxy(runtime, clazz, (RubyClass)proxy, superClass, javaSupport);
475+
}
476+
} finally {
477+
javaSupport.endProxy(clazz);
458478
}
459479

460-
RubyModule proxyModule = (RubyModule) runtime.getJavaSupport().getJavaInterfaceTemplate().dup();
480+
return proxy;
481+
}
482+
483+
private static void generateInterfaceProxy(final Ruby runtime, final Class javaClass, final RubyModule proxy) {
484+
assert javaClass.isInterface() : "not an interface: " + javaClass;
461485

462486
// include any interfaces we extend
463487
final Class<?>[] extended = javaClass.getInterfaces();
464-
for ( int i = extended.length; --i >= 0; ) {
488+
for (int i = extended.length; --i >= 0; ) {
465489
RubyModule extModule = getInterfaceModule(runtime, extended[i]);
466-
proxyModule.includeModule(extModule);
490+
proxy.includeModule(extModule);
467491
}
468-
Initializer.setupProxyModule(runtime, javaClass, proxyModule);
469-
addToJavaPackageModule(proxyModule);
470-
471-
return proxyModule;
492+
Initializer.setupProxyModule(runtime, javaClass, proxy);
493+
addToJavaPackageModule(proxy);
472494
}
473495

474-
private static RubyModule generateClassProxy(Ruby runtime, Class<?> clazz, JavaSupport javaSupport) {
475-
RubyModule proxyClass;
496+
private static void generateClassProxy(Ruby runtime, Class<?> clazz, RubyClass proxy, RubyClass superClass, JavaSupport javaSupport) {
476497
if (clazz.isArray()) {
477-
proxyClass = createProxyClass(runtime, javaSupport.getArrayProxyClass(), clazz, true);
498+
createProxyClass(runtime, proxy, clazz, true);
478499

479500
// FIXME: Organizationally this might be nicer in a specialized class
480501
if ( clazz.getComponentType() == byte.class ) {
481502
final Encoding ascii8bit = runtime.getEncodingService().getAscii8bitEncoding();
482503

483504
// All bytes can be considered raw strings and forced to particular codings if not 8bitascii
484-
proxyClass.addMethod("to_s", new JavaMethodZero(proxyClass, PUBLIC) {
505+
proxy.addMethod("to_s", new JavaMethodZero(proxy, PUBLIC) {
485506
@Override
486507
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
487508
ByteList bytes = new ByteList((byte[]) ((ArrayJavaProxy) self).getObject(), ascii8bit);
@@ -491,55 +512,48 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
491512
}
492513
}
493514
else if ( clazz.isPrimitive() ) {
494-
proxyClass = createProxyClass(runtime, javaSupport.getConcreteProxyClass(), clazz, true);
515+
createProxyClass(runtime, proxy, clazz, true);
495516
}
496517
else if ( clazz == Object.class ) {
497518
// java.lang.Object is added at root of java proxy classes
498-
proxyClass = createProxyClass(runtime, javaSupport.getConcreteProxyClass(), clazz, true);
519+
createProxyClass(runtime, proxy, clazz, true);
499520
if (NEW_STYLE_EXTENSION) {
500-
proxyClass.getMetaClass().defineAnnotatedMethods(NewStyleExtensionInherited.class);
521+
proxy.getMetaClass().defineAnnotatedMethods(NewStyleExtensionInherited.class);
501522
} else {
502-
proxyClass.getMetaClass().defineAnnotatedMethods(OldStyleExtensionInherited.class);
523+
proxy.getMetaClass().defineAnnotatedMethods(OldStyleExtensionInherited.class);
503524
}
504-
addToJavaPackageModule(proxyClass);
525+
addToJavaPackageModule(proxy);
505526
}
506527
else {
507-
// other java proxy classes added under their superclass' java proxy
508-
RubyClass superProxyClass = (RubyClass) getProxyClass(runtime, clazz.getSuperclass());
509-
proxyClass = createProxyClass(runtime, superProxyClass, clazz, false);
528+
createProxyClass(runtime, proxy, clazz, false);
510529
// include interface modules into the proxy class
511530
final Class<?>[] interfaces = clazz.getInterfaces();
512531
for ( int i = interfaces.length; --i >= 0; ) {
513-
proxyClass.includeModule(getInterfaceModule(runtime, interfaces[i]));
532+
proxy.includeModule(getInterfaceModule(runtime, interfaces[i]));
514533
}
515534
if ( Modifier.isPublic(clazz.getModifiers()) ) {
516-
addToJavaPackageModule(proxyClass);
535+
addToJavaPackageModule(proxy);
517536
}
518537
}
519538

520539
// JRUBY-1000, fail early when attempting to subclass a final Java class;
521540
// solved here by adding an exception-throwing "inherited"
522541
if ( Modifier.isFinal(clazz.getModifiers()) ) {
523542
final String clazzName = clazz.getCanonicalName();
524-
proxyClass.getMetaClass().addMethod("inherited", new org.jruby.internal.runtime.methods.JavaMethod(proxyClass, PUBLIC) {
543+
proxy.getMetaClass().addMethod("inherited", new org.jruby.internal.runtime.methods.JavaMethod(proxy, PUBLIC) {
525544
@Override
526545
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
527546
throw context.runtime.newTypeError("can not extend final Java class: " + clazzName);
528547
}
529548
});
530549
}
531-
532-
return proxyClass;
533550
}
534551

535552
private static RubyClass createProxyClass(final Ruby runtime,
536-
final RubyClass baseType, final Class<?> javaClass, boolean invokeInherited) {
537-
RubyClass proxyClass;
553+
final RubyClass proxyClass, final Class<?> javaClass, boolean invokeInherited) {
538554

539-
// this needs to be split, since conditional calling #inherited doesn't fit standard ruby semantics
555+
final RubyClass superClass = proxyClass.getSuperClass();
540556

541-
final RubyClass superClass = baseType;
542-
proxyClass = RubyClass.newClass(runtime, superClass);
543557
proxyClass.makeMetaClass( superClass.getMetaClass() );
544558

545559
if ( Map.class.isAssignableFrom( javaClass ) ) {
@@ -1456,6 +1470,6 @@ private static void addToJavaPackageModule(RubyModule proxyClass, JavaClass java
14561470
@Deprecated
14571471
private static RubyClass createProxyClass(final Ruby runtime,
14581472
final RubyClass baseType, final JavaClass javaClass, boolean invokeInherited) {
1459-
return createProxyClass(runtime, baseType, javaClass.javaClass(), invokeInherited);
1473+
return createProxyClass(runtime, RubyClass.newClass(runtime, baseType), javaClass, invokeInherited);
14601474
}
14611475
}

core/src/main/java/org/jruby/javasupport/JavaSupport.java

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import java.util.HashMap;
3939
import java.util.Map;
4040
import java.util.Set;
41+
import java.util.concurrent.ConcurrentHashMap;
42+
import java.util.concurrent.locks.ReentrantLock;
4143

4244
import org.jruby.Ruby;
4345
import org.jruby.RubyClass;
@@ -67,7 +69,13 @@ public IRubyObject allocateProxy(Object javaObject, RubyClass clazz) {
6769

6870
private final ClassValue<JavaClass> javaClassCache;
6971
private final ClassValue<RubyModule> proxyClassCache;
70-
private final ClassValue<ThreadLocal<RubyModule>> unfinishedProxyClassCache;
72+
private class UnfinishedProxy extends ReentrantLock {
73+
volatile RubyModule proxy;
74+
UnfinishedProxy(RubyModule proxy) {
75+
this.proxy = proxy;
76+
}
77+
}
78+
private final Map<Class, UnfinishedProxy> unfinishedProxies;
7179
private final ClassValue<Map<String, AssignedName>> staticAssignedNames;
7280
private final ClassValue<Map<String, AssignedName>> instanceAssignedNames;
7381

@@ -104,19 +112,20 @@ public JavaClass computeValue(Class<?> cls) {
104112
return new JavaClass(runtime, cls);
105113
}
106114
});
115+
107116
this.proxyClassCache = ClassValue.newInstance(new ClassValueCalculator<RubyModule>() {
117+
/**
118+
* Because of the complexity of processing a given class and all its dependencies,
119+
* we opt to synchronize this logic. Creation of all proxies goes through here,
120+
* allowing us to skip some threading work downstream.
121+
*/
108122
@Override
109-
public RubyModule computeValue(Class<?> cls) {
123+
public synchronized RubyModule computeValue(Class<?> cls) {
110124
return Java.createProxyClassForClass(runtime, cls);
111125
}
112126
});
113-
this.unfinishedProxyClassCache = ClassValue.newInstance(new ClassValueCalculator<ThreadLocal<RubyModule>>() {
114-
@Override
115-
public ThreadLocal<RubyModule> computeValue(Class<?> cls) {
116-
return new ThreadLocal<RubyModule>();
117-
}
118-
});
119-
this.staticAssignedNames =ClassValue.newInstance(new ClassValueCalculator<Map<String, AssignedName>>() {
127+
128+
this.staticAssignedNames = ClassValue.newInstance(new ClassValueCalculator<Map<String, AssignedName>>() {
120129
@Override
121130
public Map<String, AssignedName> computeValue(Class<?> cls) {
122131
return new HashMap<String, AssignedName>();
@@ -128,6 +137,9 @@ public Map<String, AssignedName> computeValue(Class<?> cls) {
128137
return new HashMap<String, AssignedName>();
129138
}
130139
});
140+
141+
// Proxy creation is synchronized (see above) so a HashMap is fine for recursion detection.
142+
this.unfinishedProxies = new ConcurrentHashMap<Class, UnfinishedProxy>(8, 0.75f, 1);
131143
}
132144

133145
public Class loadJavaClass(String className) throws ClassNotFoundException {
@@ -327,10 +339,6 @@ public Map<Set<?>, JavaProxyClass> getJavaProxyClassCache() {
327339
return this.javaProxyClassCache;
328340
}
329341

330-
public ClassValue<ThreadLocal<RubyModule>> getUnfinishedProxyClassCache() {
331-
return unfinishedProxyClassCache;
332-
}
333-
334342
public ClassValue<Map<String, AssignedName>> getStaticAssignedNames() {
335343
return staticAssignedNames;
336344
}
@@ -339,6 +347,23 @@ public ClassValue<Map<String, AssignedName>> getInstanceAssignedNames() {
339347
return instanceAssignedNames;
340348
}
341349

350+
public void beginProxy(Class cls, RubyModule proxy) {
351+
UnfinishedProxy up = new UnfinishedProxy(proxy);
352+
up.lock();
353+
unfinishedProxies.put(cls, up);
354+
}
355+
356+
public void endProxy(Class cls) {
357+
UnfinishedProxy up = unfinishedProxies.remove(cls);
358+
up.unlock();
359+
}
360+
361+
public RubyModule getUnfinishedProxy(Class cls) {
362+
UnfinishedProxy up = unfinishedProxies.get(cls);
363+
if (up != null && up.isHeldByCurrentThread()) return up.proxy;
364+
return null;
365+
}
366+
342367
@Deprecated
343368
private volatile Map<Object, Object[]> javaObjectVariables;
344369

core/src/main/java/org/jruby/javasupport/binding/ClassInitializer.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ public RubyModule initialize(RubyModule proxy) {
3232

3333
proxyClass.setReifiedClass(javaClass);
3434

35-
runtime.getJavaSupport().getUnfinishedProxyClassCache().get(javaClass).set(proxyClass);
36-
3735
if ( javaClass.isArray() || javaClass.isPrimitive() ) {
3836
return proxy;
3937
}

core/src/main/java/org/jruby/javasupport/binding/Initializer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jruby.internal.runtime.methods.JavaMethod;
88
import org.jruby.javasupport.Java;
99
import org.jruby.javasupport.JavaClass;
10+
import org.jruby.javasupport.JavaSupport;
1011
import org.jruby.javasupport.JavaUtil;
1112
import org.jruby.runtime.Block;
1213
import org.jruby.runtime.Helpers;
@@ -34,6 +35,7 @@
3435
*/
3536
public abstract class Initializer {
3637
protected final Ruby runtime;
38+
protected final JavaSupport javaSupport;
3739
protected final Class javaClass;
3840

3941
private static final Logger LOG = LoggerFactory.getLogger("Initializer");
@@ -68,6 +70,7 @@ public abstract class Initializer {
6870

6971
public Initializer(Ruby runtime, Class javaClass) {
7072
this.runtime = runtime;
73+
this.javaSupport = runtime.getJavaSupport();
7174
this.javaClass = javaClass;
7275
}
7376

core/src/main/java/org/jruby/javasupport/binding/InterfaceInitializer.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public RubyModule initialize(RubyModule proxy) {
2525

2626
super.initializeBase(proxy);
2727

28-
runtime.getJavaSupport().getUnfinishedProxyClassCache().get(javaClass).set(proxy);
29-
3028
Field[] fields = JavaClass.getDeclaredFields(javaClass);
3129

3230
for (int i = fields.length; --i >= 0; ) {

0 commit comments

Comments
 (0)