Skip to content

Commit 6daec2e

Browse files
cigalybeikov
authored andcommitted
HHH-18377 Holding state in immutable object to allow atomic calculation and change
1 parent 2e42db8 commit 6daec2e

File tree

2 files changed

+99
-78
lines changed

2 files changed

+99
-78
lines changed

hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion6Strategy.java

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
package org.hibernate.id.uuid;
66

77
import java.security.SecureRandom;
8-
import java.time.Duration;
98
import java.time.Instant;
109
import java.time.LocalDate;
1110
import java.time.ZoneId;
1211
import java.util.UUID;
13-
import java.util.concurrent.atomic.AtomicLong;
14-
import java.util.concurrent.locks.Lock;
15-
import java.util.concurrent.locks.ReentrantLock;
12+
import java.util.concurrent.atomic.AtomicReference;
1613

1714
import org.hibernate.Internal;
1815
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@@ -31,34 +28,61 @@
3128
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
3229
* </ul>
3330
*
34-
* @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality.
35-
*
3631
* @author Cedomir Igaly
32+
* @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality.
3733
*/
3834
public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
3935
public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy();
4036

4137
private static class Holder {
4238
static final SecureRandom numberGenerator = new SecureRandom();
43-
static final Instant EPOCH_1582 = LocalDate.of( 1582, 10, 15 )
39+
static final long EPOCH_1582_SECONDS = LocalDate.of( 1582, 10, 15 )
4440
.atStartOfDay( ZoneId.of( "UTC" ) )
45-
.toInstant();
41+
.toInstant().getEpochSecond();
42+
4643
}
4744

48-
private final Lock lock = new ReentrantLock( true );
49-
private final AtomicLong clockSequence = new AtomicLong( 0 );
50-
private long currentTimestamp;
45+
private record State(long timestamp, int sequence) {
46+
public State getNextState() {
47+
final long now = instantToTimestamp();
48+
if ( this.timestamp < now ) {
49+
return new State(
50+
now,
51+
randomSequence()
52+
);
53+
}
54+
else if ( sequence == 0x3FFF ) {
55+
return new State(
56+
this.timestamp + 1,
57+
randomSequence()
58+
);
59+
}
60+
else {
61+
return new State( timestamp, sequence + 1 );
62+
}
63+
}
5164

65+
private static int randomSequence() {
66+
return Holder.numberGenerator.nextInt( 1 << 14 );
67+
}
68+
69+
private static long instantToTimestamp() {
70+
final Instant instant = Instant.now();
71+
final long seconds = instant.getEpochSecond() - Holder.EPOCH_1582_SECONDS;
72+
return seconds * 10_000_000 + instant.getNano() / 100;
73+
}
74+
}
75+
76+
private final AtomicReference<State> lastState;
5277

5378
@Internal
5479
public UuidVersion6Strategy() {
55-
this( getCurrentTimestamp(), 0 );
80+
this( Long.MIN_VALUE, Integer.MIN_VALUE );
5681
}
5782

5883
@Internal
59-
public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) {
60-
this.currentTimestamp = currentTimestamp;
61-
this.clockSequence.set( clockSequence );
84+
public UuidVersion6Strategy(final long initialTimestamp, final int initialSequence) {
85+
this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) );
6286
}
6387

6488
/**
@@ -70,47 +94,31 @@ public int getGeneratedVersion() {
7094
}
7195

7296
@Override
73-
public UUID generateUUID(SharedSessionContractImplementor session) {
97+
public UUID generateUUID(final SharedSessionContractImplementor session) {
7498
return generateUuid( session );
7599
}
76100

77101
@Override
78-
public UUID generateUuid(SharedSessionContractImplementor session) {
79-
final long currentTimestamp = getCurrentTimestamp();
102+
public UUID generateUuid(final SharedSessionContractImplementor session) {
103+
final State state = lastState.updateAndGet( State::getNextState );
80104

81105
return new UUID(
82106
// MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
83-
currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
107+
state.timestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
84108
// MSB bits 48-51 - version = 6
85109
| 0x6000L
86110
// MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
87-
| currentTimestamp & 0x0FFFL,
111+
| state.timestamp & 0x0FFFL,
88112
// LSB bits 0-1 - variant = 4
89113
0x8000_0000_0000_0000L
90114
// LSB bits 2-15 - clock sequence
91-
| ( getSequence( currentTimestamp ) & 0x3FFFL ) << 48
92-
// LSB bits 16-63 - pseudorandom data
93-
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
115+
| ( state.sequence & 0x3FFFL ) << 48
116+
// LSB bits 16-63 - pseudorandom data, least significant bit of the first octet is set to 1
117+
| randomNode()
94118
);
95119
}
96120

97-
98-
private long getSequence(final long currentTimestamp) {
99-
lock.lock();
100-
try {
101-
if ( this.currentTimestamp < currentTimestamp ) {
102-
this.currentTimestamp = currentTimestamp;
103-
clockSequence.updateAndGet( l -> l & 0x1FFFL );
104-
}
105-
}
106-
finally {
107-
lock.unlock();
108-
}
109-
return clockSequence.getAndIncrement();
110-
}
111-
112-
private static long getCurrentTimestamp() {
113-
final Duration duration = Duration.between( Holder.EPOCH_1582, Instant.now() );
114-
return duration.toSeconds() * 10_000_000 + duration.toNanosPart() / 100;
121+
private static long randomNode() {
122+
return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L;
115123
}
116124
}

hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import java.security.SecureRandom;
88
import java.time.Instant;
99
import java.util.UUID;
10-
import java.util.concurrent.atomic.AtomicLong;
11-
import java.util.concurrent.locks.Lock;
12-
import java.util.concurrent.locks.ReentrantLock;
10+
import java.util.concurrent.atomic.AtomicReference;
1311

1412
import org.hibernate.Internal;
1513
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@@ -32,33 +30,63 @@
3230
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
3331
* </ul>
3432
*
33+
* @author Cedomir Igaly
3534
* @apiNote Version 7 features a time-ordered value field derived from the widely implemented and
3635
* well-known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC,
3736
* leap seconds excluded.
38-
*
39-
* @author Cedomir Igaly
4037
*/
4138
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
4239

4340
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
4441

4542
private static class Holder {
46-
static final SecureRandom numberGenerator = new SecureRandom();
43+
private static final SecureRandom numberGenerator = new SecureRandom();
44+
4745
}
4846

49-
private final Lock lock = new ReentrantLock( true );
50-
private final AtomicLong clockSequence;
51-
private Instant currentTimestamp;
47+
public record State(Instant timestamp, int sequence) {
48+
public long millis() {
49+
return timestamp.toEpochMilli();
50+
}
51+
52+
public long nanos() {
53+
return (long) ( ( timestamp.getNano() % 1_000_000L ) * 0.004096 );
54+
}
55+
56+
public State getNextState() {
57+
final Instant now = Instant.now();
58+
if ( timestamp.toEpochMilli() < now.toEpochMilli() ) {
59+
return new State(
60+
now.truncatedTo( MILLIS ),
61+
randomSequence()
62+
);
63+
}
64+
else if ( sequence == 0x3FFF ) {
65+
return new State(
66+
timestamp.plusMillis( 1 ),
67+
Holder.numberGenerator.nextInt( 1 << 14 )
68+
);
69+
}
70+
else {
71+
return new State( timestamp, sequence + 1 );
72+
}
73+
}
74+
75+
private static int randomSequence() {
76+
return Holder.numberGenerator.nextInt( 1 << 14 );
77+
}
78+
}
79+
80+
private final AtomicReference<State> lastState;
5281

5382
@Internal
5483
public UuidVersion7Strategy() {
55-
this( Instant.now(), 0 );
84+
this( Instant.EPOCH, Integer.MIN_VALUE );
5685
}
5786

5887
@Internal
59-
public UuidVersion7Strategy(final Instant currentTimestamp, final long clockSequence) {
60-
this.currentTimestamp = currentTimestamp;
61-
this.clockSequence = new AtomicLong( clockSequence );
88+
public UuidVersion7Strategy(final Instant initialTimestamp, final int initialSequence) {
89+
this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) );
6290
}
6391

6492
/**
@@ -70,46 +98,31 @@ public int getGeneratedVersion() {
7098
}
7199

72100
@Override
73-
public UUID generateUUID(SharedSessionContractImplementor session) {
101+
public UUID generateUUID(final SharedSessionContractImplementor session) {
74102
return generateUuid( session );
75103
}
76104

77105
@Override
78-
public UUID generateUuid(SharedSessionContractImplementor session) {
79-
final Instant currentTimestamp = Instant.now();
80-
81-
final long seq = getSequence( currentTimestamp );
82-
83-
final long millis = currentTimestamp.toEpochMilli();
84-
final long nanosPart = (long) ( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 );
106+
public UUID generateUuid(final SharedSessionContractImplementor session) {
107+
final State state = lastState.updateAndGet( State::getNextState );
85108

86109
return new UUID(
87110
// MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds
88-
millis << 16 & 0xFFFF_FFFF_FFFF_0000L
111+
state.millis() << 16 & 0xFFFF_FFFF_FFFF_0000L
89112
// MSB bits 48-51 - version = 7
90113
| 0x7000L
91114
// MSB bits 52-63 - sub-milliseconds part of timestamp
92-
| nanosPart & 0xFFFL,
115+
| state.nanos() & 0xFFFL,
93116
// LSB bits 0-1 - variant = 4
94117
0x8000_0000_0000_0000L
95118
// LSB bits 2-15 - counter
96-
| ( seq & 0x3FFFL ) << 48
119+
| ( state.sequence & 0x3FFFL ) << 48
97120
// LSB bits 16-63 - pseudorandom data
98-
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
121+
| randomNode()
99122
);
100123
}
101124

102-
private long getSequence(final Instant currentTimestamp) {
103-
lock.lock();
104-
try {
105-
if ( this.currentTimestamp.toEpochMilli() < currentTimestamp.toEpochMilli() ) {
106-
this.currentTimestamp = currentTimestamp.truncatedTo( MILLIS );
107-
clockSequence.updateAndGet( l -> l & 0x1FFFL );
108-
}
109-
}
110-
finally {
111-
lock.unlock();
112-
}
113-
return clockSequence.getAndIncrement();
125+
private static long randomNode() {
126+
return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L;
114127
}
115128
}

0 commit comments

Comments
 (0)