From: Mathieu Baudier Date: Sun, 23 Jan 2022 13:26:56 +0000 (+0100) Subject: Asynchronous UUID factory. X-Git-Tag: argeo-commons-2.3.5~70 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=8b201d419e0de2f0df848e47dd4b48a2a10c711b Asynchronous UUID factory. --- 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 index 000000000..09becbaec --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractAsyncUuidFactory.java @@ -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 request(ForkJoinTask newUuid) { + return CompletableFuture.supplyAsync(newUuid::invoke).minimalCompletionStage(); + } + + @Override + public CompletionStage requestNameUUIDv5(UUID namespace, byte[] data) { + return request(futureNameUUIDv5(namespace, data)); + } + + @Override + public CompletionStage requestNameUUIDv3(UUID namespace, byte[] data) { + return request(futureNameUUIDv3(namespace, data)); + } + + @Override + public CompletionStage requestRandomUUIDStrong() { + return request(futureRandomUUIDStrong()); + } + + @Override + public CompletionStage requestTimeUUID() { + return request(futureTimeUUID()); + } + + @Override + public CompletionStage requestTimeUUIDwithMacAddress() { + return request(futureTimeUUIDwithMacAddress()); + } + + /* + * ASYNC OPERATIONS (light) + */ + protected ForkJoinTask submit(Callable newUuid) { + return ForkJoinTask.adapt(newUuid); + } + + @Override + public ForkJoinTask futureNameUUIDv5(UUID namespace, byte[] data) { + return submit(() -> newNameUUIDv5(namespace, data)); + } + + @Override + public ForkJoinTask futureNameUUIDv3(UUID namespace, byte[] data) { + return submit(() -> newNameUUIDv3(namespace, data)); + } + + @Override + public ForkJoinTask futureRandomUUIDStrong() { + return submit(this::newRandomUUIDStrong); + } + + @Override + public ForkJoinTask futureTimeUUID() { + return submit(this::newTimeUUID); + } + + @Override + public ForkJoinTask 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(); +// } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java index daa186f28..1ecb64e12 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java @@ -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 index 000000000..97751a6b0 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AsyncUuidFactory.java @@ -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 requestTimeUUID(); + + CompletionStage requestTimeUUIDwithMacAddress(); + + ForkJoinTask futureTimeUUID(); + + ForkJoinTask futureTimeUUIDwithMacAddress(); + + /* + * NAME BASED (version 3 and 5) + */ + CompletionStage requestNameUUIDv5(UUID namespace, byte[] data); + + CompletionStage requestNameUUIDv3(UUID namespace, byte[] data); + + ForkJoinTask futureNameUUIDv5(UUID namespace, byte[] data); + + ForkJoinTask futureNameUUIDv3(UUID namespace, byte[] data); + + /* + * RANDOM (version 4) + */ + CompletionStage requestRandomUUIDStrong(); + + ForkJoinTask 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(); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/ConcurrentTimeUuidState.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/ConcurrentTimeUuidState.java index d83356b5c..db72a4b21 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/ConcurrentTimeUuidState.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/ConcurrentTimeUuidState.java @@ -11,6 +11,14 @@ import java.util.Objects; * different clock sequences for each thread. */ public class ConcurrentTimeUuidState implements TimeUuidState { +// public final static ThreadLocal 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(); diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/SimpleUuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/SimpleUuidFactory.java index 2d522fa98..cffd446bd 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/SimpleUuidFactory.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/SimpleUuidFactory.java @@ -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 { // private byte[] source; // private int offset; diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/UuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/UuidFactory.java index e99cd71eb..94e5158df 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/UuidFactory.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/UuidFactory.java @@ -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 { * 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();