Improve time based UUID speed and configuration.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 24 Jan 2022 10:07:50 +0000 (11:07 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 24 Jan 2022 10:07:50 +0000 (11:07 +0100)
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/SimpleUuidFactory.java [deleted file]
org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuidState.java
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java

index 2cdb59f77f08b5d4ff2eeaedfe548e2107c3d51d..f57462388959f2fb9e64c8dd55c0da38cdaf8bd0 100644 (file)
@@ -20,7 +20,9 @@ import java.util.concurrent.ThreadLocalRandom;
  */
 public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory implements AsyncUuidFactory {
        private SecureRandom secureRandom;
-       protected TimeUuidState timeUuidState;
+       protected ConcurrentTimeUuidState timeUuidState;
+
+       private NodeIdSupplier nodeIdSupplier;
 
        public AbstractAsyncUuidFactory() {
                secureRandom = newSecureRandom();
@@ -30,11 +32,22 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
         * ABSTRACT METHODS
         */
 
-       protected abstract UUID newTimeUUID();
+       protected abstract SecureRandom newSecureRandom();
 
-       protected abstract UUID newTimeUUIDwithMacAddress();
+       /*
+        * STATE
+        */
+       public void reset() {
+               if (nodeIdSupplier == null)
+                       throw new IllegalStateException("No node id supplier available");
+               long nodeIdBase = nodeIdSupplier.get();
+               timeUuidState.reset(nodeIdBase);
+       }
 
-       protected abstract SecureRandom newSecureRandom();
+       public void setNodeIdSupplier(NodeIdSupplier nodeIdSupplier) {
+               this.nodeIdSupplier = nodeIdSupplier;
+               reset();
+       }
 
        /*
         * SYNC OPERATIONS
@@ -47,6 +60,22 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                return newRandomUUID(ThreadLocalRandom.current());
        }
 
+       protected UUID newTimeUUID() {
+               if (nodeIdSupplier == null)
+                       throw new IllegalStateException("No node id supplier available");
+               UUID uuid = new UUID(timeUuidState.getMostSignificantBits(), timeUuidState.getLeastSignificantBits());
+               long clockSequence = timeUuidState.getClockSequence(); 
+               long timestamp = timeUuidState.getLastTimestamp(); 
+               // assert uuid.node() == longFromBytes(node);
+               assert uuid.timestamp() == timestamp;
+               assert uuid.clockSequence() == clockSequence
+                               : "uuid.clockSequence()=" + uuid.clockSequence() + " clockSequence=" + clockSequence;
+               assert uuid.version() == 1;
+               assert uuid.variant() == 2;
+
+               return uuid;
+       }
+
        /*
         * ASYNC OPERATIONS (heavy)
         */
@@ -74,11 +103,6 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                return request(futureTimeUUID());
        }
 
-       @Override
-       public CompletionStage<UUID> requestTimeUUIDwithMacAddress() {
-               return request(futureTimeUUIDwithMacAddress());
-       }
-
        /*
         * ASYNC OPERATIONS (light)
         */
@@ -105,26 +129,4 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
        public ForkJoinTask<UUID> futureTimeUUID() {
                return submit(this::newTimeUUID);
        }
-
-       @Override
-       public ForkJoinTask<UUID> futureTimeUUIDwithMacAddress() {
-               return submit(this::newTimeUUIDwithMacAddress);
-       }
-
-//     @Override
-//     public UUID timeUUID() {
-//             if (ConcurrentTimeUuidState.isTimeUuidThread.get())
-//                     return newTimeUUID();
-//             else
-//                     return futureTimeUUID().join();
-//     }
-//
-//     @Override
-//     public UUID timeUUIDwithMacAddress() {
-//             if (ConcurrentTimeUuidState.isTimeUuidThread.get())
-//                     return newTimeUUIDwithMacAddress();
-//             else
-//                     return futureTimeUUIDwithMacAddress().join();
-//     }
-
 }
index ba2196add75155b6f33360634959ef8ca759675f..9d829987b6cd6cd047d939ddbba3da7ffb26db73 100644 (file)
@@ -272,7 +272,7 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return new String(hexChars);
        }
 
-       protected byte[] toNodeId(byte[] source, int offset) {
+       protected byte[] toNodeIdBytes(byte[] source, int offset) {
                if (source == null)
                        return null;
                if (offset < 0 || offset + 6 > source.length)
@@ -282,6 +282,17 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return nodeId;
        }
 
+//     protected long toNodeIdBase(byte[] node) {
+//             assert node.length == 6;
+//             return LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
+//                             | (node[0] & 0xFFL) //
+//                             | ((node[1] & 0xFFL) << 8) //
+//                             | ((node[2] & 0xFFL) << 16) //
+//                             | ((node[3] & 0xFFL) << 24) //
+//                             | ((node[4] & 0xFFL) << 32) //
+//                             | ((node[5] & 0xFFL) << 40); //
+//     }
+
        /*
         * STATIC UTILITIES
         */
index dd40f81e99d00d43339bf42e7d74fc10910397c9..bd92f561ea99069eb8cfa564983b36f39f588892 100644 (file)
@@ -11,12 +11,8 @@ public interface AsyncUuidFactory extends UuidFactory {
         */
        CompletionStage<UUID> requestTimeUUID();
 
-       CompletionStage<UUID> requestTimeUUIDwithMacAddress();
-
        ForkJoinTask<UUID> futureTimeUUID();
 
-       ForkJoinTask<UUID> futureTimeUUIDwithMacAddress();
-
        /*
         * NAME BASED (version 3 and 5)
         */
@@ -48,11 +44,6 @@ public interface AsyncUuidFactory extends UuidFactory {
                return futureTimeUUID().invoke();
        }
 
-       @Override
-       default UUID timeUUIDwithMacAddress() {
-               return futureTimeUUIDwithMacAddress().invoke();
-       }
-
        @Override
        default UUID nameUUIDv5(UUID namespace, byte[] data) {
                return futureNameUUIDv5(namespace, data).invoke();
index 1529a3a5deac63d83b9d3264f74e9b99330bcef9..61f5b8304d3710713a500fa2fc7347ec81ae07c6 100644 (file)
@@ -8,10 +8,12 @@ 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;
 
 /**
  * A simple base implementation of {@link TimeUuidState}, which maintains
@@ -33,6 +35,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();
@@ -54,7 +58,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;
                        }
@@ -113,14 +117,48 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
 
        @Override
        public long getClockSequence() {
-               return (long) currentHolder.get().clockSequence;
+               return currentHolder.get().clockSequence;
        }
 
-       private static class Holder {
+       @Override
+       public long getLastTimestamp() {
+               return currentHolder.get().lastTimestamp;
+       }
+
+       public void reset(long nodeIdBase) {
+               synchronized (clockSequenceProvider) {
+                       this.nodeIdBase = nodeIdBase;
+                       clockSequenceProvider.reset();
+                       clockSequenceProvider.notifyAll();
+               }
+       }
+
+       @Override
+       public long getLeastSignificantBits() {
+               return currentHolder.get().leastSig;
+       }
+
+       @Override
+       public long getMostSignificantBits() {
+               long timestamp = useTimestamp();
+               long mostSig = MOST_SIG_VERSION1 // base for version 1 UUID
+                               | ((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 +167,13 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
                        return isItself;
                }
 
-               private synchronized void setClockSequence(int clockSequence) {
+               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
                }
 
                @Override
@@ -144,11 +187,11 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
                private int rangeSize = 256;
                private volatile int min;
                private volatile int max;
-               private final AtomicInteger counter = new AtomicInteger(-1);
+               private final AtomicLong counter = new AtomicLong(-1);
 
                private final SecureRandom secureRandom;
 
-               private final WeakHashMap<Holder, Integer> activeHolders = new WeakHashMap<>();
+               private final Map<Holder, Long> activeHolders = Collections.synchronizedMap(new WeakHashMap<>());
 
                ClockSequenceProvider(SecureRandom secureRandom) {
                        this.secureRandom = secureRandom;
@@ -185,11 +228,8 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
                }
 
                private synchronized void newClockSequence(Holder holder) {
-//                     int activeCount = activeHolders.size();
+                       // Too many holders, we will remove the oldes ones
                        while (activeHolders.size() > rangeSize) {
-//                             throw new IllegalStateException(
-//                                             "There are too many holders for range [" + min + "," + max + "] : " + activeCount);
-                               // remove oldest
                                long oldestTimeStamp = -1;
                                Holder holderToRemove = null;
                                holders: for (Holder h : activeHolders.keySet()) {
@@ -214,7 +254,7 @@ 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++;
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
new file mode 100644 (file)
index 0000000..05b2f05
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.api.uuid;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.lang.System.Logger;
+import java.security.DrbgParameters;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Objects;
+
+/**
+ * A configurable implementation of an {@link AsyncUuidFactory}, which can be
+ * used as a base class for more optimised implementations.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122
+ */
+public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
+       private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
+
+//     private byte[] defaultNodeId;
+
+       private Long nodeIdBase;
+
+       public ConcurrentUuidFactory(byte[] nodeId, int offset) {
+               Objects.requireNonNull(nodeId);
+               if (offset + 6 > nodeId.length)
+                       throw new IllegalArgumentException("Offset too big: " + offset);
+               byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
+               nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
+               setNodeIdSupplier(() -> nodeIdBase);
+       }
+
+       protected ConcurrentUuidFactory() {
+
+       }
+
+       @Override
+       protected SecureRandom newSecureRandom() {
+               SecureRandom secureRandom;
+               try {
+                       secureRandom = SecureRandom.getInstance("DRBG",
+                                       DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
+               } catch (NoSuchAlgorithmException e) {
+                       try {
+                               logger.log(DEBUG, "DRBG secure random not found, using strong");
+                               secureRandom = SecureRandom.getInstanceStrong();
+                       } catch (NoSuchAlgorithmException e1) {
+                               logger.log(WARNING, "No strong secure random was found, using default");
+                               secureRandom = new SecureRandom();
+                       }
+               }
+               return secureRandom;
+       }
+
+       /*
+        * TIME-BASED (version 1)
+        */
+//
+//     @Override
+//     public UUID newTimeUUID() {
+//             return newTimeUUID(timeUuidState.useTimestamp(), timeUuidState.getClockSequence(), defaultNodeId, 0);
+//     }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
new file mode 100644 (file)
index 0000000..d1ce6de
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.uuid;
+
+public class MacAddressUuidFactory extends ConcurrentUuidFactory {
+       public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
+
+       public MacAddressUuidFactory() {
+               setNodeIdSupplier(() -> {
+                       byte[] hardwareAddress = getHardwareAddress();
+                       byte[] macAddressNodeId = toNodeIdBytes(hardwareAddress, 0);
+                       long nodeIdBase = NodeIdSupplier.toNodeIdBase(macAddressNodeId);
+                       return nodeIdBase;
+               });
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java
new file mode 100644 (file)
index 0000000..ae9686c
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.api.uuid;
+
+import java.util.function.Supplier;
+
+/** A factory for node id base */
+public interface NodeIdSupplier extends Supplier<Long> {
+       long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
+
+       static long toNodeIdBase(byte[] node) {
+               assert node.length == 6;
+               return LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
+                               | (node[0] & 0xFFL) //
+                               | ((node[1] & 0xFFL) << 8) //
+                               | ((node[2] & 0xFFL) << 16) //
+                               | ((node[3] & 0xFFL) << 24) //
+                               | ((node[4] & 0xFFL) << 32) //
+                               | ((node[5] & 0xFFL) << 40); //
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/SimpleUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/SimpleUuidFactory.java
deleted file mode 100644 (file)
index 71f8a13..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.api.uuid;
-
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.WARNING;
-
-import java.lang.System.Logger;
-import java.security.DrbgParameters;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.time.Clock;
-import java.util.UUID;
-
-/**
- * Simple implementation of an {@link UuidFactory}, which can be used as a base
- * class for more optimised implementations.
- * 
- * @see https://datatracker.ietf.org/doc/html/rfc4122
- */
-public class SimpleUuidFactory extends AbstractAsyncUuidFactory {
-       private final static Logger logger = System.getLogger(SimpleUuidFactory.class.getName());
-       public final static UuidFactory DEFAULT = new SimpleUuidFactory(null, -1, null);
-
-//     private NodeId macAddressNodeId;
-//     private NodeId defaultNodeId;
-       private byte[] macAddressNodeId;
-       private byte[] defaultNodeId;
-
-       public SimpleUuidFactory(byte[] nodeId, int offset, Clock clock) {
-               byte[] hardwareAddress = getHardwareAddress();
-//             macAddressNodeId = hardwareAddress != null ? new NodeId(hardwareAddress, 0) : null;
-               macAddressNodeId = toNodeId(hardwareAddress, 0);
-
-//             defaultNodeId = nodeId != null ? new NodeId(nodeId, offset) : macAddressNodeId;
-               defaultNodeId = nodeId != null ? toNodeId(nodeId, offset) : toNodeId(macAddressNodeId, 0);
-               if (defaultNodeId == null)
-                       throw new IllegalStateException("No default node id specified");
-       }
-
-       @Override
-       protected SecureRandom newSecureRandom() {
-               SecureRandom secureRandom;
-               try {
-                       secureRandom = SecureRandom.getInstance("DRBG",
-                                       DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
-               } catch (NoSuchAlgorithmException e) {
-                       try {
-                               logger.log(DEBUG, "DRBG secure random not found, using strong");
-                               secureRandom = SecureRandom.getInstanceStrong();
-                       } catch (NoSuchAlgorithmException e1) {
-                               logger.log(WARNING, "No strong secure random was found, using default");
-                               secureRandom = new SecureRandom();
-                       }
-               }
-               return secureRandom;
-       }
-
-       /*
-        * TIME-BASED (version 1)
-        */
-
-       @Override
-       public UUID newTimeUUIDwithMacAddress() {
-               if (macAddressNodeId == null)
-                       throw new UnsupportedOperationException("No MAC address is available");
-               return newTimeUUID(timeUuidState.useTimestamp(), timeUuidState.getClockSequence(), macAddressNodeId, 0);
-       }
-
-       @Override
-       public UUID newTimeUUID() {
-               return newTimeUUID(timeUuidState.useTimestamp(), timeUuidState.getClockSequence(), defaultNodeId, 0);
-       }
-
-       /*
-        * RANDOM v4
-        */
-//     @Override
-//     public UUID randomUUID(Random random) {
-//             return newRandomUUID(random);
-//     }
-
-//     @Override
-//     public UUID randomUUID() {
-//             return randomUUID(secureRandom);
-//     }
-//
-//     static class NodeId extends ThreadLocal<byte[]> {
-//             private byte[] source;
-//             private int offset;
-//
-//             public NodeId(byte[] source, int offset) {
-//                     Objects.requireNonNull(source);
-//                     this.source = source;
-//                     this.offset = offset;
-//                     if (offset < 0 || offset + 6 > source.length)
-//                             throw new ArrayIndexOutOfBoundsException(offset);
-//             }
-//
-//             @Override
-//             protected byte[] initialValue() {
-//                     byte[] value = new byte[6];
-//                     System.arraycopy(source, offset, value, 0, 6);
-//                     return value;
-//             }
-//
-//     }
-}
index b926ccb3642d0c09a3098d05f3b5d4ab6ff92193..780e8ad337070e6fc129db45e91e6646509abaa2 100644 (file)
@@ -11,14 +11,20 @@ import java.time.ZonedDateTime;
  * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1
  */
 public interface TimeUuidState {
+       long MOST_SIG_VERSION1 = (1l << 12);
+       long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
 
        /** Start of the Gregorian time, used by time-based UUID (v1). */
        final static Instant GREGORIAN_START = ZonedDateTime.of(1582, 10, 15, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
 
-       long useTimestamp();
+       long getLastTimestamp();
 
        long getClockSequence();
 
+       long getLeastSignificantBits();
+
+       long getMostSignificantBits();
+
        static boolean isNoMacAddressNodeId(byte[] nodeId) {
                return (nodeId[0] & 1) != 0;
        }
index a2970618403f45002a2706ad9e48e0badf649c88..78bb402cf4ff13e5a44fc3b710c99b21209b0f26 100644 (file)
@@ -19,15 +19,11 @@ public interface UuidFactory extends Supplier<UUID> {
        /*
         * TIME-BASED (version 1)
         */
-
        UUID timeUUID();
 
-       UUID timeUUIDwithMacAddress();
-
        /*
         * NAME BASED (version 3 and 5)
         */
-
        UUID nameUUIDv5(UUID namespace, byte[] data);
 
        UUID nameUUIDv3(UUID namespace, byte[] data);