Author: amiloslavskiy
Date: Thu Oct 15 10:16:39 2020
New Revision: 1882523
URL: http://svn.apache.org/viewvc?rev=1882523&view=rev
Log:
JavaHL: Fix crash when TunnelAgent continues reading after being closed
The problem here is that when 'RemoteSession' is destroyed, it also
does 'apr_pool_destroy()' which frees memory behind 'apr_file_t', which
is represented by 'TunnelChannel.nativeChannel' in Java.
'TunnelChannel' runs on a different thread and is unaware that
'apr_file_t' pointer is now invalid.
Fix this by informing 'TunnelChannel' before 'apr_file_t' is freed.
This crash is demonstrated by the following JavaHL tests:
* testCrash_RequestChannel_nativeRead_AfterException
* testCrash_RequestChannel_nativeRead_AfterSvnError
This commit alone does not fix all problems in these tests, see
subsequent commits as well.
[in subversion/bindings/javahl]
* native/OperationContext.cpp
(close_TunnelChannel): New function to inform Java side.
(openTunnel): Keep references to Java tunnel objects.
(closeTunnel): Inform Java side when tunnel is closed.
* src/org/apache/subversion/javahl/util/RequestChannel.java
* src/org/apache/subversion/javahl/util/ResponseChannel.java
Add 'synchronized' to avoid error described in 'syncClose()'
* src/org/apache/subversion/javahl/util/Tunnel.java
A new function to clean up. I decided not to change old '.close()'
because the new function can lead to a deadlock if used incorrectly.
Modified:
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
Modified:
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
URL:
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
---
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
(original)
+++
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/native/OperationContext.cpp
Thu Oct 15 10:16:39 2020
@@ -492,6 +492,8 @@ public:
request_out(NULL),
response_in(NULL),
response_out(NULL),
+ jrequest(NULL),
+ jresponse(NULL),
jclosecb(NULL)
{
status = apr_file_pipe_create_ex(&request_in, &request_out,
@@ -512,6 +514,8 @@ public:
apr_file_t *response_in;
apr_file_t *response_out;
apr_status_t status;
+ jobject jrequest;
+ jobject jresponse;
jobject jclosecb;
};
@@ -523,7 +527,10 @@ jobject create_Channel(const char *class
jmethodID ctor = env->GetMethodID(cls, "<init>", "(J)V");
if (JNIUtil::isJavaExceptionThrown())
return NULL;
- return env->NewObject(cls, ctor, reinterpret_cast<jlong>(fd));
+ jobject channel = env->NewObject(cls, ctor, reinterpret_cast<jlong>(fd));
+ if (JNIUtil::isJavaExceptionThrown())
+ return NULL;
+ return env->NewGlobalRef(channel);
}
jobject create_RequestChannel(JNIEnv *env, apr_file_t *fd)
@@ -534,6 +541,24 @@ jobject create_ResponseChannel(JNIEnv *e
{
return create_Channel(JAVAHL_CLASS("/util/ResponseChannel"), env, fd);
}
+void close_TunnelChannel(JNIEnv* env, jobject channel)
+{
+ // Usually after this function, the memory will be freed behind
+ // 'TunnelChannel.nativeChannel'. Ask Java side to forget it. This is the
+ // only way to avoid a JVM crash when 'TunnelAgent' tries to read/write,
+ // not knowing that 'TunnelChannel' is already closed in native side.
+ static jmethodID mid = 0;
+ if (0 == mid)
+ {
+ jclass cls;
+ SVN_JNI_CATCH_VOID(
+ cls = env->FindClass(JAVAHL_CLASS("/util/TunnelChannel")));
+ SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "syncClose", "()V"));
+ }
+
+ SVN_JNI_CATCH_VOID(env->CallVoidMethod(channel, mid));
+ env->DeleteGlobalRef(channel);
+}
} // anonymous namespace
svn_boolean_t
@@ -590,10 +615,10 @@ OperationContext::openTunnel(svn_stream_
JNIEnv *env = JNIUtil::getEnv();
- jobject jrequest = create_RequestChannel(env, tc->request_in);
+ tc->jrequest = create_RequestChannel(env, tc->request_in);
SVN_JNI_CATCH(, SVN_ERR_BASE);
- jobject jresponse = create_ResponseChannel(env, tc->response_out);
+ tc->jresponse = create_ResponseChannel(env, tc->response_out);
SVN_JNI_CATCH(, SVN_ERR_BASE);
jstring jtunnel_name = JNIUtil::makeJString(tunnel_name);
@@ -625,7 +650,7 @@ OperationContext::openTunnel(svn_stream_
jobject jtunnelcb = jobject(tunnel_baton);
SVN_JNI_CATCH(
tc->jclosecb = env->CallObjectMethod(
- jtunnelcb, mid, jrequest, jresponse,
+ jtunnelcb, mid, tc->jrequest, tc->jresponse,
jtunnel_name, juser, jhostname, jint(port)),
SVN_ERR_BASE);
@@ -657,7 +682,12 @@ void
OperationContext::closeTunnel(void *tunnel_context, void *)
{
TunnelContext* tc = static_cast<TunnelContext*>(tunnel_context);
+ jobject jrequest = tc->jrequest;
+ jobject jresponse = tc->jresponse;
jobject jclosecb = tc->jclosecb;
+
+ // Note that this closes other end of the pipe, which cancels and
+ // prevents further read/writes in 'TunnelAgent'
delete tc;
JNIEnv *env = JNIUtil::getEnv();
@@ -666,4 +696,14 @@ OperationContext::closeTunnel(void *tunn
if (jclosecb)
callCloseTunnelCallback(env, jclosecb);
+
+ if (jrequest)
+ {
+ close_TunnelChannel(env, jrequest);
+ }
+
+ if (jresponse)
+ {
+ close_TunnelChannel(env, jresponse);
+ }
}
Modified:
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
URL:
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
---
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
(original)
+++
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java
Thu Oct 15 10:16:39 2020
@@ -42,15 +42,18 @@ class RequestChannel
public int read(ByteBuffer dst) throws IOException
{
- long channel = nativeChannel.get();
- if (channel != 0)
- try {
- return nativeRead(channel, dst);
- } catch (IOException ex) {
- nativeChannel.set(0); // Close the channel
- throw ex;
- }
- throw new ClosedChannelException();
+ synchronized (nativeChannel)
+ {
+ long channel = nativeChannel.get();
+ if (channel != 0)
+ try {
+ return nativeRead(channel, dst);
+ } catch (IOException ex) {
+ nativeChannel.set(0); // Close the channel
+ throw ex;
+ }
+ throw new ClosedChannelException();
+ }
}
private static native int nativeRead(long nativeChannel, ByteBuffer dst)
Modified:
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
URL:
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
---
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
(original)
+++
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java
Thu Oct 15 10:16:39 2020
@@ -42,15 +42,18 @@ class ResponseChannel
public int write(ByteBuffer src) throws IOException
{
- long channel = this.nativeChannel.get();
- if (channel != 0)
- try {
- return nativeWrite(channel, src);
- } catch (IOException ex) {
- nativeChannel.set(0); // Close the channel
- throw ex;
- }
- throw new ClosedChannelException();
+ synchronized (nativeChannel)
+ {
+ long channel = this.nativeChannel.get();
+ if (channel != 0)
+ try {
+ return nativeWrite(channel, src);
+ } catch (IOException ex) {
+ nativeChannel.set(0); // Close the channel
+ throw ex;
+ }
+ throw new ClosedChannelException();
+ }
}
private static native int nativeWrite(long nativeChannel, ByteBuffer src)
Modified:
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
URL:
http://svn.apache.org/viewvc/subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java?rev=1882523&r1=1882522&r2=1882523&view=diff
==============================================================================
---
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
(original)
+++
subversion/branches/javahl-1.14-fixes/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java
Thu Oct 15 10:16:39 2020
@@ -50,6 +50,39 @@ abstract class TunnelChannel implements
nativeClose(channel);
}
+ /**
+ * Wait for current read/write to complete, then close() channel.
+ * Compared to close(), it has the following differences:
+ * <ul>
+ * <li>
+ * Prevents race condition where read/write could use incorrect file :
+ * <ol>
+ * <li>Writer thread extracts OS file descriptor from
nativeChannel.</li>
+ * <li>Writer thread calls OS API to write to file, passing file
descriptor.</li>
+ * <li>Writer thread is interrupted.</li>
+ * <li>Closer thread closes OS file descriptor. The file descriptor
number is now free.</li>
+ * <li>Unrelated thread opens a new file. OS reuses the old file
descriptor (currently free).</li>
+ * <li>Writer thread resumes inside OS API to write to file.</li>
+ * <li>Writer thread writes to unrelated file, corrupting it with
unexpected data.</li>
+ * </ol>
+ * </li>
+ * <li>
+ * It can no longer cancel a read/write operation already in progress.
+ * The native implementation closes the other end of the pipe,
breaking the pipe,
+ * which prevents the risk of never-completing read/write.
+ * </li>
+ * <ul/>
+ *
+ * @throws IOException
+ */
+ public void syncClose() throws IOException
+ {
+ synchronized (nativeChannel)
+ {
+ close();
+ }
+ }
+
private native static void nativeClose(long nativeChannel)
throws IOException;