Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
I tried to write a test case to verify the hypothesis along the line of https://issues.jenkins-ci.org/browse/JENKINS-20707?focusedCommentId=198755&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-198755, but ultimately it was a dead-end. The reason I was wrong is because ImportedClassLoaderTable maintain strong references to all the imported classloaders, and it prevents them from getting garbage collected. Probably for the reasons I was mentioning in JENKINS-20707, RemoteClassLoaders never get garbage collected, so they should never get unexported. I've spent much time trying to make the test case work, so I'm going to commit it on the side just in case someone finds useful in the future.
- Loading branch information
Showing
6 changed files
with
209 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package hudson.remoting; | ||
|
||
import hudson.remoting.util.OneShotEvent; | ||
import org.jvnet.hudson.test.Bug; | ||
|
||
import java.io.IOException; | ||
import java.io.ObjectInputStream; | ||
import java.io.Serializable; | ||
import java.lang.ref.WeakReference; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
/** | ||
* @author Kohsuke Kawaguchi | ||
*/ | ||
public class Bug20707Test extends RmiTestBase implements Serializable { | ||
@Bug(20707) | ||
public void testGc() throws Exception { | ||
final DummyClassLoader cl = new DummyClassLoader(TestEcho.class); | ||
final IEcho c = cl.load(TestEcho.class); | ||
c.set(new Object[]{ | ||
new HangInducer(), | ||
cl.load(TestEcho.class) // <- DA BOMB. We are trying to blow up the deserialization of this | ||
}); | ||
|
||
// when the response comes back, make it hang | ||
HANG = new OneShotEvent(); | ||
|
||
ExecutorService es = Executors.newCachedThreadPool(); | ||
|
||
final java.util.concurrent.Future<Object> f = es.submit(new java.util.concurrent.Callable<Object>() { | ||
public Object call() throws Exception { | ||
// send a couple of objects, which exports DummyClassLoader. | ||
// when the computation is done on the other side, RemoteClassLoader can be garbage collected any time | ||
// on this side, the obtained UserResponse gets passed to the thread that made the request | ||
// (this thread) | ||
|
||
channel.call(new TestEcho(c)); | ||
|
||
// we'll use HangInducer.HANG to make the response unmarshalling hang | ||
|
||
return null; | ||
} | ||
}); | ||
|
||
// wait until the echo call comes back and hangs at the unmarshalling | ||
BLOCKING.block(); | ||
|
||
// induce GC on the other side until we get classloader unexported | ||
channel.call(new Callable<Void,InterruptedException>() { | ||
public Void call() throws InterruptedException { | ||
while (ECHO_CLASSLOADER.get()!=null) { | ||
System.gc(); | ||
Thread.sleep(100); | ||
} | ||
return null; | ||
} | ||
}); | ||
|
||
// by the time the above Callable comes back, Unexport command has executed and classloader is unexported | ||
assertFalse(channel.exportedObjects.isExported(cl)); | ||
|
||
// and now if we let the unmarshalling go, it'll finish deserializing HangInducer | ||
// and will try to unmarshal DA BOMB, and it should blow up | ||
f.get(); | ||
} | ||
|
||
private Object writeReplace() { | ||
return null; | ||
} | ||
|
||
public static interface IEcho extends Callable<Object,IOException> { | ||
void set(Object o); | ||
Object get(); | ||
} | ||
|
||
public static class HangInducer implements Serializable { | ||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { | ||
if (Channel.current().getName().equals("north")) { | ||
try { | ||
BLOCKING.signal(); // let the world know that we are hanging now | ||
HANG.block(); | ||
} catch (InterruptedException e) { | ||
throw new Error(e); | ||
} | ||
} | ||
} | ||
|
||
private static final long serialVersionUID = 1L; | ||
} | ||
|
||
private static OneShotEvent BLOCKING = new OneShotEvent(); | ||
private static OneShotEvent HANG; | ||
|
||
static WeakReference<ClassLoader> ECHO_CLASSLOADER; | ||
|
||
public static void set(WeakReference<ClassLoader> cl) { | ||
ECHO_CLASSLOADER = cl; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package hudson.remoting; | ||
|
||
import hudson.remoting.Bug20707Test.IEcho; | ||
|
||
import java.io.IOException; | ||
import java.lang.ref.WeakReference; | ||
|
||
/** | ||
* @author Kohsuke Kawaguchi | ||
*/ | ||
public class TestEcho implements IEcho { | ||
/** | ||
* For adding arbitrary objects into the echo back. | ||
*/ | ||
private Object o; | ||
|
||
public TestEcho(Object o) { | ||
this.o = o; | ||
} | ||
|
||
public TestEcho() { | ||
} | ||
|
||
public Object call() throws IOException { | ||
Bug20707Test.set(new WeakReference<ClassLoader>(getClass().getClassLoader())); | ||
return this; | ||
} | ||
|
||
public void set(Object o) { | ||
this.o = o; | ||
} | ||
|
||
public Object get() { | ||
return o; | ||
} | ||
|
||
private static final long serialVersionUID = 1L; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package hudson.remoting.util; | ||
|
||
public final class OneShotEvent { | ||
private boolean signaled; | ||
private final Object lock; | ||
|
||
public OneShotEvent() { | ||
this.lock = this; | ||
} | ||
|
||
public OneShotEvent(Object lock) { | ||
this.lock = lock; | ||
} | ||
|
||
/** | ||
* Non-blocking method that signals this event. | ||
*/ | ||
public void signal() { | ||
synchronized (lock) { | ||
if(signaled) return; | ||
this.signaled = true; | ||
lock.notifyAll(); | ||
} | ||
} | ||
|
||
/** | ||
* Blocks until the event becomes the signaled state. | ||
* | ||
* <p> | ||
* This method blocks infinitely until a value is offered. | ||
*/ | ||
public void block() throws InterruptedException { | ||
synchronized (lock) { | ||
while(!signaled) | ||
lock.wait(); | ||
} | ||
} | ||
|
||
/** | ||
* Blocks until the event becomes the signaled state. | ||
* | ||
* <p> | ||
* If the specified amount of time elapses, | ||
* this method returns null even if the value isn't offered. | ||
*/ | ||
public void block(long timeout) throws InterruptedException { | ||
synchronized (lock) { | ||
if(!signaled) | ||
lock.wait(timeout); | ||
} | ||
} | ||
|
||
/** | ||
* Returns true if a value is offered. | ||
*/ | ||
public boolean isSignaled() { | ||
synchronized (lock) { | ||
return signaled; | ||
} | ||
} | ||
} |