Skip to content

Commit f9f70f2

Browse files
authored
Added per-request namespace capability (on-premises only). (#51)
Added per-request namespace capability (on-premises only). Also modified internal timing to use nanoTime() instead of currentTimeMillis(), which can go backwards in specific instances, and made per-request-iteration timeout values more accurate.
1 parent c85e77a commit f9f70f2

21 files changed

+465
-90
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
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+
## [Unreleased]
6+
7+
### Added
8+
- On-premises only: added support for setting namespace on a per-request basis
9+
10+
### Fixed
11+
- Internal: changed to use nanoTime() instead of currentTimeMillis() to avoid
12+
possible issue if system clock rolls backwards
13+
- Additional internal per-request-iteration timeout corrections
14+
515
## [5.4.9] 2023-02-14
616

717
### Added

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,6 @@ public void delay(Request request,
9898
* do not delay at all.
9999
*/
100100
public static int computeBackoffDelay(Request request, int fixedDelayMs) {
101-
int timeoutMs = request.getTimeoutInternal();
102-
long startTimeMs = request.getStartTimeMs();
103-
104101
int delayMs = fixedDelayMs;
105102
if (delayMs == 0) {
106103
/* add 200ms plus a small random amount */
@@ -114,10 +111,12 @@ public static int computeBackoffDelay(Request request, int fixedDelayMs) {
114111
* if the delay would put us over the timeout, reduce it to just before
115112
* the timeout would occur.
116113
*/
117-
long nowMs = System.currentTimeMillis();
118-
long msLeft = (startTimeMs + (long)timeoutMs) - nowMs;
119-
if ((int)msLeft < delayMs) {
120-
delayMs = (int)msLeft;
114+
long nanosUsed = System.nanoTime() - request.getStartNanos();
115+
int msUsed = Math.toIntExact(nanosUsed / 1_000_000);
116+
int timeoutMs = request.getTimeoutInternal();
117+
int msLeft = (timeoutMs - msUsed) - 1;
118+
if (msLeft < delayMs) {
119+
delayMs = msLeft;
121120
if (delayMs < 1) {
122121
return 0;
123122
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public class NoSQLHandleConfig implements Cloneable {
205205
private String compartment;
206206

207207
/**
208-
* On-premise only.
208+
* On-premises only.
209209
*
210210
* The default namespace to use for all requests. If this is null (the
211211
* default), no namespace is used unless specified in table names in
@@ -1087,9 +1087,9 @@ public String getDefaultCompartment() {
10871087
}
10881088

10891089
/**
1090-
* @hidden
1090+
* Set the default namespace to use for the handle.
10911091
*
1092-
* On-premise only.
1092+
* On-premises only.
10931093
*
10941094
* Sets the default namespace to use for requests sent using the
10951095
* handle. This is an optional convenience method to avoid having to
@@ -1098,23 +1098,30 @@ public String getDefaultCompartment() {
10981098
* Any non-namespace qualified table name in requests and/or SQL
10991099
* statements will be resolved/qualified to the specified namespace.
11001100
*
1101+
* This value can be overridden on a per-request basis by calling
1102+
* setNamespace() on individual requests.
1103+
*
11011104
* @param defaultNamespace the default namespace to use
11021105
*
11031106
* @return this
1107+
*
1108+
* @since 5.4.10
11041109
*/
11051110
public NoSQLHandleConfig setDefaultNamespace(String defaultNamespace) {
11061111
this.defaultNamespace = defaultNamespace;
11071112
return this;
11081113
}
11091114

11101115
/**
1111-
* @hidden
1116+
* Get the default namespace.
11121117
*
1113-
* On-premise only.
1118+
* On-premises only.
11141119
*
11151120
* Returns the default namespace to use for requests or null if not set.
11161121
*
11171122
* @return the default namespace
1123+
*
1124+
* @since 5.4.10
11181125
*/
11191126
public String getDefaultNamespace() {
11201127
return defaultNamespace;

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

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -470,16 +470,16 @@ public Result execute(Request kvRequest) {
470470
}
471471
}
472472

473-
final long startTime = System.currentTimeMillis();
474-
kvRequest.setStartTimeMs(startTime);
473+
final long startNanos = System.nanoTime();
474+
kvRequest.setStartNanos(startNanos);
475475
final String requestClass = kvRequest.getClass().getSimpleName();
476476

477477
String requestId = "";
478478
int thisIterationTimeoutMs = 0;
479479

480480
do {
481-
long thisTime = System.currentTimeMillis();
482-
thisIterationTimeoutMs = timeoutMs - (int)(thisTime - startTime);
481+
thisIterationTimeoutMs =
482+
getIterationTimeoutMs(timeoutMs, startNanos);
483483

484484
/*
485485
* Check rate limiters before executing the request.
@@ -513,15 +513,15 @@ public Result execute(Request kvRequest) {
513513
}
514514
}
515515

516+
/* update iteration timeout in case limiters slept for some time */
517+
thisIterationTimeoutMs =
518+
getIterationTimeoutMs(timeoutMs, startNanos);
519+
516520
/* ensure limiting didn't throw us over the timeout */
517-
if (timeoutRequest(startTime, timeoutMs, exception)) {
521+
if (thisIterationTimeoutMs <= 0) {
518522
break;
519523
}
520524

521-
/* update iteration timeout in case limiters slept for some time */
522-
thisTime = System.currentTimeMillis();
523-
thisIterationTimeoutMs = timeoutMs - (int)(thisTime - startTime);
524-
525525
final String authString =
526526
authProvider.getAuthorizationString(kvRequest);
527527
authProvider.validateAuthString(authString);
@@ -550,7 +550,6 @@ public Result execute(Request kvRequest) {
550550
ResponseHandler responseHandler = null;
551551
short serialVersionUsed = serialVersion;
552552
ByteBuf buffer = null;
553-
long networkLatency;
554553
try {
555554
/*
556555
* NOTE: the ResponseHandler will release the Channel
@@ -559,6 +558,14 @@ public Result execute(Request kvRequest) {
559558
* operations in the loop.
560559
*/
561560
Channel channel = httpClient.getChannel(thisIterationTimeoutMs);
561+
/* update iteration timeout in case channel took some time */
562+
thisIterationTimeoutMs =
563+
getIterationTimeoutMs(timeoutMs, startNanos);
564+
/* ensure limiting didn't throw us over the timeout */
565+
if (thisIterationTimeoutMs <= 0) {
566+
break;
567+
}
568+
562569
requestId = Long.toString(nextRequestId());
563570
responseHandler =
564571
new ResponseHandler(httpClient, logger, channel,
@@ -573,10 +580,16 @@ public Result execute(Request kvRequest) {
573580
*/
574581
kvRequest.setCheckRequestSize(false);
575582

576-
/* update timeout in request to match this iteration timeout */
583+
/*
584+
* Temporarily change the timeout in the request object so
585+
* the serialized timeout sent to the server is correct for
586+
* this iteration. After serializing the request, set the
587+
* timeout back to the overall request timeout so that other
588+
* processing (retry delays, etc) work correctly.
589+
*/
577590
kvRequest.setTimeoutInternal(thisIterationTimeoutMs);
578-
579591
serialVersionUsed = writeContent(buffer, kvRequest);
592+
kvRequest.setTimeoutInternal(timeoutMs);
580593

581594
/*
582595
* If on-premises the authProvider will always be a
@@ -627,17 +640,20 @@ public Result execute(Request kvRequest) {
627640
}
628641
authProvider.setRequiredHeaders(authString, kvRequest, headers);
629642

630-
if (config.getDefaultNamespace() != null) {
631-
headers.add(REQUEST_NAMESPACE_HEADER,
632-
config.getDefaultNamespace());
643+
String namespace = kvRequest.getNamespace();
644+
if (namespace == null) {
645+
namespace = config.getDefaultNamespace();
646+
}
647+
if (namespace != null) {
648+
headers.add(REQUEST_NAMESPACE_HEADER, namespace);
633649
}
634650

635651
if (isLoggable(logger, Level.FINE) &&
636652
!kvRequest.getIsRefresh()) {
637653
logTrace(logger, "Request: " + requestClass +
638654
", requestId=" + requestId);
639655
}
640-
networkLatency = System.currentTimeMillis();
656+
long latencyNanos = System.nanoTime();
641657
httpClient.runRequest(request, responseHandler, channel);
642658

643659
boolean isTimeout =
@@ -663,7 +679,9 @@ public Result execute(Request kvRequest) {
663679
rateDelayedMs += getRateDelayedFromHeader(
664680
responseHandler.getHeaders());
665681
int resSize = wireContent.readerIndex();
666-
networkLatency = System.currentTimeMillis() - networkLatency;
682+
long networkLatency =
683+
(System.nanoTime() - latencyNanos) / 1_000_000;
684+
667685

668686
if (serialVersionUsed < 3) {
669687
/* so we can emit a one-time message if the app */
@@ -813,7 +831,8 @@ public Result execute(Request kvRequest) {
813831
} catch (UnsupportedProtocolException upe) {
814832
/* reduce protocol version and try again */
815833
if (decrementSerialVersion(serialVersionUsed) == true) {
816-
exception = upe;
834+
/* Don't set this exception: it's misleading */
835+
/* exception = upe; */
817836
logFine(logger, "Got unsupported protocol error " +
818837
"from server: decrementing serial version to " +
819838
serialVersion + " and trying again.");
@@ -900,7 +919,7 @@ public Result execute(Request kvRequest) {
900919
responseHandler.close();
901920
}
902921
}
903-
} while (! timeoutRequest(startTime, timeoutMs, exception));
922+
} while (! timeoutRequest(startNanos, timeoutMs, exception));
904923

905924
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
906925
statsControl.observeError(kvRequest);
@@ -912,6 +931,18 @@ public Result execute(Request kvRequest) {
912931
kvRequest.getRetryStats() : ""), exception);
913932
}
914933

934+
/**
935+
* Calculate the timeout for the next iteration.
936+
* This is basically the given timeout minus the time
937+
* elapsed since the start of the request processing.
938+
* If this returns zero or negative, the request should be
939+
* aborted with a timeout exception.
940+
*/
941+
private int getIterationTimeoutMs(long timeoutMs, long startNanos) {
942+
long diffNanos = System.nanoTime() - startNanos;
943+
return ((int)timeoutMs - Math.toIntExact(diffNanos / 1_000_000));
944+
}
945+
915946
/**
916947
* Returns a rate limiter for a query operation, if the query op has
917948
* a prepared statement and a limiter exists in the rate limiter map
@@ -1046,18 +1077,18 @@ public boolean updateRateLimiters(String tableName, TableLimits limits) {
10461077

10471078
/**
10481079
* Determine if the request should be timed out.
1049-
* Check if the request exceed the timeout given.
1080+
* Check if the request exceeds the timeout given.
10501081
*
1051-
* @param startTime when the request starts
1052-
* @param requestTimeout the default timeout of this request
1082+
* @param startNanos when the request starts
1083+
* @param requestTimeoutMs the timeout of this request in ms
10531084
* @param exception the last exception
10541085
*
1055-
* @return true the request need to be timed out.
1086+
* @return true if the request needs to be timed out.
10561087
*/
1057-
boolean timeoutRequest(long startTime,
1058-
long requestTimeout,
1088+
boolean timeoutRequest(long startNanos,
1089+
long requestTimeoutMs,
10591090
Throwable exception) {
1060-
return ((System.currentTimeMillis() - startTime) >= requestTimeout);
1091+
return (getIterationTimeoutMs(requestTimeoutMs, startNanos) <= 0);
10611092
}
10621093

10631094
/**

driver/src/main/java/oracle/nosql/driver/ops/DeleteRequest.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ public DeleteRequest setReturnRow(boolean value) {
211211

212212
/**
213213
* Sets the optional request timeout value, in milliseconds. This overrides
214-
* any default value set in {@link NoSQLHandleConfig}. The value must be
215-
* positive.
214+
* any default value set with {@link NoSQLHandleConfig#setRequestTimeout}.
215+
* The value must be positive.
216216
*
217217
* @param timeoutMs the timeout value, in milliseconds
218218
*
@@ -226,6 +226,27 @@ public DeleteRequest setTimeout(int timeoutMs) {
226226
return this;
227227
}
228228

229+
/**
230+
* Sets the optional namespace.
231+
* On-premises only.
232+
*
233+
* This overrides any default value set with
234+
* {@link NoSQLHandleConfig#setDefaultNamespace}.
235+
* Note: if a namespace is specified in the table name for the request
236+
* (using the namespace:tablename format), that value will override this
237+
* setting.
238+
*
239+
* @param namespace the namespace to use for the operation
240+
*
241+
* @return this
242+
*
243+
* @since 5.4.10
244+
*/
245+
public DeleteRequest setNamespace(String namespace) {
246+
super.setNamespaceInternal(namespace);
247+
return this;
248+
}
249+
229250
/**
230251
* @hidden
231252
*/

driver/src/main/java/oracle/nosql/driver/ops/GetIndexesRequest.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public String getIndexName() {
8787

8888
/**
8989
* Sets the request timeout value, in milliseconds. This overrides any
90-
* default value set in {@link NoSQLHandleConfig}. The value must be
91-
* positive.
90+
* default value set with {@link NoSQLHandleConfig#setRequestTimeout}.
91+
* The value must be positive.
9292
*
9393
* @param timeoutMs the timeout value, in milliseconds
9494
*
@@ -102,6 +102,27 @@ public GetIndexesRequest setTimeout(int timeoutMs) {
102102
return this;
103103
}
104104

105+
/**
106+
* Sets the optional namespace.
107+
* On-premises only.
108+
*
109+
* This overrides any default value set with
110+
* {@link NoSQLHandleConfig#setDefaultNamespace}.
111+
* Note: if a namespace is specified in the table name for the request
112+
* (using the namespace:tablename format), that value will override this
113+
* setting.
114+
*
115+
* @param namespace the namespace to use for the operation
116+
*
117+
* @return this
118+
*
119+
* @since 5.4.10
120+
*/
121+
public GetIndexesRequest setNamespace(String namespace) {
122+
super.setNamespaceInternal(namespace);
123+
return this;
124+
}
125+
105126
/**
106127
* @hidden
107128
*/

driver/src/main/java/oracle/nosql/driver/ops/GetRequest.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ public GetRequest setConsistency(Consistency consistency) {
132132

133133
/**
134134
* Sets the request timeout value, in milliseconds. This overrides any
135-
* default value set in {@link NoSQLHandleConfig}. The value must be
136-
* positive.
135+
* default value set with {@link NoSQLHandleConfig#setRequestTimeout}.
136+
* The value must be positive.
137137
*
138138
* @param timeoutMs the timeout value, in milliseconds
139139
*
@@ -147,6 +147,27 @@ public GetRequest setTimeout(int timeoutMs) {
147147
return this;
148148
}
149149

150+
/**
151+
* Sets the optional namespace.
152+
* On-premises only.
153+
*
154+
* This overrides any default value set with
155+
* {@link NoSQLHandleConfig#setDefaultNamespace}.
156+
* Note: if a namespace is specified in the table name for the request
157+
* (using the namespace:tablename format), that value will override this
158+
* setting.
159+
*
160+
* @param namespace the namespace to use for the operation
161+
*
162+
* @return this
163+
*
164+
* @since 5.4.10
165+
*/
166+
public GetRequest setNamespace(String namespace) {
167+
super.setNamespaceInternal(namespace);
168+
return this;
169+
}
170+
150171
/**
151172
* @hidden
152173
* Internal use only

0 commit comments

Comments
 (0)