Skip to content

Commit 3a02229

Browse files
committed
Enhanced control and logging of SSL handshake process
Fixed a possible unexpected Cloud service latency path
1 parent 884ad68 commit 3a02229

22 files changed

+524
-397
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
All notable changes to this project will be documented in this file.
33
The format is based on [Keep a Changelog](http://keepachangelog.com/).
44

5+
## [Unpublished]
6+
7+
### Added
8+
- Added new methods in NoSQLHandleConfig to control the SSL handshake timeout,
9+
default 3000 milliseconds.
10+
- get/setSslHandshakeTimeout
11+
12+
### Fixed
13+
- Cloud only: Fixed an issue that a request may have unexpected latency when
14+
authenticated with instance/resource principal due to security token refresh.
15+
516
## [5.3.1] 2022-02-17
617

718
See also sections on 5.2.30 and 5.2.31 as they were not released publicly. The last public release was 5.2.29.

driver/.classpath

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
<classpathentry kind="output" path="target/classes"/>
66
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
77
<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.12.1/jackson-core-2.12.1.jar"/>
8-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-buffer/4.1.52.Final/netty-buffer-4.1.52.Final.jar"/>
9-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-common/4.1.52.Final/netty-common-4.1.52.Final.jar"/>
10-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec-http/4.1.52.Final/netty-codec-http-4.1.52.Final.jar"/>
11-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-transport/4.1.52.Final/netty-transport-4.1.52.Final.jar"/>
12-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-resolver/4.1.52.Final/netty-resolver-4.1.52.Final.jar"/>
13-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec/4.1.52.Final/netty-codec-4.1.52.Final.jar"/>
14-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-handler/4.1.52.Final/netty-handler-4.1.52.Final.jar"/>
15-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-handler-proxy/4.1.52.Final/netty-handler-proxy-4.1.52.Final.jar"/>
16-
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec-socks/4.1.52.Final/netty-codec-socks-4.1.52.Final.jar"/>
8+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-buffer/4.1.71.Final/netty-buffer-4.1.71.Final.jar"/>
9+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-common/4.1.71.Final/netty-common-4.1.71.Final.jar"/>
10+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec-http/4.1.71.Final/netty-codec-http-4.1.71.Final.jar"/>
11+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-transport/4.1.71.Final/netty-transport-4.1.71.Final.jar"/>
12+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-resolver/4.1.71.Final/netty-resolver-4.1.71.Final.jar"/>
13+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec/4.1.71.Final/netty-codec-4.1.71.Final.jar"/>
14+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-handler/4.1.71.Final/netty-handler-4.1.71.Final.jar"/>
15+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-handler-proxy/4.1.71.Final/netty-handler-proxy-4.1.71.Final.jar"/>
16+
<classpathentry kind="var" path="M2_REPO/io/netty/netty-codec-socks/4.1.71.Final/netty-codec-socks-4.1.71.Final.jar"/>
1717
<classpathentry kind="var" path="M2_REPO/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68.jar"/>
1818
<classpathentry kind="var" path="M2_REPO/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68.jar"/>
19-
<classpathentry kind="var" path="M2_REPO/junit/junit/4.12/junit-4.12.jar"/>
19+
<classpathentry kind="var" path="M2_REPO/junit/junit/4.13.1/junit-4.13.1.jar"/>
2020
<classpathentry kind="var" path="M2_REPO/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"/>
2121
</classpath>

driver/src/main/java/oracle/nosql/driver/DefaultRetryHandler.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,23 @@ public int getNumRetries() {
4646
* Decide whether to retry or not.
4747
* Default behavior is to *not* retry OperationThrottlingException
4848
* because the retry time is likely much longer than normal because
49-
* they are DDL operations. In addition, *not* retry any requests that
50-
* should not be retried: TableRequest, ListTablesRequest,
51-
* GetTableRequest, TableUsageRequest, GetIndexesRequest.
49+
* they are DDL operations. Read and Write throttling exceptions are
50+
* always retryable. Otherwise check the request itself to see if
51+
* it should not be retried.
5252
*/
5353
@Override
5454
public boolean doRetry(Request request,
5555
int numRetries,
5656
RetryableException re) {
5757
if (re instanceof OperationThrottlingException) {
5858
return false;
59-
} else if (!request.shouldRetry()) {
60-
return false;
6159
}
62-
return (numRetries < maxRetries);
60+
if (re instanceof ReadThrottlingException ||
61+
re instanceof WriteThrottlingException ||
62+
request.shouldRetry()) {
63+
return (numRetries < maxRetries);
64+
}
65+
return false;
6366
}
6467

6568
/**
@@ -100,15 +103,17 @@ public static int computeBackoffDelay(Request request, int fixedDelayMs) {
100103

101104
int delayMs = fixedDelayMs;
102105
if (delayMs == 0) {
103-
// add 200ms plus a small random amount
106+
/* add 200ms plus a small random amount */
104107
int mSecToAdd = 200 + (int)(Math.random() * 50);
105108

106109
delayMs = request.getRetryDelayMs();
107110
delayMs += mSecToAdd;
108111
}
109112

110-
// if the delay would put us over the timeout, reduce it to just before
111-
// the timeout would occur.
113+
/*
114+
* if the delay would put us over the timeout, reduce it to just before
115+
* the timeout would occur.
116+
*/
112117
long nowMs = System.currentTimeMillis();
113118
long msLeft = (startTimeMs + (long)timeoutMs) - nowMs;
114119
if ((int)msLeft < delayMs) {

driver/src/main/java/oracle/nosql/driver/NoSQLHandleConfig.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ public class NoSQLHandleConfig implements Cloneable {
167167
*/
168168
private int sslSessionTimeout = 0;
169169

170+
/**
171+
* The timeout limit of SSH handshake, or 0 if not configured by
172+
* the user.
173+
*/
174+
private int sslHandshakeTimeoutMs = 0;
175+
170176
/**
171177
* Cloud service only.
172178
*
@@ -1022,6 +1028,16 @@ public int getSSLSessionCacheSize() {
10221028
return sslSessionCacheSize;
10231029
}
10241030

1031+
/**
1032+
* Returns the configured SSL handshake timeout, in milliseconds.
1033+
*
1034+
* @return the timeout, in milliseconds, or 0 if it has not been set
1035+
* @since 5.3.2
1036+
*/
1037+
public int getSSLHandshakeTimeout() {
1038+
return sslHandshakeTimeoutMs;
1039+
}
1040+
10251041
/**
10261042
* Set SSL cipher suites to enable, in the order of preference. null to
10271043
* use default cipher suites.
@@ -1081,7 +1097,7 @@ public NoSQLHandleConfig setSSLSessionCacheSize(int cacheSize) {
10811097
}
10821098

10831099
/**
1084-
* Set the timeout for the cached SSL session objects, in seconds. 0 to
1100+
* Sets the timeout for the cached SSL session objects, in seconds. 0 to
10851101
* use the default value, no limit. When the timeout limit is exceeded for
10861102
* a session, the SSLSession object is invalidated and future connections
10871103
* cannot resume or rejoin the session.
@@ -1100,6 +1116,27 @@ public NoSQLHandleConfig setSSLSessionTimeout(int timeout) {
11001116
return this;
11011117
}
11021118

1119+
/**
1120+
* Sets the timeout for the SSL handshake, in milliseconds. 0 to use the
1121+
* default value, 3000 milliseconds. In general the default works. This
1122+
* value can be set to help debug suspected SSL issues and force
1123+
* retries within the request timeout period.
1124+
*
1125+
* @param timeout the SSL handshake timeout
1126+
*
1127+
* @return this
1128+
* @since 5.3.2
1129+
*/
1130+
public NoSQLHandleConfig setSSLHandshakeTimeout(int timeout) {
1131+
if (timeout < 0) {
1132+
throw new IllegalArgumentException(
1133+
"NoSQLHandleConfig.setSSLHandshakeTimeout: timeout must " +
1134+
"be a positive value or zero");
1135+
}
1136+
this.sslHandshakeTimeoutMs = timeout;
1137+
return this;
1138+
}
1139+
11031140
/**
11041141
* Sets an HTTP proxy host to be used for the session. If a proxy host
11051142
* is specified a proxy port must also be specified, using

driver/src/main/java/oracle/nosql/driver/http/Client.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ public Client(Logger logger,
215215
httpConfig.getMaxContentLength(),
216216
httpConfig.getMaxChunkSize(),
217217
sslCtx,
218+
config.getSSLHandshakeTimeout(),
218219
"NoSQL Driver",
219220
logger);
220221
if (httpConfig.getProxyHost() != null) {
@@ -501,8 +502,8 @@ public Result execute(Request kvRequest) {
501502
Channel channel = httpClient.getChannel(thisIterationTimeoutMs);
502503
requestId = Long.toString(nextRequestId());
503504
responseHandler =
504-
new ResponseHandler(httpClient, logger, channel, requestId);
505-
505+
new ResponseHandler(httpClient, logger, channel,
506+
requestId, kvRequest.shouldRetry());
506507
buffer = channel.alloc().directBuffer();
507508
buffer.retain();
508509

driver/src/main/java/oracle/nosql/driver/http/NoSQLHandleImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,16 @@ private void configAuthProvider(Logger logger, NoSQLHandleConfig config) {
144144
endpoint = endpoint.substring(0, endpoint.length() - 1);
145145
}
146146
stProvider.setEndpoint(endpoint)
147-
.setSslContext(config.getSslContext());
147+
.setSslContext(config.getSslContext())
148+
.setSslHandshakeTimeout(
149+
config.getSSLHandshakeTimeout());
148150
}
149151
} else if (ap instanceof SignatureProvider) {
150152
SignatureProvider sigProvider = (SignatureProvider) ap;
151-
sigProvider.prepare(config);
152153
if (sigProvider.getLogger() == null) {
153154
sigProvider.setLogger(logger);
154155
}
156+
sigProvider.prepare(config);
155157
}
156158
}
157159

driver/src/main/java/oracle/nosql/driver/httpclient/HttpClient.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class HttpClient {
7878
static final int DEFAULT_MAX_PENDING = 3;
7979
static final int DEFAULT_MAX_CONTENT_LENGTH = 32 * 1024 * 1024; // 32MB
8080
static final int DEFAULT_MAX_CHUNK_SIZE = 65536;
81+
static final int DEFAULT_HANDSHAKE_TIMEOUT_MS = 3000;
8182
/*
8283
* timeout for acquiring a Netty channel in ms. If exceeded a new
8384
* connection is created
@@ -107,6 +108,7 @@ public class HttpClient {
107108
* Non-null if using SSL
108109
*/
109110
private final SslContext sslCtx;
111+
private final int handshakeTimeoutMs;
110112

111113
private final Logger logger;
112114

@@ -136,6 +138,7 @@ public class HttpClient {
136138
* @param connectionPoolSize the max number of HTTP connections to use
137139
* for concurrent requests. If 0, a default value is used (2)
138140
* @param sslCtx if non-null, SSL context to use for connections.
141+
* @param handshakeTimeoutMs if not zero, timeout to use for SSL handshake
139142
* @param name A name to use in logging messages for this client.
140143
* @param logger A logger to use for logging messages.
141144
*/
@@ -145,11 +148,12 @@ public HttpClient(String host,
145148
int connectionPoolSize,
146149
int poolMaxPending,
147150
SslContext sslCtx,
151+
int handshakeTimeoutMs,
148152
String name,
149153
Logger logger) {
150154
this(host, port, numThreads, connectionPoolSize, poolMaxPending,
151155
DEFAULT_MAX_CONTENT_LENGTH, DEFAULT_MAX_CHUNK_SIZE,
152-
sslCtx, name, logger);
156+
sslCtx, handshakeTimeoutMs, name, logger);
153157
}
154158

155159
/**
@@ -169,6 +173,7 @@ public HttpClient(String host,
169173
* @param maxChunkSize maximum size in bytes of chunked response messages.
170174
* If 0, a default value is used (64KB).
171175
* @param sslCtx if non-null, SSL context to use for connections.
176+
* @param handshakeTimeoutMs if not zero, timeout to use for SSL handshake
172177
* @param name A name to use in logging messages for this client.
173178
* @param logger A logger to use for logging messages.
174179
*/
@@ -180,6 +185,7 @@ public HttpClient(String host,
180185
int maxContentLength,
181186
int maxChunkSize,
182187
SslContext sslCtx,
188+
int handshakeTimeoutMs,
183189
String name,
184190
Logger logger) {
185191

@@ -190,12 +196,15 @@ public HttpClient(String host,
190196
this.name = name;
191197

192198
poolMaxPending = (poolMaxPending == 0 ?
193-
DEFAULT_MAX_PENDING : poolMaxPending);
199+
DEFAULT_MAX_PENDING : poolMaxPending);
194200

195201
this.maxContentLength = (maxContentLength == 0 ?
196-
DEFAULT_MAX_CONTENT_LENGTH : maxContentLength);
202+
DEFAULT_MAX_CONTENT_LENGTH : maxContentLength);
197203
this.maxChunkSize = (maxChunkSize == 0 ?
198-
DEFAULT_MAX_CHUNK_SIZE : maxChunkSize);
204+
DEFAULT_MAX_CHUNK_SIZE : maxChunkSize);
205+
206+
this.handshakeTimeoutMs = (handshakeTimeoutMs == 0 ?
207+
DEFAULT_HANDSHAKE_TIMEOUT_MS : handshakeTimeoutMs);
199208

200209
int cores = Runtime.getRuntime().availableProcessors();
201210

@@ -258,6 +267,10 @@ Logger getLogger() {
258267
return logger;
259268
}
260269

270+
int getHandshakeTimeoutMs() {
271+
return handshakeTimeoutMs;
272+
}
273+
261274
public int getMaxContentLength() {
262275
return maxContentLength;
263276
}
@@ -340,8 +353,9 @@ public Channel getChannel(int timeoutMs)
340353
retChan = fut.get(thisTimeoutMs, TimeUnit.MILLISECONDS);
341354
} catch (TimeoutException e) {
342355
if (retries == 0) {
343-
logInfo(logger, "Timed out after " + msDiff +
344-
"ms trying to acquire channel: retrying");
356+
logInfo(logger, "Timed out after " +
357+
(System.currentTimeMillis() - startMs) +
358+
"ms trying to acquire channel: retrying");
345359
}
346360
/* fall through */
347361
}

driver/src/main/java/oracle/nosql/driver/httpclient/HttpClientChannelPoolHandler.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
package oracle.nosql.driver.httpclient;
99

1010
import static oracle.nosql.driver.util.LogUtil.logFine;
11-
import static oracle.nosql.driver.util.LogUtil.logInfo;
12-
import java.net.InetSocketAddress;
1311

12+
import java.net.InetSocketAddress;
1413
import javax.net.ssl.SSLEngine;
1514
import javax.net.ssl.SSLParameters;
1615

1716
import io.netty.channel.Channel;
1817
import io.netty.channel.ChannelHandler.Sharable;
18+
import io.netty.channel.ChannelHandlerContext;
19+
import io.netty.channel.ChannelInboundHandlerAdapter;
1920
import io.netty.channel.ChannelPipeline;
2021
import io.netty.channel.EventLoop;
2122
import io.netty.channel.pool.ChannelHealthChecker;
@@ -24,6 +25,7 @@
2425
import io.netty.handler.codec.http.HttpObjectAggregator;
2526
import io.netty.handler.proxy.HttpProxyHandler;
2627
import io.netty.handler.ssl.SslHandler;
28+
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
2729
import io.netty.util.concurrent.Future;
2830

2931
/**
@@ -74,7 +76,10 @@ public void channelCreated(Channel ch) {
7476
final SSLParameters sslParameters = sslEngine.getSSLParameters();
7577
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
7678
sslEngine.setSSLParameters(sslParameters);
79+
sslHandler.setHandshakeTimeoutMillis(client.getHandshakeTimeoutMs());
80+
7781
p.addLast(sslHandler);
82+
p.addLast(new ChannelLoggingHandler(client));
7883
}
7984
p.addLast(CODEC_HANDLER_NAME, new HttpClientCodec
8085
(4096, // initial line
@@ -125,4 +130,46 @@ public Future<Boolean> isHealthy(Channel channel) {
125130
return val? loop.newSucceededFuture(Boolean.TRUE) :
126131
loop.newSucceededFuture(Boolean.FALSE);
127132
}
133+
134+
private static class ChannelLoggingHandler
135+
extends ChannelInboundHandlerAdapter {
136+
137+
private HttpClient client;
138+
139+
ChannelLoggingHandler(HttpClient client) {
140+
this.client = client;
141+
}
142+
143+
@Override
144+
public void channelActive(ChannelHandlerContext ctx) {
145+
logFine(client.getLogger(),
146+
"HttpClient " + client.getName() +
147+
", channel " + ctx.channel() + " connected");
148+
ctx.fireChannelActive();
149+
}
150+
151+
@Override
152+
public void channelInactive(ChannelHandlerContext ctx) {
153+
logFine(client.getLogger(),
154+
"HttpClient " + client.getName() +
155+
", channel " + ctx.channel() + " inactive");
156+
ctx.fireChannelInactive();
157+
}
158+
159+
@Override
160+
public void userEventTriggered(ChannelHandlerContext ctx,
161+
Object evt)
162+
throws Exception {
163+
164+
if (evt instanceof SslHandshakeCompletionEvent) {
165+
if (!((SslHandshakeCompletionEvent) evt).isSuccess()) {
166+
logFine(client.getLogger(),
167+
"HttpClient " + client.getName() +
168+
", channel: " + ctx.channel() +
169+
" handshake failed: " + evt);
170+
}
171+
}
172+
ctx.fireUserEventTriggered(evt);
173+
}
174+
}
128175
}

0 commit comments

Comments
 (0)