Improve clock sequence range configuration
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Jan 2022 09:16:09 +0000 (10:16 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Jan 2022 09:16:09 +0000 (10:16 +0100)
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java

index 38327223ff168173f62870eb93a293d7a63d7be9..52becc85ac0946a018e692b1065e3b20c999c83a 100644 (file)
@@ -26,6 +26,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
        protected ConcurrentTimeUuidState timeUuidState;
 
        private NodeIdSupplier nodeIdSupplier;
+       private long currentClockSequenceRange = 0;
 
        public AbstractAsyncUuidFactory() {
                secureRandom = createSecureRandom();
@@ -44,7 +45,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                if (nodeIdSupplier == null)
                        throw new IllegalStateException("No node id supplier available");
                long nodeIdBase = nodeIdSupplier.get();
-               timeUuidState.reset(nodeIdBase);
+               timeUuidState.reset(nodeIdBase, currentClockSequenceRange);
        }
 
        public void setNodeIdSupplier(NodeIdSupplier nodeIdSupplier) {
@@ -52,10 +53,24 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                reset();
        }
 
+       public void setNodeIdSupplier(NodeIdSupplier nodeIdSupplier, long range) {
+               this.currentClockSequenceRange = range >= 0 ? range & 0x3F00 : range;
+               setNodeIdSupplier(nodeIdSupplier);
+       }
+
        protected NodeIdSupplier getNodeIdSupplier() {
                return nodeIdSupplier;
        }
 
+       /**
+        * If positive, only clock_hi is taken from the argument (range & 0x3F00), if
+        * negative, the full range of possible values is used.
+        */
+       public void setCurrentClockSequenceRange(long range) {
+               this.currentClockSequenceRange = range >= 0 ? range & 0x3F00 : range;
+               reset();
+       }
+
        /*
         * SYNC OPERATIONS
         */
index 25ba62b03ba4d7224bcc8ec3326838a68b95b9ce..5bab35980370474e2a30663f8795a405be9e3c80 100644 (file)
@@ -15,15 +15,24 @@ import java.util.Set;
 import java.util.WeakHashMap;
 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 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<ConcurrentTimeUuidState.Holder> currentHolder;
@@ -121,10 +130,10 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
                return currentHolder.get().lastTimestamp;
        }
 
-       public void reset(long nodeIdBase) {
+       protected void reset(long nodeIdBase, long range) {
                synchronized (clockSequenceProvider) {
                        this.nodeIdBase = nodeIdBase;
-                       clockSequenceProvider.reset();
+                       clockSequenceProvider.reset(range);
                        clockSequenceProvider.notifyAll();
                }
        }
@@ -146,7 +155,6 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
        /*
         * INTERNAL CLASSSES
         */
-
        private class Holder {
                private long lastTimestamp;
                private long clockSequence;
@@ -164,8 +172,6 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
 
                private void setClockSequence(long clockSequence) {
                        this.clockSequence = clockSequence;
-//                     if (nodeIdBase == null)
-//                             throw new IllegalStateException("Node id base is not initialised");
                        this.leastSig = nodeIdBase // already computed node base
                                        | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res
                                        | ((clockSequence & 0xFF) << 48); // clk_seq_low
@@ -179,9 +185,11 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
        }
 
        private static class ClockSequenceProvider {
-               private int rangeSize = 256;
-               private volatile int min;
-               private volatile int max;
+               /** 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;
@@ -190,12 +198,37 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
 
                ClockSequenceProvider(SecureRandom secureRandom) {
                        this.secureRandom = secureRandom;
-                       reset();
+//                     reset(range);
                }
 
-               synchronized void reset() {
-                       int min = secureRandom.nextInt(ConcurrentTimeUuidState.MAX_CLOCKSEQUENCE - rangeSize);
-                       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)
@@ -210,21 +243,21 @@ public class ConcurrentTimeUuidState implements UuidFactory.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) {
                        // Too many holders, we will remove the oldes ones
-                       while (activeHolders.size() > rangeSize) {
+                       while (activeHolders.size() > getRangeSize()) {
                                long oldestTimeStamp = -1;
                                Holder holderToRemove = null;
                                holders: for (Holder h : activeHolders.keySet()) {
@@ -253,7 +286,7 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
                        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();
@@ -267,9 +300,17 @@ public class ConcurrentTimeUuidState implements UuidFactory.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);
                }
 
        }
index 15245b2932f50074f374b8156b06d696ee77cf8a..264e047063ac0872af99f1fc8b0adeba0530aaa3 100644 (file)
@@ -19,17 +19,17 @@ import java.util.UUID;
 public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory {
        private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
 
-       public ConcurrentUuidFactory(byte[] nodeId) {
-               this(nodeId, 0);
+       public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId) {
+               this(initialClockRange, nodeId, 0);
        }
 
-       public ConcurrentUuidFactory(byte[] nodeId, int offset) {
+       public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId, int offset) {
                Objects.requireNonNull(nodeId);
                if (offset + 6 > nodeId.length)
                        throw new IllegalArgumentException("Offset too big: " + offset);
                byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
                long nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
-               setNodeIdSupplier(() -> nodeIdBase);
+               setNodeIdSupplier(() -> nodeIdBase, initialClockRange);
        }
 
        /**
index 9f27fad842bde97e615a2acef99425ee9ec4856b..fa68df1dbe2777328a87b008e932039b23e56a5b 100644 (file)
@@ -16,7 +16,11 @@ public class MacAddressUuidFactory extends ConcurrentUuidFactory {
        public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
 
        public MacAddressUuidFactory() {
-               super(localHardwareAddressAsNodeId());
+               this(0);
+       }
+
+       public MacAddressUuidFactory(long initialClockRange) {
+               super(initialClockRange, localHardwareAddressAsNodeId());
        }
 
        public static byte[] localHardwareAddressAsNodeId() {
index 3ce27fce1ad02fcbe81967078cf72b1b11b2e0b2..7270c0896255f4596f73cc489a7819f95f5bf791 100644 (file)
@@ -17,7 +17,7 @@ public class CmsUuidFactory extends ConcurrentUuidFactory {
        private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class);
 
        public CmsUuidFactory(byte[] nodeId) {
-               super(nodeId);
+               super(0, nodeId);
                assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0];
        }