X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.api.uuid%2Fsrc%2Forg%2Fargeo%2Fapi%2Fuuid%2FConcurrentTimeUuidState.java;h=5bab35980370474e2a30663f8795a405be9e3c80;hb=0f8bddc982e2ea4f260e1eb639777b74e7893ea3;hp=7d5e6013bcfa5cec798d6866bd57a9d803b56d61;hpb=38414ed12442720c522f5cb6d89fbdb3010bf2f4;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java index 7d5e6013b..5bab35980 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java @@ -8,20 +8,31 @@ import java.security.SecureRandom; import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.util.Collections; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.argeo.api.uuid.UuidFactory.TimeUuidState; /** * A simple base implementation of {@link TimeUuidState}, which maintains - * different clock sequences for each thread. + * different clock sequences for each thread, based on a specified range. This + * range is defined as clock_seq_hi (cf. RFC4122) and only clock_seq_low is + * dynamically allocated. It means that there can be at most 256 parallel clock + * sequences. If that limit is reached, the clock sequence which has not be used + * for the most time is reallocated to the new thread. It is assumed that the + * context where time uUIDs will be generated will often be using thread pools + * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.) + * and that such reallocation won't have to happen too often. */ -public class ConcurrentTimeUuidState implements TimeUuidState { +public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState { private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName()); /** The maximum possible value of the clocksequence. */ - private final static int MAX_CLOCKSEQUENCE = 16384; + private final static int MAX_CLOCKSEQUENCE = 0x3F00; private final ClockSequenceProvider clockSequenceProvider; private final ThreadLocal currentHolder; @@ -33,6 +44,8 @@ public class ConcurrentTimeUuidState implements TimeUuidState { private final Clock clock; private final boolean useClockForMeasurement; + private long nodeIdBase; + public ConcurrentTimeUuidState(SecureRandom secureRandom, Clock clock) { useClockForMeasurement = clock != null; this.clock = clock != null ? clock : Clock.systemUTC(); @@ -42,8 +55,8 @@ public class ConcurrentTimeUuidState implements TimeUuidState { // compute the start reference startInstant = Instant.now(this.clock); long nowVm = nowVm(); - Duration duration = Duration.between(TimeUuidState.GREGORIAN_START, startInstant); - startTimeStamp = durationToUuidTimestamp(duration) - nowVm; + Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, startInstant); + startTimeStamp = TimeUuid.durationToTimestamp(duration) - nowVm; clockSequenceProvider = new ClockSequenceProvider(secureRandom); @@ -54,7 +67,7 @@ public class ConcurrentTimeUuidState implements TimeUuidState { protected ConcurrentTimeUuidState.Holder initialValue() { ConcurrentTimeUuidState.Holder value = new ConcurrentTimeUuidState.Holder(); value.threadId = Thread.currentThread().getId(); - value.lastTimestamp = startTimeStamp; + value.lastTimestamp = 0; clockSequenceProvider.newClockSequence(value); return value; } @@ -96,8 +109,8 @@ public class ConcurrentTimeUuidState implements TimeUuidState { private long computeNow() { if (useClockForMeasurement) { - Duration duration = Duration.between(TimeUuidState.GREGORIAN_START, Instant.now(clock)); - return durationToUuidTimestamp(duration); + Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, Instant.now(clock)); + return TimeUuid.durationToTimestamp(duration); } else { return startTimeStamp + nowVm(); } @@ -107,20 +120,48 @@ public class ConcurrentTimeUuidState implements TimeUuidState { return System.nanoTime() / 100; } - private long durationToUuidTimestamp(Duration duration) { - return (duration.getSeconds() * 10000000 + duration.getNano() / 100); + @Override + public long getClockSequence() { + return currentHolder.get().clockSequence; } @Override - public long getClockSequence() { - return (long) currentHolder.get().clockSequence; + public long getLastTimestamp() { + return currentHolder.get().lastTimestamp; + } + + protected void reset(long nodeIdBase, long range) { + synchronized (clockSequenceProvider) { + this.nodeIdBase = nodeIdBase; + clockSequenceProvider.reset(range); + clockSequenceProvider.notifyAll(); + } } - private static class Holder { + @Override + public long getLeastSignificantBits() { + return currentHolder.get().leastSig; + } + + @Override + public long getMostSignificantBits() { + long timestamp = useTimestamp(); + long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low + | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid + | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version + return mostSig; + } + + /* + * INTERNAL CLASSSES + */ + private class Holder { private long lastTimestamp; - private int clockSequence; + private long clockSequence; private long threadId; + private long leastSig; + @Override public boolean equals(Object obj) { boolean isItself = this == obj; @@ -129,8 +170,11 @@ public class ConcurrentTimeUuidState implements TimeUuidState { return isItself; } - private synchronized void setClockSequence(int clockSequence) { + private void setClockSequence(long clockSequence) { this.clockSequence = clockSequence; + this.leastSig = nodeIdBase // already computed node base + | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res + | ((clockSequence & 0xFF) << 48); // clk_seq_low } @Override @@ -141,23 +185,50 @@ public class ConcurrentTimeUuidState implements TimeUuidState { } private static class ClockSequenceProvider { - private int rangeSize = 256; - private volatile int min; - private volatile int max; - private final AtomicInteger counter = new AtomicInteger(-1); + /** Set to an illegal value. */ + private long range = MAX_CLOCKSEQUENCE;// this is actually clk_seq_hi +// private int rangeSize = 256; + private volatile long min; + private volatile long max; + private final AtomicLong counter = new AtomicLong(-1); private final SecureRandom secureRandom; - private final WeakHashMap activeHolders = new WeakHashMap<>(); + private final Map activeHolders = Collections.synchronizedMap(new WeakHashMap<>()); ClockSequenceProvider(SecureRandom secureRandom) { this.secureRandom = secureRandom; - reset(); +// reset(range); } - synchronized void reset() { - int min = secureRandom.nextInt(ConcurrentTimeUuidState.MAX_CLOCKSEQUENCE); - int max = min + rangeSize; + synchronized void reset(long range) { + // long min = secureRandom.nextInt(ConcurrentTimeUuidState.MAX_CLOCKSEQUENCE - + // rangeSize); + // long max = min + rangeSize; + + long min, max; + if (range >= 0) { + if (range > MAX_CLOCKSEQUENCE) + throw new IllegalArgumentException("Range " + Long.toHexString(range) + " is too big"); + long previousRange = this.range; + this.range = range & 0x3F00; + if (this.range != range) { + this.range = previousRange; + throw new IllegalArgumentException( + "Range is not properly formatted: " + range + " (0x" + Long.toHexString(range) + ")"); + } + + min = this.range; + max = min | 0xFF; + } else {// full range + this.range = range; + min = 0; + max = MAX_CLOCKSEQUENCE; + } + assert min == (int) min; + assert max == (int) max; + + // TODO rather use assertions if (min >= max) throw new IllegalArgumentException("Minimum " + min + " is bigger than maximum " + max); if (min < 0 || min > MAX_CLOCKSEQUENCE) @@ -172,24 +243,21 @@ public class ConcurrentTimeUuidState implements TimeUuidState { if (activeCount > getRangeSize()) throw new IllegalStateException( "There are too many holders for range [" + min + "," + max + "] : " + activeCount); - // reset the counter - counter.set(min); + + // reset the counter with a random value in range + long firstCount = min + secureRandom.nextInt(getRangeSize()); + counter.set(firstCount); + + // reset holders for (Holder holder : active) { // save old clocksequence? newClockSequence(holder); } } - private synchronized int getRangeSize() { - return rangeSize; - } - private synchronized void newClockSequence(Holder holder) { -// int activeCount = activeHolders.size(); - while (activeHolders.size() > rangeSize) { -// throw new IllegalStateException( -// "There are too many holders for range [" + min + "," + max + "] : " + activeCount); - // remove oldest + // Too many holders, we will remove the oldes ones + while (activeHolders.size() > getRangeSize()) { long oldestTimeStamp = -1; Holder holderToRemove = null; holders: for (Holder h : activeHolders.keySet()) { @@ -214,11 +282,11 @@ public class ConcurrentTimeUuidState implements TimeUuidState { logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence); } - int newClockSequence = -1; + long newClockSequence = -1; int tryCount = 0;// an explicit exit condition do { tryCount++; - if (tryCount >= rangeSize) + if (tryCount >= getRangeSize()) throw new IllegalStateException("No more clock sequence available"); newClockSequence = counter.incrementAndGet(); @@ -232,9 +300,17 @@ public class ConcurrentTimeUuidState implements TimeUuidState { // TODO use an iterator to check the values holder.setClockSequence(newClockSequence); activeHolders.put(holder, newClockSequence); - if (logger.isLoggable(DEBUG)) - logger.log(DEBUG, - "New clocksequence " + newClockSequence + " for thread " + Thread.currentThread().getId()); + if (logger.isLoggable(DEBUG)) { + String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF) + : Long.toHexString(newClockSequence | 0x8000); + String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000); + logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId() + + " (in range " + rangeDesc + ")"); + } + } + + private synchronized int getRangeSize() { + return (int) (max - min); } }