// Copyright 2017 JanusGraph Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.janusgraph.diskstorage.util.time;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementations of {@link TimestampProvider} for different resolutions of time:
* <ul>
* <li>NANO: nano-second time resolution based on System.nanoTime using a base-time established
* by System.currentTimeMillis(). The exact resolution depends on the particular JVM and host machine.</li>
* <li>MICRO: micro-second time which is actually at milli-second resolution.</li>
* <li>MILLI: milli-second time resolution</li>
* </ul>
*/
public enum TimestampProviders implements TimestampProvider {
NANO {
/**
* This returns the approximate number of nanoseconds
* elapsed since the UNIX Epoch. The least significant
* bit is overridden to 1 or 0 depending on whether
* setLSB is true or false (respectively).
* <p/>
* This timestamp rolls over about every 2^63 ns, or
* just over 292 years. The first rollover starting
* from the UNIX Epoch would be sometime in 2262.
*
* @return a timestamp as described above
*/
@Override
public Instant getTime() {
return Instant.now();
}
@Override
public Instant getTime(long sinceEpoch) {
return Instant.ofEpochSecond(0, sinceEpoch);
}
@Override
public ChronoUnit getUnit() {
return ChronoUnit.NANOS;
}
@Override
public long getTime(Instant timestamp) {
return timestamp.getEpochSecond() * 1000000000L + timestamp.getNano();
}
},
MICRO {
@Override
public Instant getTime() {
return Instant.now();
}
@Override
public Instant getTime(long sinceEpoch) {
return Instant.ofEpochSecond(0, (sinceEpoch * 1000L));
}
@Override
public ChronoUnit getUnit() {
return ChronoUnit.MICROS;
}
@Override
public long getTime(Instant timestamp) {
return timestamp.getEpochSecond() * 1000000L + timestamp.getNano()/1000;
}
},
MILLI {
@Override
public Instant getTime() {
return Instant.now();
}
@Override
public Instant getTime(long sinceEpoch) {
return Instant.ofEpochMilli(sinceEpoch);
}
@Override
public ChronoUnit getUnit() {
return ChronoUnit.MILLIS;
}
@Override
public long getTime(Instant timestamp) {
return timestamp.getEpochSecond() * 1000 + timestamp.getNano() / 1000000;
}
};
private static final Logger log =
LoggerFactory.getLogger(TimestampProviders.class);
@Override
public Instant sleepPast(Instant futureTime) throws InterruptedException {
Instant now;
ChronoUnit unit = getUnit();
/*
* Distributed storage managers that rely on timestamps play with the
* least significant bit in timestamp longs, turning it on or off to
* ensure that deletions are logically ordered before additions within a
* single batch mutation. This is not a problem at microsecond
* resolution because we pretendulate microsecond resolution by
* multiplying currentTimeMillis by 1000, so the LSB can vary freely.
* It's also not a problem with nanosecond resolution because the
* resolution is just too fine, relative to how long a mutation takes,
* for it to matter in practice. But it can lead to corruption at
* millisecond resolution (and does, in testing).
*/
if (unit.equals(ChronoUnit.MILLIS))
futureTime = futureTime.plusMillis(1L);
while ((now = getTime()).compareTo(futureTime) <= 0) {
long delta = getTime(futureTime) - getTime(now);
if (0L == delta)
delta = 1L;
if (log.isTraceEnabled()) {
log.trace("Sleeping: now={} targettime={} delta={} {}",
new Object[] { now, futureTime, delta, unit });
}
Temporals.timeUnit(unit).sleep(delta);
}
return now;
}
@Override
public void sleepFor(Duration duration) throws InterruptedException {
if (duration.isZero()) return;
TimeUnit.NANOSECONDS.sleep(duration.toNanos());
}
@Override
public Timer getTimer() {
return new Timer(this);
}
@Override
public String toString() {
return name();
}
}