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
.Objects
;
13 import java
.util
.WeakHashMap
;
14 import java
.util
.concurrent
.atomic
.AtomicInteger
;
17 * A simple base implementation of {@link TimeUuidState}, which maintains
18 * different clock sequences for each thread.
20 public class ConcurrentTimeUuidState
implements TimeUuidState
{
21 private final static Logger logger
= System
.getLogger(ConcurrentTimeUuidState
.class.getName());
23 /** The maximum possible value of the clocksequence. */
24 private final static int MAX_CLOCKSEQUENCE
= 16384;
26 private final ClockSequenceProvider clockSequenceProvider
;
27 private final ThreadLocal
<ConcurrentTimeUuidState
.Holder
> currentHolder
;
29 private final Instant startInstant
;
30 /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
31 private final long startTimeStamp
;
33 private final Clock clock
;
34 private final boolean useClockForMeasurement
;
36 public ConcurrentTimeUuidState(SecureRandom secureRandom
, Clock clock
) {
37 useClockForMeasurement
= clock
!= null;
38 this.clock
= clock
!= null ? clock
: Clock
.systemUTC();
40 Objects
.requireNonNull(secureRandom
);
42 // compute the start reference
43 startInstant
= Instant
.now(this.clock
);
45 Duration duration
= Duration
.between(TimeUuidState
.GREGORIAN_START
, startInstant
);
46 startTimeStamp
= durationToUuidTimestamp(duration
) - nowVm
;
48 clockSequenceProvider
= new ClockSequenceProvider(secureRandom
);
50 // initalise a state per thread
51 currentHolder
= new ThreadLocal
<>() {
54 protected ConcurrentTimeUuidState
.Holder
initialValue() {
55 ConcurrentTimeUuidState
.Holder value
= new ConcurrentTimeUuidState
.Holder();
56 value
.threadId
= Thread
.currentThread().getId();
57 value
.lastTimestamp
= startTimeStamp
;
58 clockSequenceProvider
.newClockSequence(value
);
68 public long useTimestamp() {
69 Holder holder
= currentHolder
.get();
70 if (holder
.clockSequence
< 0) {
71 clockSequenceProvider
.newClockSequence(holder
);
74 long previousTimestamp
= holder
.lastTimestamp
;
75 long now
= computeNow();
77 // rare case where we are sooner
78 // (e.g. if system time has changed in between and we use the clock)
79 if (previousTimestamp
> now
) {
80 clockSequenceProvider
.newClockSequence(holder
);
83 // very unlikely case where it took less than 100ns between both
84 if (previousTimestamp
== now
) {
87 } catch (InterruptedException e
) {
91 assert previousTimestamp
!= now
;
93 holder
.lastTimestamp
= now
;
97 private long computeNow() {
98 if (useClockForMeasurement
) {
99 Duration duration
= Duration
.between(TimeUuidState
.GREGORIAN_START
, Instant
.now(clock
));
100 return durationToUuidTimestamp(duration
);
102 return startTimeStamp
+ nowVm();
106 private long nowVm() {
107 return System
.nanoTime() / 100;
110 private long durationToUuidTimestamp(Duration duration
) {
111 return (duration
.getSeconds() * 10000000 + duration
.getNano() / 100);
115 public long getClockSequence() {
116 return (long) currentHolder
.get().clockSequence
;
119 private static class Holder
{
120 private long lastTimestamp
;
121 private int clockSequence
;
122 private long threadId
;
125 public boolean equals(Object obj
) {
126 boolean isItself
= this == obj
;
127 if (!isItself
&& clockSequence
== ((Holder
) obj
).clockSequence
)
128 throw new IllegalStateException("There is another holder with the same clockSequence " + clockSequence
);
132 private synchronized void setClockSequence(int clockSequence
) {
133 this.clockSequence
= clockSequence
;
137 public String
toString() {
138 return "Holder " + clockSequence
+ ", threadId=" + threadId
+ ", lastTimestamp=" + lastTimestamp
;
143 private static class ClockSequenceProvider
{
144 private int rangeSize
= 256;
145 private volatile int min
;
146 private volatile int max
;
147 private final AtomicInteger counter
= new AtomicInteger(-1);
149 private final SecureRandom secureRandom
;
151 private final WeakHashMap
<Holder
, Integer
> activeHolders
= new WeakHashMap
<>();
153 ClockSequenceProvider(SecureRandom secureRandom
) {
154 this.secureRandom
= secureRandom
;
158 synchronized void reset() {
159 int min
= secureRandom
.nextInt(ConcurrentTimeUuidState
.MAX_CLOCKSEQUENCE
);
160 int max
= min
+ rangeSize
;
162 throw new IllegalArgumentException("Minimum " + min
+ " is bigger than maximum " + max
);
163 if (min
< 0 || min
> MAX_CLOCKSEQUENCE
)
164 throw new IllegalArgumentException("Minimum " + min
+ " is not valid");
165 if (max
< 0 || max
> MAX_CLOCKSEQUENCE
)
166 throw new IllegalArgumentException("Maximum " + max
+ " is not valid");
170 Set
<Holder
> active
= activeHolders
.keySet();
171 int activeCount
= active
.size();
172 if (activeCount
> getRangeSize())
173 throw new IllegalStateException(
174 "There are too many holders for range [" + min
+ "," + max
+ "] : " + activeCount
);
177 for (Holder holder
: active
) {
178 // save old clocksequence?
179 newClockSequence(holder
);
183 private synchronized int getRangeSize() {
187 private synchronized void newClockSequence(Holder holder
) {
188 // int activeCount = activeHolders.size();
189 while (activeHolders
.size() > rangeSize
) {
190 // throw new IllegalStateException(
191 // "There are too many holders for range [" + min + "," + max + "] : " + activeCount);
193 long oldestTimeStamp
= -1;
194 Holder holderToRemove
= null;
195 holders
: for (Holder h
: activeHolders
.keySet()) {
196 if (h
== holder
)// skip the caller
199 if (oldestTimeStamp
< 0) {
200 oldestTimeStamp
= h
.lastTimestamp
;
203 if (h
.lastTimestamp
<= oldestTimeStamp
) {
204 oldestTimeStamp
= h
.lastTimestamp
;
209 assert holderToRemove
!= null;
210 long oldClockSequence
= holderToRemove
.clockSequence
;
211 holderToRemove
.clockSequence
= -1;
212 activeHolders
.remove(holderToRemove
);
213 if (logger
.isLoggable(WARNING
))
214 logger
.log(WARNING
, "Removed " + holderToRemove
+ ", oldClockSequence=" + oldClockSequence
);
217 int newClockSequence
= -1;
218 int tryCount
= 0;// an explicit exit condition
221 if (tryCount
>= rangeSize
)
222 throw new IllegalStateException("No more clock sequence available");
224 newClockSequence
= counter
.incrementAndGet();
225 assert newClockSequence
>= 0 : "Clock sequence cannot be negative";
226 if (newClockSequence
> max
) {
228 newClockSequence
= min
;
229 counter
.set(newClockSequence
);
231 } while (activeHolders
.containsValue(newClockSequence
));
232 // TODO use an iterator to check the values
233 holder
.setClockSequence(newClockSequence
);
234 activeHolders
.put(holder
, newClockSequence
);
235 if (logger
.isLoggable(DEBUG
))
237 "New clocksequence " + newClockSequence
+ " for thread " + Thread
.currentThread().getId());