From ec9d4a8596080ca622df4cd33f210f5a480d6689 Mon Sep 17 00:00:00 2001 From: David Connelly Date: Sun, 14 Feb 2016 18:02:46 -0800 Subject: [PATCH 1/2] Issue #425: Fix hanging StreamingSubscriptionConnection.close() StreamingSubscriptionConnection.close() calls HttpWebRequest.close() which will first attempts to consume the response before releasing the connection. This unfortunately will hang since the notification reader thread is still blocked reading from the same connection. The fix is to just the connection without consuming the response entity. --- .../data/core/request/HangingServiceRequestBase.java | 4 ++-- .../data/core/request/HttpClientWebRequest.java | 7 +++++++ .../webservices/data/core/request/HttpWebRequest.java | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java b/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java index 04db10e43..5ced262fe 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java @@ -266,7 +266,7 @@ private void setIsConnected(boolean value) { */ public void disconnect() { synchronized (this) { - IOUtils.closeQuietly(this.response); + this.response.releaseConnection(); this.disconnect(HangingRequestDisconnectReason.UserInitiated, null); } } @@ -279,7 +279,7 @@ public void disconnect() { */ public void disconnect(HangingRequestDisconnectReason reason, Exception exception) { if (this.isConnected()) { - IOUtils.closeQuietly(this.response); + this.response.releaseConnection(); this.internalOnDisconnect(reason, exception); } } diff --git a/src/main/java/microsoft/exchange/webservices/data/core/request/HttpClientWebRequest.java b/src/main/java/microsoft/exchange/webservices/data/core/request/HttpClientWebRequest.java index 8cd6ccb8f..eaf8b32ae 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/request/HttpClientWebRequest.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/request/HttpClientWebRequest.java @@ -94,6 +94,13 @@ public void close() throws IOException { httpPost = null; } + @Override + public void releaseConnection() { + if (httpPost != null) { + httpPost.releaseConnection(); + } + } + /** * Prepares the request by setting appropriate headers, authentication, timeouts, etc. */ diff --git a/src/main/java/microsoft/exchange/webservices/data/core/request/HttpWebRequest.java b/src/main/java/microsoft/exchange/webservices/data/core/request/HttpWebRequest.java index f8abec964..28758a2f2 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/request/HttpWebRequest.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/request/HttpWebRequest.java @@ -495,6 +495,8 @@ public void setCredentials(String domain, String user, String pwd) { */ public abstract void close() throws IOException; + public abstract void releaseConnection(); + /** * Prepare connection. */ From bf7a6a111950de6f44f3e35fb3456d1eec92dabf Mon Sep 17 00:00:00 2001 From: David Connelly Date: Sun, 14 Feb 2016 20:15:43 -0800 Subject: [PATCH 2/2] Don't invoke disconnect listener if socket was closed because we are already disconnecting --- .../request/HangingServiceRequestBase.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java b/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java index 5ced262fe..2d9534b25 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/request/HangingServiceRequestBase.java @@ -46,6 +46,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectStreamException; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownServiceException; import java.util.ArrayList; @@ -93,6 +94,8 @@ public interface IHandleResponseObject { */ private HttpWebRequest response; + private volatile boolean disconnecting; + /** * Expected minimum frequency in response, in milliseconds. */ @@ -222,15 +225,11 @@ private void parseResponses() { } catch (SocketTimeoutException ex) { // The connection timed out. this.disconnect(HangingRequestDisconnectReason.Timeout, ex); - } catch (UnknownServiceException ex) { - // Stream is closed, so disconnect. - this.disconnect(HangingRequestDisconnectReason.Exception, ex); - } catch (ObjectStreamException ex) { - // Stream is closed, so disconnect. - this.disconnect(HangingRequestDisconnectReason.Exception, ex); } catch (IOException ex) { - // Stream is closed, so disconnect. - this.disconnect(HangingRequestDisconnectReason.Exception, ex); + if (!(disconnecting && isSocketClosed(ex))) { + // Stream was closed due to an error + this.disconnect(HangingRequestDisconnectReason.Exception, ex); + } } catch (UnsupportedOperationException ex) { LOG.error(ex); // This is thrown if we close the stream during a @@ -239,13 +238,26 @@ private void parseResponses() { //simply results in a long-running connection. this.disconnect(HangingRequestDisconnectReason.UserInitiated, null); } catch (Exception ex) { - // Stream is closed, so disconnect. - this.disconnect(HangingRequestDisconnectReason.Exception, ex); + if (!(disconnecting && isSocketClosed(ex))) { + // Stream was closed due to an error + this.disconnect(HangingRequestDisconnectReason.Exception, ex); + } + } finally { IOUtils.closeQuietly(responseCopy); } } + private static boolean isSocketClosed(Throwable e) { + while (e != null) { + if (e instanceof SocketException) { + return true; + } + e = e.getCause(); + } + return false; + } + private boolean isConnected; /** @@ -266,6 +278,7 @@ private void setIsConnected(boolean value) { */ public void disconnect() { synchronized (this) { + disconnecting = true; this.response.releaseConnection(); this.disconnect(HangingRequestDisconnectReason.UserInitiated, null); } @@ -279,6 +292,7 @@ public void disconnect() { */ public void disconnect(HangingRequestDisconnectReason reason, Exception exception) { if (this.isConnected()) { + disconnecting = true; this.response.releaseConnection(); this.internalOnDisconnect(reason, exception); }