1 package org
.argeo
.api
.uuid
;
3 import java
.security
.SecureRandom
;
4 import java
.time
.Clock
;
5 import java
.time
.Duration
;
6 import java
.time
.Instant
;
7 import java
.util
.Collections
;
9 import java
.util
.Objects
;
11 import java
.util
.WeakHashMap
;
12 import java
.util
.concurrent
.atomic
.AtomicLong
;
14 import org
.argeo
.api
.uuid
.UuidFactory
.TimeUuidState
;
17 * A simple base implementation of {@link TimeUuidState}, which maintains
18 * different clock sequences for each thread, based on a specified range. This
19 * range is defined as clock_seq_hi (cf. RFC4122) and only clock_seq_low is
20 * dynamically allocated. It means that there can be at most 256 parallel clock
21 * sequences. If that limit is reached, the clock sequence which has not be used
22 * for the most time is reallocated to the new thread. It is assumed that the
23 * context where time uUIDs will be generated will often be using thread pools
24 * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.)
25 * and that such reallocation won't have to happen too often.
27 public class ConcurrentTimeUuidState
implements UuidFactory
.TimeUuidState
{
28 // private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
30 /** The maximum possible value of the clocksequence. */
31 private final static int MAX_CLOCKSEQUENCE
= 0x3F00;
33 private final ClockSequenceProvider clockSequenceProvider
;
34 private final ThreadLocal
<ConcurrentTimeUuidState
.Holder
> currentHolder
;
36 private final Instant startInstant
;
37 /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
38 private final long startTimeStamp
;
40 private final Clock clock
;
41 private final boolean useClockForMeasurement
;
43 private long nodeIdBase
;
45 public ConcurrentTimeUuidState(SecureRandom secureRandom
, Clock clock
) {
46 useClockForMeasurement
= clock
!= null;
47 this.clock
= clock
!= null ? clock
: Clock
.systemUTC();
49 Objects
.requireNonNull(secureRandom
);
51 // compute the start reference
52 startInstant
= Instant
.now(this.clock
);
54 Duration duration
= Duration
.between(TimeUuid
.TIMESTAMP_ZERO
, startInstant
);
55 startTimeStamp
= TimeUuid
.durationToTimestamp(duration
) - nowVm
;
57 clockSequenceProvider
= new ClockSequenceProvider(secureRandom
);
59 // initalise a state per thread
60 currentHolder
= new ThreadLocal
<>() {
63 protected ConcurrentTimeUuidState
.Holder
initialValue() {
64 ConcurrentTimeUuidState
.Holder value
= new ConcurrentTimeUuidState
.Holder();
65 value
.threadId
= Thread
.currentThread().getId();
66 value
.lastTimestamp
= 0;
67 clockSequenceProvider
.newClockSequence(value
);
77 public long useTimestamp() {
78 Holder holder
= currentHolder
.get();
79 if (holder
.clockSequence
< 0) {
80 clockSequenceProvider
.newClockSequence(holder
);
83 long previousTimestamp
= holder
.lastTimestamp
;
84 long now
= computeNow();
86 // rare case where we are sooner
87 // (e.g. if system time has changed in between and we use the clock)
88 if (previousTimestamp
> now
) {
89 clockSequenceProvider
.newClockSequence(holder
);
92 // very unlikely case where it took less than 100ns between both
93 if (previousTimestamp
== now
) {
96 } catch (InterruptedException e
) {
100 assert previousTimestamp
!= now
;
102 holder
.lastTimestamp
= now
;
106 private long computeNow() {
107 if (useClockForMeasurement
) {
108 Duration duration
= Duration
.between(TimeUuid
.TIMESTAMP_ZERO
, Instant
.now(clock
));
109 return TimeUuid
.durationToTimestamp(duration
);
111 return startTimeStamp
+ nowVm();
115 private long nowVm() {
116 return System
.nanoTime() / 100;
120 public long getClockSequence() {
121 return currentHolder
.get().clockSequence
;
125 public long getLastTimestamp() {
126 return currentHolder
.get().lastTimestamp
;
129 protected void reset(long nodeIdBase
, long range
) {
130 synchronized (clockSequenceProvider
) {
131 this.nodeIdBase
= nodeIdBase
;
132 clockSequenceProvider
.reset(range
);
133 clockSequenceProvider
.notifyAll();
138 public long getLeastSignificantBits() {
139 return currentHolder
.get().leastSig
;
143 public long getMostSignificantBits() {
144 long timestamp
= useTimestamp();
145 long mostSig
= UuidFactory
.MOST_SIG_VERSION1
| ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
146 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
147 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
154 private class Holder
{
155 private long lastTimestamp
;
156 private long clockSequence
;
157 private long threadId
;
159 private long leastSig
;
162 public boolean equals(Object obj
) {
163 boolean isItself
= this == obj
;
164 if (!isItself
&& clockSequence
== ((Holder
) obj
).clockSequence
)
165 throw new IllegalStateException("There is another holder with the same clockSequence " + clockSequence
);
169 private void setClockSequence(long clockSequence
) {
170 this.clockSequence
= clockSequence
;
171 this.leastSig
= nodeIdBase
// already computed node base
172 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
173 | ((clockSequence
& 0xFF) << 48); // clk_seq_low
177 public String
toString() {
178 return "Holder " + clockSequence
+ ", threadId=" + threadId
+ ", lastTimestamp=" + lastTimestamp
;
183 private static class ClockSequenceProvider
{
184 /** Set to an illegal value. */
185 private long range
= MAX_CLOCKSEQUENCE
;// this is actually clk_seq_hi
186 // private int rangeSize = 256;
187 private volatile long min
;
188 private volatile long max
;
189 private final AtomicLong counter
= new AtomicLong(-1);
191 private final SecureRandom secureRandom
;
193 private final Map
<Holder
, Long
> activeHolders
= Collections
.synchronizedMap(new WeakHashMap
<>());
195 ClockSequenceProvider(SecureRandom secureRandom
) {
196 this.secureRandom
= secureRandom
;
200 synchronized void reset(long range
) {
201 // long min = secureRandom.nextInt(ConcurrentTimeUuidState.MAX_CLOCKSEQUENCE -
203 // long max = min + rangeSize;
207 if (range
> MAX_CLOCKSEQUENCE
)
208 throw new IllegalArgumentException("Range " + Long
.toHexString(range
) + " is too big");
209 long previousRange
= this.range
;
210 this.range
= range
& 0x3F00;
211 if (this.range
!= range
) {
212 this.range
= previousRange
;
213 throw new IllegalArgumentException(
214 "Range is not properly formatted: " + range
+ " (0x" + Long
.toHexString(range
) + ")");
219 } else {// full range
222 max
= MAX_CLOCKSEQUENCE
;
224 assert min
== (int) min
;
225 assert max
== (int) max
;
227 // TODO rather use assertions
229 throw new IllegalArgumentException("Minimum " + min
+ " is bigger than maximum " + max
);
230 if (min
< 0 || min
> MAX_CLOCKSEQUENCE
)
231 throw new IllegalArgumentException("Minimum " + min
+ " is not valid");
232 if (max
< 0 || max
> MAX_CLOCKSEQUENCE
)
233 throw new IllegalArgumentException("Maximum " + max
+ " is not valid");
237 Set
<Holder
> active
= activeHolders
.keySet();
238 int activeCount
= active
.size();
239 if (activeCount
> getRangeSize())
240 throw new IllegalStateException(
241 "There are too many holders for range [" + min
+ "," + max
+ "] : " + activeCount
);
243 // reset the counter with a random value in range
244 long firstCount
= min
+ secureRandom
.nextInt(getRangeSize());
245 counter
.set(firstCount
);
248 for (Holder holder
: active
) {
249 // save old clocksequence?
250 newClockSequence(holder
);
254 private synchronized void newClockSequence(Holder holder
) {
255 // Too many holders, we will remove the oldes ones
256 while (activeHolders
.size() > getRangeSize()) {
257 long oldestTimeStamp
= -1;
258 Holder holderToRemove
= null;
259 holders
: for (Holder h
: activeHolders
.keySet()) {
260 if (h
== holder
)// skip the caller
263 if (oldestTimeStamp
< 0) {
264 oldestTimeStamp
= h
.lastTimestamp
;
267 if (h
.lastTimestamp
<= oldestTimeStamp
) {
268 oldestTimeStamp
= h
.lastTimestamp
;
273 assert holderToRemove
!= null;
274 // long oldClockSequence = holderToRemove.clockSequence;
275 holderToRemove
.clockSequence
= -1;
276 activeHolders
.remove(holderToRemove
);
277 // if (logger.isLoggable(WARNING))
278 // logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
281 long newClockSequence
= -1;
282 int tryCount
= 0;// an explicit exit condition
285 if (tryCount
>= getRangeSize())
286 throw new IllegalStateException("No more clock sequence available");
288 newClockSequence
= counter
.incrementAndGet();
289 assert newClockSequence
>= 0 : "Clock sequence cannot be negative";
290 if (newClockSequence
> max
) {
292 newClockSequence
= min
;
293 counter
.set(newClockSequence
);
295 } while (activeHolders
.containsValue(newClockSequence
));
296 // TODO use an iterator to check the values
297 holder
.setClockSequence(newClockSequence
);
298 activeHolders
.put(holder
, newClockSequence
);
299 // if (logger.isLoggable(DEBUG)) {
300 // String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
301 // : Long.toHexString(newClockSequence | 0x8000);
302 // String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
303 // logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
304 // + " (in range " + rangeDesc + ")");
308 private synchronized int getRangeSize() {
309 return (int) (max
- min
);