/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Oct 26, 2006
*/
package com.bigdata.util;
import org.apache.log4j.Logger;
/**
* A timestamp factory based on {@link System#currentTimeMillis()}. Timestamps
* reported by this factory are guarenteed to be distinct and strictly
* increasing during the life cycle of the JVM. No guarentee is made if across
* JVMs or system reboots. A means is available to inform the factory of the
* earliest timestamp that it may serve. This may be used on restart to ensure
* that time goes forward or when handing off from one timestamp service to
* another.
* <p>
* Note: Time as reported by {@link System#currentTimeMillis()} can do crazy
* things, including going backwards - presumably because of an error somewhere
* in the time management stack (observed on Fedora core 6 with Sun JDK
* 1.6.0_03). In these cases a warning is logged and the timestamp factory
* begins to assign up one long integers instead. If time catches up, then
* another warning is logged and the factory again begins to report timestamps
* based on {@link System#currentTimeMillis()}. Note that when the factory is
* using a one-up assignment it may appear to have a resolution finer than one
* millisecond.
* <p>
* Note: method on this class are <code>synchronized</code> to ensure that
* concurrent callers receive distinct timestamps. Likewise, the methods on this
* class are <code>static</code> to ensure that assigned timestamps are global
* for a JVM.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class MillisecondTimestampFactory {
private static final Logger log = Logger
.getLogger(MillisecondTimestampFactory.class);
/**
* The initial lower bound for the assigned timestamps.
*/
private static long _lastTimestamp = System.currentTimeMillis();
/**
* Initially the factory will use times as reported by
* {@link System#currentTimeMillis()}.
*/
private static boolean _autoIncMode = false;
/**
* Return <code>true</code> if the timestamp factory is in auto-increment
* mode.
*/
synchronized static public boolean isAutoIncMode() {
return _autoIncMode;
}
/**
* No public constructor.
*/
private MillisecondTimestampFactory() {
}
/**
* Sets the lower bound for the generated timestamps. This method may be
* used to safely synchronize a failover timestamp service with a primary.
* <strong>Extreme care should be used with this method as it can force the
* factory to return timestamps out of order.</strong>
*
* @param lowerBound
* The lower bound.
*
* @throws IllegalArgumentException
* if the given timestamp is non-positive.
*/
synchronized static public void setLowerBound(final long lowerBound) {
assertPositive(lowerBound);
if (_lastTimestamp < lowerBound) {
log.warn("Timestamp factory is being set to an earlier time!");
} else {
log.info("Advancing: lowerBound=" + lowerBound);
}
_lastTimestamp = lowerBound;
_autoIncMode = false;
}
/**
* This is a paranoia check in case the timestamp overflows. The timestamp
* value 0L and negative timestamps all have special interpretations for
* bigdata so this factory MUST NOT assign a non-positive timestamp.
*
* @param t
* A timestamp.
*
* @throws IllegalStateException
* if <i>t</i> is non-positive.
*/
private static void assertPositive(final long t) {
if (t <= 0L) {
throw new IllegalStateException("Timestamp is non-positive: " + t);
}
}
/**
* Generates a timestamp based on {@link System#currentTimeMillis()} that is
* guaranteed to be distinct from the last timestamp generated by this
* method during the life cycle of the JVM. No guarantee is made if across
* JVMs or system reboots. However, a means is available to inform the
* factory of the earliest timestamp that it may serve.
* <p>
* <p>
*
* @return A timestamp with no more millisecond resolution.
*
* @see System#currentTimeMillis()
*/
synchronized static public long nextMillis() {
// final long lastTimestamp = lastTimestamp;
// current time.
long timestamp = System.currentTimeMillis();
if (_autoIncMode) {
if (timestamp < _lastTimestamp) {
timestamp = _lastTimestamp + 1;
// overflow and paranoia test.
assertPositive(timestamp);
// timestamp is Ok, so save it.
_lastTimestamp = timestamp;
// return the assigned timestamp value.
return timestamp;
}
_autoIncMode = false;
log
.warn("Leaving auto-increment mode: time is going forward again: lastTimestamp="
+ _lastTimestamp + ", millisTime=" + timestamp);
// fall through.
}
assert !_autoIncMode;
// // spin looking for a distinct timestamp.
// for(int i=0; i<1000 && timestamp == lastMillisTime; i++) {
//
// timestamp = System.currentTimeMillis();
//
// }
// if not distinct, then sleep waiting for a distinct timestamp.
while (timestamp == _lastTimestamp) {
try {
Thread.sleep(0, 1);
} catch (InterruptedException ex) {
// ignore.
}
timestamp = System.currentTimeMillis();
}
if (timestamp < _lastTimestamp) {
/*
* System.currentTimeMillis() is reporting a time that is less than
* the last timestamp reported by this class. We switch over to an
* auto-increment mode and recursively invoke ourselves to return a
* one-up timestamp in order to keep the apparent time as reported
* by this factory moving forward.
*/
log
.warn("Entering auto-increment mode : milliseconds go backward: lastTimestamp="
+ _lastTimestamp + ", millisTime=" + timestamp);
_autoIncMode = true;
return nextMillis();
}
// overflow and paranoia test.
assertPositive(timestamp);
// timestamp is Ok, so save it.
_lastTimestamp = timestamp;
// return the assigned timestamp value.
return timestamp;
}
}