From 0f8bddc982e2ea4f260e1eb639777b74e7893ea3 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 26 Jan 2022 10:16:09 +0100 Subject: [PATCH] Improve clock sequence range configuration --- .../api/uuid/AbstractAsyncUuidFactory.java | 17 +++- .../api/uuid/ConcurrentTimeUuidState.java | 91 ++++++++++++++----- .../argeo/api/uuid/ConcurrentUuidFactory.java | 8 +- .../argeo/api/uuid/MacAddressUuidFactory.java | 6 +- .../src/org/argeo/cms/acr/CmsUuidFactory.java | 2 +- 5 files changed, 92 insertions(+), 32 deletions(-) diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java index 38327223f..52becc85a 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java @@ -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 */ 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 25ba62b03..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 @@ -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 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); } } 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 index 15245b293..264e04706 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java @@ -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); } /** 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 index 9f27fad84..fa68df1db 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java @@ -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() { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java index 3ce27fce1..7270c0896 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java @@ -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]; } -- 2.30.2