package com.xiaomi.infra.chronos;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.thrift.TException;
import com.xiaomi.infra.chronos.exception.FatalChronosException;
import com.xiaomi.infra.chronos.exception.ChronosException;
import com.xiaomi.infra.chronos.generated.ChronosService;
/**
* Implement the chronos.thrift and provide the interface to get timestamp. It will return the
* precise auto-increasing timestamp which is based on the current wall-time. Update the value in
* ZooKeeper to guarantee that the persistent timestamp is larger than any allocated value.
*/
public class ChronosImplement implements ChronosService.Iface {
private static final Log LOG = LogFactory.getLog(ChronosImplement.class);
private final ChronosServerWatcher chronosServerWatcher;
private final long zkAdvanceTimestamp;
private long maxAssignedTimestamp;
private volatile boolean isAsyncSetPersistentTimestamp = false;
/**
* Construct ChronosImplement with properties and ChronosServerWatcher.
*
* @param properties the properties of zkAdvanceTimestamp
* @param chronosServerWatcher the ZooKeeper client to set persistent timestamp
* @throws FatalChronosException when set a smaller timestamp in ZooKeeper
* @throws ChronosException when error to set value in ZooKeeper
*/
public ChronosImplement(Properties properties, ChronosServerWatcher chronosServerWatcher)
throws FatalChronosException, ChronosException {
this.chronosServerWatcher = chronosServerWatcher;
this.zkAdvanceTimestamp = Long.parseLong(properties.getProperty(
ChronosServer.ZK_ADVANCE_TIMESTAMP, "1000"));
}
/**
* Assign required number of timestamps, client can use [timestamp, timestamp + range).
*
* @param range, the number of timestamps to assign
* @return timestamp, the first available timestamp to client
*/
public long getTimestamps(int range) throws TException {
// can get 2^18(262144) times for each millisecond for about 1115 years
long currentTime = System.currentTimeMillis() << 18;
synchronized (this) {
// maxAssignedTimestamp is assigned last time, can't return currentTime when it's less or equal
if (currentTime > maxAssignedTimestamp) {
maxAssignedTimestamp = currentTime + range - 1;
} else {
maxAssignedTimestamp += range;
}
// now [maxAssignedTimestamp - range + 1, maxAssignedTimestamp] will be returned
// for correctness, compare with persistent timestamp and set it if necessary
if (maxAssignedTimestamp >= chronosServerWatcher.getCachedPersistentTimestamp()) {
// wait for the result of asyn set
sleepUntilAsyncSet();
// sync set persistent timestamp if necessary
if (maxAssignedTimestamp >= chronosServerWatcher.getCachedPersistentTimestamp()) {
long newPersistentTimestamp = maxAssignedTimestamp + zkAdvanceTimestamp;
if (LOG.isDebugEnabled()) {
LOG.debug("Try to sync set persistent timestamp " + newPersistentTimestamp);
}
try {
chronosServerWatcher.setPersistentTimestamp(newPersistentTimestamp);
} catch (ChronosException e) {
LOG.fatal("Error to set persistent timestamp, exit immediately");
System.exit(0);
}
}
}
// for performance, async set persistent timestamp before reaching persistent timestamp
if (!isAsyncSetPersistentTimestamp
&& maxAssignedTimestamp >= chronosServerWatcher.getCachedPersistentTimestamp()
- zkAdvanceTimestamp * 0.5) {
long newPersistentTimestamp = chronosServerWatcher.getCachedPersistentTimestamp()
+ zkAdvanceTimestamp;
if (LOG.isDebugEnabled()) {
LOG.debug("Try to async set persistent timestamp " + newPersistentTimestamp);
}
isAsyncSetPersistentTimestamp = true;
asyncSetPersistentTimestamp(newPersistentTimestamp);
}
// return the first available timestamp
return maxAssignedTimestamp - range + 1;
}
}
/**
* Provide a convenient interface to get a single timestamp.
*
* @return the allocated timestamp
* @throws TException when error to response thrift request
*/
public long getTimestamp() throws TException {
return getTimestamps(1);
}
/**
* Sleep until asynchronously set persistent timestamp successfully.
*/
private void sleepUntilAsyncSet() {
LOG.info("Sleep a while until asynchronously set persistent timestamp");
while (isAsyncSetPersistentTimestamp) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
LOG.fatal("Interrupt when sleep to set persistent timestamp, exit immediately");
System.exit(0);
}
}
}
/**
* Get the persistent timestamp in ZooKeeper and initialize the new one in ZooKeeper.
*
* @throws ChronosException when error to set value in ZooKeeper
* @throws FatalChronosException when set a smaller timestamp in ZooKeeper
*/
public void initTimestamp() throws ChronosException, FatalChronosException {
maxAssignedTimestamp = chronosServerWatcher.getPersistentTimestamp();
long newPersistentTimestamp = maxAssignedTimestamp + zkAdvanceTimestamp;
chronosServerWatcher.setPersistentTimestamp(newPersistentTimestamp);
LOG.info("Get persistent timestamp " + maxAssignedTimestamp + " and set "
+ newPersistentTimestamp + " in ZooKeeper");
}
/**
* Create a new thread to asynchronously set persistent timestamp in ZooKeeper.
*
* @param newPersistentTimestamp the new timestamp to set
*/
public synchronized void asyncSetPersistentTimestamp(final long newPersistentTimestamp) {
new Thread() {
@Override
public void run() {
try {
chronosServerWatcher.setPersistentTimestamp(newPersistentTimestamp);
isAsyncSetPersistentTimestamp = false;
} catch (Exception e) {
LOG.fatal("Error to set persistent timestamp, exit immediately");
System.exit(0);
}
}
}.start();
}
public ChronosServerWatcher getChronosServerWatcher() {
return chronosServerWatcher;
}
}