Please find test snippet and patch attached.
[[[
Fix JavaHL crash in TunnelAgent.CloseTunnelCallback after GC
When jobject reference is kept across different JNI calls, a new global
reference must be requested with NewGlobalRef(). Otherwise, GC is free
to remove the object. Even if Java code keeps a reference to the object,
GC can still move the object around, invalidating the kept jobject,
which results in a native crash when trying to access it.
[in subversion/bindings/javahl]
* native/OperationContext.cpp
(OperationContext::openTunnel): Add NewGlobalRef() for kept jobject.
(OperationContext::closeTunnel): Add a matching DeleteGlobalRef().
]]]
Index: subversion/bindings/javahl/native/OperationContext.cpp
===================================================================
--- subversion/bindings/javahl/native/OperationContext.cpp (revision
1880687)
+++ subversion/bindings/javahl/native/OperationContext.cpp (working copy)
@@ -629,6 +629,12 @@
jtunnel_name, juser, jhostname, jint(port)),
SVN_ERR_BASE);
+ if (tc->jclosecb)
+ {
+ tc->jclosecb = env->NewGlobalRef(tc->jclosecb);
+ SVN_JNI_CATCH(, SVN_ERR_BASE);
+ }
+
return SVN_NO_ERROR;
}
@@ -656,4 +662,5 @@
SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "closeTunnel", "()V"));
}
SVN_JNI_CATCH_VOID(env->CallVoidMethod(jclosecb, mid));
+ env->DeleteGlobalRef(jclosecb);
}
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import org.apache.subversion.javahl.*;
import org.apache.subversion.javahl.callback.*;
import org.apache.subversion.javahl.remote.*;
/**
* @author Marc Strapetz
*/
public class JavaHL_Crash_RemoteSession_nativeDispose {
public static void main(String[] args) throws Exception {
final SVNClient svnClient = new SVNClient();
final TunnelAgent.CloseTunnelCallback closeTunnelCallback = ()
-> System.out.println("TunnelAgent.CloseTunnelCallback");
final TunnelAgent tunnelAgent = new TunnelAgent() {
@Override
public boolean checkTunnel(String name) {
return true;
}
@Override
public CloseTunnelCallback
openTunnel(ReadableByteChannel request, WritableByteChannel response, String
name, String user, String hostname, int port) throws Throwable {
new Thread(() -> {
try {
sleep(100);
response.write(ByteBuffer.wrap("( success ( 2 2 ( ) ( edit-pipeline svndiff1
absent-entries commit-revprops depth log-revprops atomic-revprops
partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) )
".getBytes(StandardCharsets.UTF_8)));
request.read(ByteBuffer.allocate(1024));
sleep(100);
response.write(ByteBuffer.wrap("( success ( ( ANONYMOUS EXTERNAL ) 15:Test
Repository ) ) ".getBytes(StandardCharsets.UTF_8)));
request.read(ByteBuffer.allocate(1024));
sleep(100);
response.write(ByteBuffer.wrap("( success ( ) ) ( success (
36:00000000-0000-0000-0000-000000000000
57:svn+ssh://[email protected]/home/testUser/svnrepos/test ( mergeinfo ) ) )
".getBytes(StandardCharsets.UTF_8)));
final ByteBuffer allocate =
ByteBuffer.allocate(1024);
request.read(allocate);
sleep(100);
}
catch (IOException e) {
e.printStackTrace();
}
}).start();
return closeTunnelCallback;
}
};
svnClient.setTunnelAgent(tunnelAgent);
try {
final RemoteFactory f = new RemoteFactory();
f.setTunnelAgent(tunnelAgent);
final ISVNRemote remote =
f.openRemoteSession("svn+ssh://[email protected]/home/testUser/svnrepos/test",
4);
// 'OperationContext::openTunnel()' doesn't
'NewGlobalRef()' callback returned by 'TunnelAgent.openTunnel()'.
// When GC runs, it disposes the callback. When JavaHL
tries to call it in 'remote.dispose()', JVM crashes.
for (int index = 0; index < 5; index++) {
sleep(100);
System.gc();
}
remote.dispose();
}
catch (SubversionException ex) {
ex.printStackTrace();
}
}
private static void sleep(int millis) {
try {
Thread.sleep(millis);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}