1 package org
.argeo
.api
.uuid
;
3 import static java
.lang
.System
.Logger
.Level
.DEBUG
;
4 import static java
.lang
.System
.Logger
.Level
.WARNING
;
6 import java
.lang
.System
.Logger
;
7 import java
.security
.SecureRandom
;
8 import java
.time
.Clock
;
9 import java
.time
.Duration
;
10 import java
.time
.Instant
;
11 import java
.util
.Collections
;
13 import java
.util
.Objects
;
15 import java
.util
.WeakHashMap
;
16 import java
.util
.concurrent
.atomic
.AtomicLong
;
19 * A simple base implementation of {@link TimeUuidState}, which maintains
20 * different clock sequences for each thread.
22 public class ConcurrentTimeUuidState
implements TimeUuidState
{
23 private final static Logger logger
= System
.getLogger(ConcurrentTimeUuidState
.class.getName());
25 /** The maximum possible value of the clocksequence. */
26 private final static int MAX_CLOCKSEQUENCE
= 16384;
28 private final ClockSequenceProvider clockSequenceProvider
;
29 private final ThreadLocal
<ConcurrentTimeUuidState
.Holder
> currentHolder
;
31 private final Instant startInstant
;
32 /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
33 private final long startTimeStamp
;
35 private final Clock clock
;
36 private final boolean useClockForMeasurement
;
38 private long nodeIdBase
;
40 public ConcurrentTimeUuidState(SecureRandom secureRandom
, Clock clock
) {
41 useClockForMeasurement
= clock
!= null;
42 this.clock
= clock
!= null ? clock
: Clock
.systemUTC();
44 Objects
.requireNonNull(secureRandom
);
46 // compute the start reference
47 startInstant
= Instant
.now(this.clock
);
49 Duration duration
= Duration
.between(TimeUuidState
.GREGORIAN_START
, startInstant
);
50 startTimeStamp
= durationToUuidTimestamp(duration
) - nowVm
;
52 clockSequenceProvider
= new ClockSequenceProvider(secureRandom
);
54 // initalise a state per thread
55 currentHolder
= new ThreadLocal
<>() {
58 protected ConcurrentTimeUuidState
.Holder
initialValue() {
59 ConcurrentTimeUuidState
.Holder value
= new ConcurrentTimeUuidState
.Holder();
60 value
.threadId
= Thread
.currentThread().getId();
61 value
.lastTimestamp
= 0;
62 clockSequenceProvider
.newClockSequence(value
);
72 public long useTimestamp() {
73 Holder holder
= currentHolder
.get();
74 if (holder
.clockSequence
< 0) {
75 clockSequenceProvider
.newClockSequence(holder
);
78 long previousTimestamp
= holder
.lastTimestamp
;
79 long now
= computeNow();
81 // rare case where we are sooner
82 // (e.g. if system time has changed in between and we use the clock)
83 if (previousTimestamp
> now
) {
84 clockSequenceProvider
.newClockSequence(holder
);
87 // very unlikely case where it took less than 100ns between both
88 if (previousTimestamp
== now
) {
91 } catch (InterruptedException e
) {
95 assert previousTimestamp
!= now
;
97 holder
.lastTimestamp
= now
;
101 private long computeNow() {
102 if (useClockForMeasurement
) {
103 Duration duration
= Duration
.between(TimeUuidState
.GREGORIAN_START
, Instant
.now(clock
));
104 return durationToUuidTimestamp(duration
);
106 return startTimeStamp
+ nowVm();
110 private long nowVm() {
111 return System
.nanoTime() / 100;
114 private long durationToUuidTimestamp(Duration duration
) {
115 return (duration
.getSeconds() * 10000000 + duration
.getNano() / 100);
119 public long getClockSequence() {
120 return currentHolder
.get().clockSequence
;
124 public long getLastTimestamp() {
125 return currentHolder
.get().lastTimestamp
;
128 public void reset(long nodeIdBase
) {
129 synchronized (clockSequenceProvider
) {
130 this.nodeIdBase
= nodeIdBase
;
131 clockSequenceProvider
.reset();
132 clockSequenceProvider
.notifyAll();
137 public long getLeastSignificantBits() {
138 return currentHolder
.get().leastSig
;
142 public long getMostSignificantBits() {
143 long timestamp
= useTimestamp();
144 long mostSig
= MOST_SIG_VERSION1
// base for version 1 UUID
145 | ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
146 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
147 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
155 private class Holder
{
156 private long lastTimestamp
;
157 private long clockSequence
;
158 private long threadId
;
160 private long leastSig
;
163 public boolean equals(Object obj
) {
164 boolean isItself
= this == obj
;
165 if (!isItself
&& clockSequence
== ((Holder
) obj
).clockSequence
)
166 throw new IllegalStateException("There is another holder with the same clockSequence " + clockSequence
);
170 private void setClockSequence(long clockSequence
) {
171 this.clockSequence
= clockSequence
;
172 // if (nodeIdBase == null)
173 // throw new IllegalStateException("Node id base is not initialised");
174 this.leastSig
= nodeIdBase
// already computed node base
175 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
176 | ((clockSequence
& 0xFF) << 48); // clk_seq_low
180 public String
toString() {
181 return "Holder " + clockSequence
+ ", threadId=" + threadId
+ ", lastTimestamp=" + lastTimestamp
;
186 private static class ClockSequenceProvider
{
187 private int rangeSize
= 256;
188 private volatile int min
;
189 private volatile int max
;
190 private final AtomicLong counter
= new AtomicLong(-1);
192 private final SecureRandom secureRandom
;
194 private final Map
<Holder
, Long
> activeHolders
= Collections
.synchronizedMap(new WeakHashMap
<>());
196 ClockSequenceProvider(SecureRandom secureRandom
) {
197 this.secureRandom
= secureRandom
;
201 synchronized void reset() {
202 int min
= secureRandom
.nextInt(ConcurrentTimeUuidState
.MAX_CLOCKSEQUENCE
- rangeSize
);
203 int max
= min
+ rangeSize
;
205 throw new IllegalArgumentException("Minimum " + min
+ " is bigger than maximum " + max
);
206 if (min
< 0 || min
> MAX_CLOCKSEQUENCE
)
207 throw new IllegalArgumentException("Minimum " + min
+ " is not valid");
208 if (max
< 0 || max
> MAX_CLOCKSEQUENCE
)
209 throw new IllegalArgumentException("Maximum " + max
+ " is not valid");
213 Set
<Holder
> active
= activeHolders
.keySet();
214 int activeCount
= active
.size();
215 if (activeCount
> getRangeSize())
216 throw new IllegalStateException(
217 "There are too many holders for range [" + min
+ "," + max
+ "] : " + activeCount
);
220 for (Holder holder
: active
) {
221 // save old clocksequence?
222 newClockSequence(holder
);
226 private synchronized int getRangeSize() {
230 private synchronized void newClockSequence(Holder holder
) {
231 // Too many holders, we will remove the oldes ones
232 while (activeHolders
.size() > rangeSize
) {
233 long oldestTimeStamp
= -1;
234 Holder holderToRemove
= null;
235 holders
: for (Holder h
: activeHolders
.keySet()) {
236 if (h
== holder
)// skip the caller
239 if (oldestTimeStamp
< 0) {
240 oldestTimeStamp
= h
.lastTimestamp
;
243 if (h
.lastTimestamp
<= oldestTimeStamp
) {
244 oldestTimeStamp
= h
.lastTimestamp
;
249 assert holderToRemove
!= null;
250 long oldClockSequence
= holderToRemove
.clockSequence
;
251 holderToRemove
.clockSequence
= -1;
252 activeHolders
.remove(holderToRemove
);
253 if (logger
.isLoggable(WARNING
))
254 logger
.log(WARNING
, "Removed " + holderToRemove
+ ", oldClockSequence=" + oldClockSequence
);
257 long newClockSequence
= -1;
258 int tryCount
= 0;// an explicit exit condition
261 if (tryCount
>= rangeSize
)
262 throw new IllegalStateException("No more clock sequence available");
264 newClockSequence
= counter
.incrementAndGet();
265 assert newClockSequence
>= 0 : "Clock sequence cannot be negative";
266 if (newClockSequence
> max
) {
268 newClockSequence
= min
;
269 counter
.set(newClockSequence
);
271 } while (activeHolders
.containsValue(newClockSequence
));
272 // TODO use an iterator to check the values
273 holder
.setClockSequence(newClockSequence
);
274 activeHolders
.put(holder
, newClockSequence
);
275 if (logger
.isLoggable(DEBUG
))
277 "New clocksequence " + newClockSequence
+ " for thread " + Thread
.currentThread().getId());