Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit 286cf5b

Browse files
committed
Added keepAliveSocketFactory
1 parent 9beae8b commit 286cf5b

File tree

3 files changed

+140
-8
lines changed

3 files changed

+140
-8
lines changed

microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public abstract class RequestOptions {
5656
*/
5757
private Boolean requireEncryption;
5858

59+
/**
60+
* A value to indicate whether we should disable socket keep-alive.
61+
*/
62+
private Boolean disableHttpsSocketKeepAlive;
63+
5964
/**
6065
* Creates an instance of the <code>RequestOptions</code> class.
6166
*/
@@ -78,6 +83,7 @@ public RequestOptions(final RequestOptions other) {
7883
this.setMaximumExecutionTimeInMs(other.getMaximumExecutionTimeInMs());
7984
this.setOperationExpiryTimeInMs(other.getOperationExpiryTimeInMs());
8085
this.setRequireEncryption(other.requireEncryption());
86+
this.setDisableHttpsSocketKeepAlive(other.disableHttpsSocketKeepAlive());
8187
}
8288
}
8389

@@ -132,6 +138,10 @@ protected static void populateRequestOptions(RequestOptions modifiedOptions,
132138
modifiedOptions.setOperationExpiryTimeInMs(new Date().getTime()
133139
+ modifiedOptions.getMaximumExecutionTimeInMs());
134140
}
141+
142+
if (modifiedOptions.disableHttpsSocketKeepAlive() == null) {
143+
modifiedOptions.setDisableHttpsSocketKeepAlive(clientOptions.disableHttpsSocketKeepAlive());
144+
}
135145
}
136146

137147
/**
@@ -178,18 +188,29 @@ public final LocationMode getLocationMode() {
178188
public Integer getMaximumExecutionTimeInMs() {
179189
return this.maximumExecutionTimeInMs;
180190
}
181-
191+
182192
/**
183193
* Gets a value to indicate whether all data written and read must be encrypted. Use <code>true</code> to
184194
* encrypt/decrypt data for transactions; otherwise, <code>false</code>. For more
185195
* information about require encryption defaults, see {@link #setRequireEncryption(Boolean)}.
186-
*
196+
*
187197
* @return A value to indicate whether all data written and read must be encrypted.
188198
*/
189199
public Boolean requireEncryption() {
190200
return this.requireEncryption;
191201
}
192202

203+
/**
204+
* Gets a value to indicate whether https socket keep-alive should be disabled. Use <code>true</code> to disable
205+
* keep-alive; otherwise, <code>false</code>. For more information about disableHttpsSocketKeepAlive defaults, see
206+
* {@link ServiceClient#getDefaultRequestOptions()}
207+
*
208+
* @return A value to indicate whther https socket keep-alive should be disabled.
209+
*/
210+
public Boolean disableHttpsSocketKeepAlive() {
211+
return this.disableHttpsSocketKeepAlive;
212+
}
213+
193214
/**
194215
* RESERVED FOR INTERNAL USE.
195216
*
@@ -283,8 +304,9 @@ public void setMaximumExecutionTimeInMs(Integer maximumExecutionTimeInMs) {
283304
* <p>
284305
* The default is set in the client and is by default false, indicating encryption is not required. You can change
285306
* the value on this request by setting this property. You can also change the value on the
286-
* {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
287-
* client will use the appropriate value.
307+
* * {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the
308+
* service
309+
* * client will use the appropriate value.
288310
*
289311
* @param requireEncryption
290312
* A value to indicate whether all data written and read must be encrypted.
@@ -293,6 +315,28 @@ public void setRequireEncryption(Boolean requireEncryption) {
293315
this.requireEncryption = requireEncryption;
294316
}
295317

318+
/**
319+
* Sets a value to indicate whether https socket keep-alive should be disabled. Use <code>true</code> to disable
320+
* keep-alive; otherwise, <code>false</code>
321+
* <p>
322+
* The default is set in the client and is by default false, indicating that https socket keep-alive will be
323+
* enabled. You can change the value on this request by setting this property. You can also change the value on
324+
* on the {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the
325+
* service client will use the appropriate value.
326+
* <p>
327+
* Setting keep-alive on https sockets is to work around a bug in the JVM where connection timeouts are not honored
328+
* on retried requests. In those cases, we use socket keep-alive as a fallback. Unfortunately, the timeout value
329+
* must be taken from a JVM property rather than configured locally. Therefore, in rare cases the JVM has configured
330+
* aggressively short keep-alive times, it may be beneficial to disable the use of keep-alives lest they interfere
331+
* with long running data transfer operations.
332+
*
333+
* @param disableHttpsSocketKeepAlive
334+
* A value to indicate whether https socket keep-alive should be disabled.
335+
*/
336+
public void setDisableHttpsSocketKeepAlive(Boolean disableHttpsSocketKeepAlive) {
337+
this.disableHttpsSocketKeepAlive = disableHttpsSocketKeepAlive;
338+
}
339+
296340
/**
297341
* RESERVED FOR INTERNAL USE.
298342
*

microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
import java.util.List;
2828
import java.util.Map;
2929
import java.util.Map.Entry;
30-
import java.util.Set;
3130

3231
import com.microsoft.azure.storage.*;
33-
import com.microsoft.azure.storage.blob.BlobBatchOperation;
34-
import com.microsoft.azure.storage.blob.BlobRequestOptions;
35-
import com.microsoft.azure.storage.blob.CloudBlobClient;
32+
import com.microsoft.azure.storage.Constants;
33+
import com.microsoft.azure.storage.OperationContext;
34+
import com.microsoft.azure.storage.RequestOptions;
35+
import com.microsoft.azure.storage.StorageException;
36+
37+
import javax.net.ssl.HttpsURLConnection;
3638

3739
import static com.microsoft.azure.storage.Constants.QueryConstants.PROPERTIES;
3840

@@ -223,6 +225,14 @@ protected PasswordAuthentication getPasswordAuthentication() {
223225
retConnection = (HttpURLConnection) resourceUrl.openConnection();
224226
}
225227

228+
/*
229+
If we are using https, check if we should enable socket keep-alive timeouts to work around JVM bug.
230+
*/
231+
if (retConnection instanceof HttpsURLConnection && !options.disableHttpsSocketKeepAlive()) {
232+
HttpsURLConnection httpsConnection = ((HttpsURLConnection) retConnection);
233+
httpsConnection.setSSLSocketFactory(new KeepAliveSocketFactory(httpsConnection.getSSLSocketFactory()));
234+
}
235+
226236
/*
227237
* ReadTimeout must be explicitly set to avoid a bug in JDK 6. In certain cases, this bug causes an immediate
228238
* read timeout exception to be thrown even if ReadTimeout is not set.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.storage.core;
5+
6+
import javax.net.ssl.SSLSocketFactory;
7+
import java.io.IOException;
8+
import java.net.InetAddress;
9+
import java.net.Socket;
10+
import java.net.UnknownHostException;
11+
12+
/**
13+
* RESERVED FOR INTERNAL USE.
14+
*
15+
* This type is used to help work around a bug in the JDK where connection timeouts are not honored on a retried
16+
* request. In other words, if a customer set a timeout on an operation, this timeout is only ever respected on the
17+
* first attempt at the request. Retries will cause a different underlying connection implementation to be loaded that
18+
* will ignore the timeout parameter. Therefore, requests can potentially hang forever if the connection is broken
19+
* after these retries.
20+
*
21+
* Enabling keep-alive timeouts acts as a fallback in these scenarios so that, even if the operation timeout is ignored,
22+
* the socket will still eventually timeout and the request will be cancelled. We enable keep alive timeouts via a
23+
* wrapper implementation of a SocketFactory. We use a default socket factory to get sockets from the system and then
24+
* simply set the keep-alive option to true before returning to the client. This factory will be set on the
25+
* HttpsUrlConnection objects.
26+
*/
27+
public class KeepAliveSocketFactory extends SSLSocketFactory {
28+
private SSLSocketFactory delegate;
29+
30+
KeepAliveSocketFactory(SSLSocketFactory delegate) {
31+
this.delegate = delegate;
32+
}
33+
34+
@Override
35+
public String[] getDefaultCipherSuites() {
36+
return delegate.getDefaultCipherSuites();
37+
}
38+
39+
@Override
40+
public String[] getSupportedCipherSuites() {
41+
return delegate.getSupportedCipherSuites();
42+
}
43+
44+
@Override
45+
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
46+
Socket ret = delegate.createSocket(socket, s, i, b);
47+
ret.setKeepAlive(true);
48+
return ret;
49+
}
50+
51+
@Override
52+
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
53+
Socket ret = delegate.createSocket(s, i);
54+
ret.setKeepAlive(true);
55+
return ret;
56+
}
57+
58+
@Override
59+
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
60+
Socket ret = delegate.createSocket(s, i, inetAddress, i1);
61+
ret.setKeepAlive(true);
62+
return ret;
63+
}
64+
65+
@Override
66+
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
67+
Socket ret = delegate.createSocket(inetAddress, i);
68+
ret.setKeepAlive(true);
69+
return ret;
70+
}
71+
72+
@Override
73+
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
74+
Socket ret = delegate.createSocket(inetAddress, i, inetAddress1, i1);
75+
ret.setKeepAlive(true);
76+
return ret;
77+
}
78+
}

0 commit comments

Comments
 (0)