Skip to content

Commit b9d744a

Browse files
Alexandre Dutraolim7t
authored andcommitted
JAVA-727: Allow monotonic timestamp generators to drift in the future + use microsecond precision when possible.
(cherry-picked from 2.1) This commit introduces a new dependency to JNR, and uses the system call 'gettimeofday' when available to generate timestamps with microsecond precision.
1 parent 39e4f0d commit b9d744a

15 files changed

Lines changed: 616 additions & 132 deletions

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Merged from 2.1 branch:
5353
- [improvement] JAVA-888: Add cluster-wide percentile tracker.
5454
- [improvement] JAVA-963: Automatically register PercentileTracker from components that use it.
5555
- [new feature] JAVA-1019: SchemaBuilder support for CREATE/ALTER/DROP KEYSPACE.
56+
- [bug] JAVA-727: Allow monotonic timestamp generators to drift in the future + use microsecond precision when possible.
5657

5758

5859
### 3.0.2

driver-core/pom.xml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
limitations under the License.
1616
1717
-->
18-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
1920
<modelVersion>4.0.0</modelVersion>
2021
<parent>
2122
<groupId>com.datastax.cassandra</groupId>
@@ -53,6 +54,12 @@
5354
<version>${metrics.version}</version>
5455
</dependency>
5556

57+
<dependency>
58+
<groupId>com.github.jnr</groupId>
59+
<artifactId>jnr-ffi</artifactId>
60+
<version>${jnr-ffi.version}</version>
61+
</dependency>
62+
5663
<!-- Compression libraries for the protocol. -->
5764
<!-- Each of them is only a mandatory runtime dependency if you want to use the compression it offers -->
5865

@@ -186,7 +193,9 @@
186193
<Bundle-SymbolicName>com.datastax.driver.core</Bundle-SymbolicName>
187194
<Bundle-Version>${project.version}</Bundle-Version>
188195
<_include>-osgi.bnd</_include>
189-
<Import-Package><![CDATA[com.google.common*;version="[16.0.1,20)",*]]></Import-Package>
196+
<Import-Package>
197+
<!-- JNR does not provide OSGi bundles, so exclude it; the driver can live without it -->
198+
<![CDATA[com.google.common*;version="[14.0,20)",!jnr.*,*]]></Import-Package>
190199
</instructions>
191200
<supportedProjectTypes>
192201
<supportedProjectType>jar</supportedProjectType>
@@ -206,7 +215,11 @@
206215
<manifestLocation>${project.build.directory}/META-INF-shaded</manifestLocation>
207216
<instructions>
208217
<Import-Package>
209-
<![CDATA[com.google.common.*;version="[16.0.1,19)",!io.netty.*,javax.security.cert,*]]></Import-Package>
218+
<!--
219+
JNR does not provide OSGi bundles, so exclude it; the driver can live without it
220+
Explicitly import javax.security.cert because it's required by Netty, but Netty has been explicitly excluded
221+
-->
222+
<![CDATA[com.google.common.*;version="[14.0,19)",!jnr.*,!io.netty.*,javax.security.cert,*]]></Import-Package>
210223
<Private-Package>com.datastax.shaded.*</Private-Package>
211224
</instructions>
212225
</configuration>
@@ -239,7 +252,8 @@
239252
</relocation>
240253
</relocations>
241254
<transformers>
242-
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
255+
<transformer
256+
implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
243257
<resources>
244258
<resource>META-INF/MANIFEST.MF</resource>
245259
<resource>META-INF/io.netty.versions.properties</resource>
@@ -256,7 +270,8 @@
256270
</resources>
257271
</transformer>
258272
<!-- Pick up the alternate manifest that was generated by the alternate execution of the bundle plugin -->
259-
<transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
273+
<transformer
274+
implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
260275
<resource>META-INF/MANIFEST.MF</resource>
261276
<file>${project.build.directory}/META-INF-shaded/MANIFEST.MF</file>
262277
</transformer>
@@ -287,7 +302,7 @@
287302
</goals>
288303
</pluginExecutionFilter>
289304
<action>
290-
<ignore />
305+
<ignore/>
291306
</action>
292307
</pluginExecution>
293308
</pluginExecutions>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (C) 2012-2015 DataStax Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.datastax.driver.core;
17+
18+
import com.google.common.annotations.VisibleForTesting;
19+
20+
/**
21+
* Base implementation for monotonic timestamp generators.
22+
* <p/>
23+
* The accuracy of the generated timestamps is largely dependent on the
24+
* granularity of the underlying operating system's clock.
25+
* <p/>
26+
* Generally speaking, this granularity is millisecond, and
27+
* the sub-millisecond part is simply a counter that gets incremented
28+
* until the next clock tick, as provided by {@link System#currentTimeMillis()}.
29+
* <p/>
30+
* On some systems, however, it is possible to have a better granularity by using a JNR
31+
* call to {@code gettimeofday}. The driver will use this system call automatically whenever
32+
* available, unless the system property {@code com.datastax.driver.USE_NATIVE_CLOCK} is
33+
* explicitly set to {@code false}.
34+
* <p/>
35+
* Beware that to guarantee monotonicity, if more than one call to {@link #next()}
36+
* is made within the same microsecond, or in the event of a system clock skew, this generator might
37+
* return timestamps that drift out in the future.
38+
* Whe this happens, {@link #onDrift(long, long)} is invoked.
39+
*/
40+
public abstract class AbstractMonotonicTimestampGenerator implements TimestampGenerator {
41+
42+
@VisibleForTesting
43+
volatile Clock clock = ClockFactory.newInstance();
44+
45+
/**
46+
* Compute the next timestamp, given the last timestamp previously generated.
47+
* <p/>
48+
* To guarantee monotonicity, the next timestamp should be strictly greater than the last one.
49+
* If the underlying clock fails to generate monotonically increasing timestamps, the generator will simply
50+
* increment the previous timestamp, and {@link #onDrift(long, long)} will be invoked.
51+
* <p/>
52+
* This implementation is inspired by {@code org.apache.cassandra.service.ClientState#getTimestamp()}.
53+
*
54+
* @param last the last timestamp generated by this generator, in microseconds.
55+
* @return the next timestamp to use, in microseconds.
56+
*/
57+
protected long computeNext(long last) {
58+
long currentTick = clock.currentTimeMicros();
59+
if (last >= currentTick) {
60+
onDrift(currentTick, last);
61+
return last + 1;
62+
}
63+
return currentTick;
64+
}
65+
66+
/**
67+
* Called when generated timestamps drift into the future compared to the underlying clock (in other words, if
68+
* {@code lastTimestamp >= currentTick}).
69+
* <p/>
70+
* This could happen if timestamps are requested faster than the clock granularity, or on a clock skew (for example
71+
* because of a leap second).
72+
*
73+
* @param currentTick the current clock tick, in microseconds.
74+
* @param lastTimestamp the last timestamp that was generated, in microseconds.
75+
*/
76+
protected abstract void onDrift(long currentTick, long lastTimestamp);
77+
}

driver-core/src/main/java/com/datastax/driver/core/AbstractTimestampGenerator.java

Lines changed: 0 additions & 49 deletions
This file was deleted.

driver-core/src/main/java/com/datastax/driver/core/AtomicMonotonicTimestampGenerator.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,41 @@
1515
*/
1616
package com.datastax.driver.core;
1717

18+
import java.util.concurrent.TimeUnit;
1819
import java.util.concurrent.atomic.AtomicLong;
1920

2021
/**
21-
* A timestamp generator based on {@code System.currentTimeMillis()}, with an incrementing atomic counter
22-
* to generate the sub-millisecond part.
23-
* <p/>
24-
* This implementation guarantees incrementing timestamps among all client threads, provided that no more than
25-
* 1000 are requested for a given clock tick (the exact granularity of of {@link System#currentTimeMillis()}
26-
* depends on the operating system).
27-
* <p/>
28-
* If that rate is exceeded, a warning is logged and the timestamps don't increment anymore until the next clock
29-
* tick. If you consistently exceed that rate, consider using {@link ThreadLocalMonotonicTimestampGenerator}.
22+
* A timestamp generator that guarantees monotonically increasing timestamps among all client threads, and logs warnings
23+
* when timestamps drift in the future.
24+
*
25+
* @see AbstractMonotonicTimestampGenerator
3026
*/
31-
public class AtomicMonotonicTimestampGenerator extends AbstractMonotonicTimestampGenerator {
27+
public class AtomicMonotonicTimestampGenerator extends LoggingMonotonicTimestampGenerator {
28+
3229
private AtomicLong lastRef = new AtomicLong(0);
3330

31+
/**
32+
* Creates a new instance with a warning threshold and warning interval of one second.
33+
*
34+
* @see #AtomicMonotonicTimestampGenerator(long, TimeUnit, long, TimeUnit)
35+
*/
36+
public AtomicMonotonicTimestampGenerator() {
37+
this(1, TimeUnit.SECONDS, 1, TimeUnit.SECONDS);
38+
}
39+
40+
/**
41+
* Creates a new instance.
42+
*
43+
* @param warningThreshold how far in the future timestamps are allowed to drift before a warning is logged.
44+
* @param warningThresholdUnit the unit for {@code warningThreshold}.
45+
* @param warningInterval how often the warning will be logged if timestamps keep drifting above the threshold.
46+
* @param warningIntervalUnit the unit for {@code warningIntervalUnit}.
47+
*/
48+
public AtomicMonotonicTimestampGenerator(long warningThreshold, TimeUnit warningThresholdUnit,
49+
long warningInterval, TimeUnit warningIntervalUnit) {
50+
super(warningThreshold, warningThresholdUnit, warningInterval, warningIntervalUnit);
51+
}
52+
3453
@Override
3554
public long next() {
3655
while (true) {

driver-core/src/main/java/com/datastax/driver/core/Clock.java

Lines changed: 104 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,120 @@
1515
*/
1616
package com.datastax.driver.core;
1717

18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
import java.util.concurrent.atomic.AtomicReference;
22+
23+
import static java.util.concurrent.TimeUnit.*;
24+
1825
/**
19-
* This interface allows us not to have a direct call to {@code System.currentTimeMillis()} for testing purposes
26+
* A small abstraction around system clock that aims to provide microsecond precision with the best accuracy possible.
2027
*/
2128
interface Clock {
29+
2230
/**
23-
* Returns the current time in milliseconds
31+
* Returns the current time in microseconds.
2432
*
25-
* @return the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
26-
* @see System#currentTimeMillis()
33+
* @return the difference, measured in microseconds, between the current time and and the Epoch
34+
* (that is, midnight, January 1, 1970 UTC).
2735
*/
28-
long currentTime();
36+
long currentTimeMicros();
37+
}
38+
39+
/**
40+
* Factory that returns the best Clock implementation depending on what native libraries are available in the system.
41+
* If LibC is available through JNR, and if the system property {@code com.datastax.driver.USE_NATIVE_CLOCK} is set to {@code true}
42+
* (which is the default value), then {@link NativeClock} is returned, otherwise {@link SystemClock} is returned.
43+
*/
44+
class ClockFactory {
45+
46+
private static final Logger LOGGER = LoggerFactory.getLogger(ClockFactory.class);
47+
48+
private static final String USE_NATIVE_CLOCK_SYSTEM_PROPERTY = "com.datastax.driver.USE_NATIVE_CLOCK";
49+
50+
static Clock newInstance() {
51+
if (Native.isLibCLoaded() && SystemProperties.getBoolean(USE_NATIVE_CLOCK_SYSTEM_PROPERTY, true)) {
52+
LOGGER.info("Using native clock to generate timestamps.");
53+
return new NativeClock();
54+
} else {
55+
LOGGER.info("Using java.lang.System clock to generate timestamps.");
56+
return new SystemClock();
57+
}
58+
}
59+
2960
}
3061

3162
/**
32-
* Default implementation of a clock that delegate its calls to the system clock.
63+
* Default implementation of a clock that delegates its calls to the system clock.
64+
*
65+
* @see System#currentTimeMillis()
3366
*/
3467
class SystemClock implements Clock {
68+
3569
@Override
36-
public long currentTime() {
37-
return System.currentTimeMillis();
70+
public long currentTimeMicros() {
71+
return System.currentTimeMillis() * 1000;
3872
}
39-
}
73+
74+
}
75+
76+
/**
77+
* Provides the current time with microseconds precision with some reasonable accuracy through
78+
* the use of {@link Native#currentTimeMicros()}.
79+
* <p/>
80+
* Because calling JNR methods is slightly expensive,
81+
* we only call it once per second and add the number of nanoseconds since the last call
82+
* to get the current time, which is good enough an accuracy for our purpose (see CASSANDRA-6106).
83+
* <p/>
84+
* This reduces the cost of the call to {@link NativeClock#currentTimeMicros()} to levels comparable
85+
* to those of a call to {@link System#currentTimeMillis()}.
86+
*/
87+
class NativeClock implements Clock {
88+
89+
private static final long ONE_SECOND_NS = NANOSECONDS.convert(1, SECONDS);
90+
private static final long ONE_MILLISECOND_NS = NANOSECONDS.convert(1, MILLISECONDS);
91+
92+
/**
93+
* Records a time in micros along with the System.nanoTime() value at the time the
94+
* time is fetched.
95+
*/
96+
private static class FetchedTime {
97+
98+
private final long timeInMicros;
99+
private final long nanoTimeAtCheck;
100+
101+
private FetchedTime(long timeInMicros, long nanoTimeAtCheck) {
102+
this.timeInMicros = timeInMicros;
103+
this.nanoTimeAtCheck = nanoTimeAtCheck;
104+
}
105+
}
106+
107+
private final AtomicReference<FetchedTime> lastFetchedTime = new AtomicReference<FetchedTime>(fetchTimeMicros());
108+
109+
@Override
110+
public long currentTimeMicros() {
111+
FetchedTime spec = lastFetchedTime.get();
112+
long curNano = System.nanoTime();
113+
if (curNano > spec.nanoTimeAtCheck + ONE_SECOND_NS) {
114+
lastFetchedTime.compareAndSet(spec, spec = fetchTimeMicros());
115+
}
116+
return spec.timeInMicros + ((curNano - spec.nanoTimeAtCheck) / 1000);
117+
}
118+
119+
private static FetchedTime fetchTimeMicros() {
120+
// To compensate for the fact that the Native.currentTimeMicros call could take
121+
// some time, instead of picking the nano time before the call or after the
122+
// call, we take the average of both.
123+
long start = System.nanoTime();
124+
long micros = Native.currentTimeMicros();
125+
long end = System.nanoTime();
126+
// If it turns out the call took us more than 1 millisecond (can happen while
127+
// the JVM warms up, unlikely otherwise, but no reasons to take risks), fall back
128+
// to System.currentTimeMillis() temporarily
129+
if ((end - start) > ONE_MILLISECOND_NS)
130+
return new FetchedTime(System.currentTimeMillis() * 1000, System.nanoTime());
131+
return new FetchedTime(micros, (end + start) / 2);
132+
}
133+
134+
}

0 commit comments

Comments
 (0)