com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer Maven / Gradle / Ivy
Show all versions of java-uuid-generator Show documentation
/* JUG Java Uuid Generator
*
* Copyright (c) 2002- Tatu Saloranta, [email protected]
*
* Licensed under the License specified in the file LICENSE which is
* included with the source code.
* You may not use this file except in compliance with the License.
*
* 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 com.fasterxml.uuid.ext;
import com.fasterxml.uuid.TimestampSynchronizer;
import java.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of {@link TimestampSynchronizer}, which uses file system
* as the storage and locking mechanism.
*
* Synchronization is achieved by obtaining an exclusive file locks on two
* specified lock files, and by using the files to store first "safe" timestamp
* value that the generator can use; alternating between one to use to ensure
* one of them always contains a valid timestamp. Latter is needed to guard
* against system clock moving backwards after UUID generator restart.
*
* Note: this class will only work on JDK 1.4 and above, since it requires
* NIO package to do proper file locking (as well as new opening mode for
* {@link RandomAccessFile}).
*
* Also note that it is assumed that the caller has taken care to synchronize
* access to method to be single-threaded. As such, none of the methods
* is explicitly synchronized here.
*/
public final class FileBasedTimestampSynchronizer
extends TimestampSynchronizer
{
private static final Logger logger = LoggerFactory.getLogger(FileBasedTimestampSynchronizer.class);
// // // Constants:
/**
* The default update interval is 10 seconds, meaning that the
* synchronizer "reserves" next 10 seconds for generation. This
* also means that the lock files need to be accessed at most
* once every ten second.
*/
final static long DEFAULT_UPDATE_INTERVAL = 10L * 1000L;
protected final static String DEFAULT_LOCK_FILE_NAME1 = "uuid1.lck";
protected final static String DEFAULT_LOCK_FILE_NAME2 = "uuid2.lck";
// // // Configuration:
protected long mInterval = DEFAULT_UPDATE_INTERVAL;
protected final LockedFile mLocked1, mLocked2;
// // // State:
/**
* Flag used to indicate which of timestamp files has the most
* recently succesfully updated timestamp value. True means that
* mFile1
is more recent; false that mFile2
* is.
*/
boolean mFirstActive = false;
/**
* Constructor that uses default values for names of files to use
* (files will get created in the current working directory), as
* well as for the update frequency value (10 seconds).
*/
public FileBasedTimestampSynchronizer()
throws IOException
{
this(new File(DEFAULT_LOCK_FILE_NAME1), new File(DEFAULT_LOCK_FILE_NAME2));
}
public FileBasedTimestampSynchronizer(File lockFile1, File lockFile2)
throws IOException
{
this(lockFile1, lockFile2, DEFAULT_UPDATE_INTERVAL);
}
public FileBasedTimestampSynchronizer(File lockFile1, File lockFile2, long interval)
throws IOException
{
mInterval = interval;
mLocked1 = new LockedFile(lockFile1);
boolean ok = false;
try {
mLocked2 = new LockedFile(lockFile2);
ok = true;
} finally {
if (!ok) {
mLocked1.deactivate();
}
}
// But let's leave reading up to initialization
}
/*
//////////////////////////////////////////////////////////////
// Configuration
//////////////////////////////////////////////////////////////
*/
public void setUpdateInterval(long interval)
{
if (interval < 1L) {
throw new IllegalArgumentException("Illegal value ("+interval+"); has to be a positive integer value");
}
mInterval = interval;
}
/*
//////////////////////////////////////////////////////////////
// Implementation of the API
//////////////////////////////////////////////////////////////
*/
/**
* This method is to be called only once by
* {@link com.fasterxml.uuid.UUIDTimer}. It
* should fetch the persisted timestamp value, which indicates
* first timestamp value that is guaranteed NOT to have used by
* a previous incarnation. If it can not determine such value, it
* is to return 0L as a marker.
*
* @return First timestamp value that was NOT locked by lock files;
* 0L to indicate that no information was read.
*/
@Override
protected long initialize() throws IOException
{
long ts1 = mLocked1.readStamp();
long ts2 = mLocked2.readStamp();
long result;
if (ts1 > ts2) {
mFirstActive = true;
result = ts1;
} else {
mFirstActive = false;
result = ts2;
}
/* Hmmh. If we didn't get a time stamp (-> 0), or if written time is
* ahead of current time, let's log something:
*/
if (result <= 0L) {
logger.warn("Could not determine safe timer starting point: assuming current system time is acceptable");
} else {
long now = System.currentTimeMillis();
//long diff = now - result;
/* It's more suspicious if old time was ahead... although with
* longer iteration values, it can be ahead without errors. So
* let's base check on current iteration value:
*/
if ((now + mInterval) < result) {
logger.warn("Safe timestamp read is {} milliseconds in future, and is greater than the inteval ({})", (result - now), mInterval);
}
/* Hmmh. Is there any way a suspiciously old timestamp could be
* harmful? It can obviously be useless but...
*/
}
return result;
}
@Override
public void deactivate() throws IOException
{
doDeactivate(mLocked1, mLocked2);
}
/**
* @return Timestamp value that the caller can NOT use. That is, all
* timestamp values prior to (less than) this value can be used
* ok, but this value and ones after can only be used by first
* calling update.
*/
@Override
public long update(long now)
throws IOException
{
long nextAllowed = now + mInterval;
/* We have to make sure to (over)write the one that is NOT
* actively used, to ensure that we always have fully persisted
* timestamp value, even if the write process gets interruped
* half-way through.
*/
if (mFirstActive) {
mLocked2.writeStamp(nextAllowed);
} else {
mLocked1.writeStamp(nextAllowed);
}
mFirstActive = !mFirstActive;
return nextAllowed;
}
/*
//////////////////////////////////////////////////////////////
// Internal methods
//////////////////////////////////////////////////////////////
*/
protected static void doDeactivate(LockedFile lf1, LockedFile lf2)
{
if (lf1 != null) {
lf1.deactivate();
}
if (lf2 != null) {
lf2.deactivate();
}
}
}