Asynchronous UUID factory.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 23 Jan 2022 13:26:56 +0000 (14:26 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 23 Jan 2022 13:26:56 +0000 (14:26 +0100)
org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractAsyncUuidFactory.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java
org.argeo.api.acr/src/org/argeo/api/acr/uuid/AsyncUuidFactory.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/uuid/ConcurrentTimeUuidState.java
org.argeo.api.acr/src/org/argeo/api/acr/uuid/SimpleUuidFactory.java
org.argeo.api.acr/src/org/argeo/api/acr/uuid/UuidFactory.java

diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractAsyncUuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractAsyncUuidFactory.java
new file mode 100644 (file)
index 0000000..09becba
--- /dev/null
@@ -0,0 +1,130 @@
+package org.argeo.api.acr.uuid;
+
+import java.security.SecureRandom;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Execute {@link UUID} creations in {@link ForkJoinPool#commonPool()}. The goal
+ * is to provide good performance while staying within the parallelism defined
+ * for the system, so as to overwhelm it if many UUIDs are requested.
+ * Additionally, with regard to time based UUIDs, since we use
+ * {@link ConcurrentTimeUuidState}, which maintains one "clock sequence" per
+ * thread, we want to limit the number of threads accessing the actual
+ * generation method.
+ */
+public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory implements AsyncUuidFactory {
+       private SecureRandom secureRandom;
+       protected TimeUuidState timeUuidState;
+
+       public AbstractAsyncUuidFactory() {
+               secureRandom = newSecureRandom();
+               timeUuidState = new ConcurrentTimeUuidState(secureRandom, null);
+       }
+       /*
+        * ABSTRACT METHODS
+        */
+
+       protected abstract UUID newTimeUUID();
+
+       protected abstract UUID newTimeUUIDwithMacAddress();
+
+       protected abstract SecureRandom newSecureRandom();
+
+       /*
+        * SYNC OPERATIONS
+        */
+       protected UUID newRandomUUIDStrong() {
+               return newRandomUUID(secureRandom);
+       }
+
+       public UUID randomUUIDWeak() {
+               return newRandomUUID(ThreadLocalRandom.current());
+       }
+
+       /*
+        * ASYNC OPERATIONS (heavy)
+        */
+       protected CompletionStage<UUID> request(ForkJoinTask<UUID> newUuid) {
+               return CompletableFuture.supplyAsync(newUuid::invoke).minimalCompletionStage();
+       }
+
+       @Override
+       public CompletionStage<UUID> requestNameUUIDv5(UUID namespace, byte[] data) {
+               return request(futureNameUUIDv5(namespace, data));
+       }
+
+       @Override
+       public CompletionStage<UUID> requestNameUUIDv3(UUID namespace, byte[] data) {
+               return request(futureNameUUIDv3(namespace, data));
+       }
+
+       @Override
+       public CompletionStage<UUID> requestRandomUUIDStrong() {
+               return request(futureRandomUUIDStrong());
+       }
+
+       @Override
+       public CompletionStage<UUID> requestTimeUUID() {
+               return request(futureTimeUUID());
+       }
+
+       @Override
+       public CompletionStage<UUID> requestTimeUUIDwithMacAddress() {
+               return request(futureTimeUUIDwithMacAddress());
+       }
+
+       /*
+        * ASYNC OPERATIONS (light)
+        */
+       protected ForkJoinTask<UUID> submit(Callable<UUID> newUuid) {
+               return ForkJoinTask.adapt(newUuid);
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureNameUUIDv5(UUID namespace, byte[] data) {
+               return submit(() -> newNameUUIDv5(namespace, data));
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureNameUUIDv3(UUID namespace, byte[] data) {
+               return submit(() -> newNameUUIDv3(namespace, data));
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureRandomUUIDStrong() {
+               return submit(this::newRandomUUIDStrong);
+       }
+
+       @Override
+       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 daa186f28957ad7365aa500ca76391bb77353c7b..1ecb64e12ae65044be0fa162656ef3865d8dcbee 100644 (file)
@@ -290,6 +290,7 @@ public abstract class AbstractUuidFactory implements UuidFactory {
         * make it more readable.
         */
        public static String toBinaryString(UUID uuid, int charsPerSegment, char separator) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
                String binaryString = toBinaryString(uuid);
                StringBuilder sb = new StringBuilder(128 + (128 / charsPerSegment));
                for (int i = 0; i < binaryString.length(); i++) {
@@ -302,6 +303,7 @@ public abstract class AbstractUuidFactory implements UuidFactory {
 
        /** Converts an UUID to a binary string (list of 0 and 1). */
        public static String toBinaryString(UUID uuid) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
                String most = zeroTo64Chars(Long.toBinaryString(uuid.getMostSignificantBits()));
                String least = zeroTo64Chars(Long.toBinaryString(uuid.getLeastSignificantBits()));
                String binaryString = most + least;
@@ -309,6 +311,16 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return binaryString;
        }
 
+       /**
+        * Force this node id to be identified as no MAC address.
+        * 
+        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+        */
+       public static void forceToNoMacAddress(byte[] nodeId, int offset) {
+               assert nodeId != null && offset < nodeId.length;
+               nodeId[offset] = (byte) (nodeId[offset] | 1);
+       }
+
        private static String zeroTo64Chars(String str) {
                assert str.length() <= 64;
                if (str.length() < 64) {
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AsyncUuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AsyncUuidFactory.java
new file mode 100644 (file)
index 0000000..97751a6
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.api.acr.uuid;
+
+import java.util.UUID;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ForkJoinTask;
+
+/** A {@link UUID} factory which creates the UUIDs asynchronously. */
+public interface AsyncUuidFactory extends UuidFactory {
+       /*
+        * TIME-BASED (version 1)
+        */
+       CompletionStage<UUID> requestTimeUUID();
+
+       CompletionStage<UUID> requestTimeUUIDwithMacAddress();
+
+       ForkJoinTask<UUID> futureTimeUUID();
+
+       ForkJoinTask<UUID> futureTimeUUIDwithMacAddress();
+
+       /*
+        * NAME BASED (version 3 and 5)
+        */
+       CompletionStage<UUID> requestNameUUIDv5(UUID namespace, byte[] data);
+
+       CompletionStage<UUID> requestNameUUIDv3(UUID namespace, byte[] data);
+
+       ForkJoinTask<UUID> futureNameUUIDv5(UUID namespace, byte[] data);
+
+       ForkJoinTask<UUID> futureNameUUIDv3(UUID namespace, byte[] data);
+
+       /*
+        * RANDOM (version 4)
+        */
+       CompletionStage<UUID> requestRandomUUIDStrong();
+
+       ForkJoinTask<UUID> futureRandomUUIDStrong();
+
+       /*
+        * DEFAULTS
+        */
+       @Override
+       default UUID randomUUIDStrong() {
+               return futureRandomUUIDStrong().invoke();
+       }
+
+       @Override
+       default UUID timeUUID() {
+               return futureTimeUUID().invoke();
+       }
+
+       @Override
+       default UUID timeUUIDwithMacAddress() {
+               return futureTimeUUIDwithMacAddress().invoke();
+       }
+
+       @Override
+       default UUID nameUUIDv5(UUID namespace, byte[] data) {
+               return futureNameUUIDv5(namespace, data).invoke();
+       }
+
+       @Override
+       default UUID nameUUIDv3(UUID namespace, byte[] data) {
+               return futureNameUUIDv3(namespace, data).invoke();
+       }
+}
index d83356b5cd3240629f9ae358c172bab93c0d0c8f..db72a4b21d72ac58e7eee9bbd65ce7e12d85dabb 100644 (file)
@@ -11,6 +11,14 @@ import java.util.Objects;
  * different clock sequences for each thread.
  */
 public class ConcurrentTimeUuidState implements TimeUuidState {
+//     public final static ThreadLocal<Boolean> isTimeUuidThread = new ThreadLocal<>() {
+//
+//             @Override
+//             protected Boolean initialValue() {
+//                     return false;
+//             }
+//     };
+
        /** The maximum possible value of the clocksequence. */
        private final static int MAX_CLOCKSEQUENCE = 16384;
 
@@ -46,6 +54,7 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
                                Holder value = new Holder();
                                value.lastTimestamp = startTimeStamp;
                                value.clockSequence = newClockSequence();
+//                             isTimeUuidThread.set(true);
                                return value;
                        }
                };
@@ -56,6 +65,7 @@ public class ConcurrentTimeUuidState implements TimeUuidState {
         */
 
        public long useTimestamp() {
+
                long previousTimestamp = holder.get().lastTimestamp;
                long now = computeNow();
 
index 2d522fa98d42db819ed09ee3ab095b7366d4c51d..cffd446bd29adbf853eacbb1d2e4caa9e2e6402b 100644 (file)
@@ -8,7 +8,6 @@ import java.security.DrbgParameters;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.time.Clock;
-import java.util.Random;
 import java.util.UUID;
 
 /**
@@ -17,19 +16,29 @@ import java.util.UUID;
  * 
  * @see https://datatracker.ietf.org/doc/html/rfc4122
  */
-public class SimpleUuidFactory extends AbstractUuidFactory {
+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 SecureRandom secureRandom;
-       private final TimeUuidState timeUuidState;
-
 //     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()));
@@ -42,17 +51,7 @@ public class SimpleUuidFactory extends AbstractUuidFactory {
                                secureRandom = new SecureRandom();
                        }
                }
-
-               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");
-
-               timeUuidState = new ConcurrentTimeUuidState(secureRandom, clock);
+               return secureRandom;
        }
 
        /*
@@ -60,43 +59,30 @@ public class SimpleUuidFactory extends AbstractUuidFactory {
         */
 
        @Override
-       public UUID timeUUIDwithMacAddress() {
+       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 timeUUID() {
+       public UUID newTimeUUID() {
                return newTimeUUID(timeUuidState.useTimestamp(), timeUuidState.getClockSequence(), defaultNodeId, 0);
        }
 
-       /*
-        * NAME BASED (version 3 and 5)
-        */
-       @Override
-       public UUID nameUUIDv5(UUID namespace, byte[] name) {
-               return newNameUUIDv5(namespace, name);
-       }
-
-       @Override
-       public UUID nameUUIDv3(UUID namespace, byte[] name) {
-               return newNameUUIDv3(namespace, name);
-       }
-
        /*
         * RANDOM v4
         */
-       @Override
-       public UUID randomUUID(Random random) {
-               return newRandomUUID(random);
-       }
-
-       @Override
-       public UUID randomUUID() {
-               return randomUUID(secureRandom);
-       }
+//     @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;
index e99cd71ebe42f7135fdc8ad6d981c768f643ede3..94e5158df07a4f8ba47b1f71e349fbc5c51e3613 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.api.acr.uuid;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import java.util.Random;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Supplier;
@@ -28,35 +28,44 @@ public interface UuidFactory extends Supplier<UUID> {
         * NAME BASED (version 3 and 5)
         */
 
-       UUID nameUUIDv5(UUID namespace, byte[] name);
+       UUID nameUUIDv5(UUID namespace, byte[] data);
 
-       UUID nameUUIDv3(UUID namespace, byte[] name);
+       UUID nameUUIDv3(UUID namespace, byte[] data);
 
        default UUID nameUUIDv5(UUID namespace, String name) {
-               if (name == null)
-                       throw new IllegalArgumentException("Name cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
                return nameUUIDv5(namespace, name.getBytes(UTF_8));
        }
 
        default UUID nameUUIDv3(UUID namespace, String name) {
-               if (name == null)
-                       throw new IllegalArgumentException("Name cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
                return nameUUIDv3(namespace, name.getBytes(UTF_8));
        }
 
        /*
-        * RANDOM v4
+        * RANDOM (version 4)
         */
-       UUID randomUUID(Random random);
+       /** A random UUID at least as good as {@link UUID#randomUUID()}. */
+       UUID randomUUIDStrong();
 
-       default UUID randomUUID() {
-               return UUID.randomUUID();
-       }
+       /**
+        * An {@link UUID} generated based on {@link ThreadLocalRandom}. Implementations
+        * should always provide it synchronously.
+        */
+       UUID randomUUIDWeak();
 
-       default UUID randomUUIDWeak() {
-               return randomUUID(ThreadLocalRandom.current());
+       /**
+        * The default random {@link UUID} (v4) generator to use. This default
+        * implementation returns {@link #randomUUIDStrong()}.
+        */
+       default UUID randomUUID() {
+               return randomUUIDStrong();
        }
 
+       /**
+        * The default {@link UUID} to provide, either random (v4) or time based (v1).
+        * This default implementations returns {@link #randomUUID()}.
+        */
        @Override
        default UUID get() {
                return randomUUID();