Skip to content

Commit 9e838df

Browse files
committed
Lock-free BlockingIdentifierGenerator LoHi
1 parent 27c87c7 commit 9e838df

File tree

1 file changed

+59
-30
lines changed

1 file changed

+59
-30
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java

+59-30
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Objects;
1818
import java.util.concurrent.CompletableFuture;
1919
import java.util.concurrent.CompletionStage;
20+
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
2021

2122
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
2223

@@ -56,22 +57,55 @@ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierG
5657
//to reason about what the current state is and what the CombinerExecutor is
5758
//supposed to work on.
5859
private static class GeneratorState {
59-
private int loValue;
60-
private long hiValue;
60+
61+
private static final class LoHi {
62+
63+
private static final AtomicIntegerFieldUpdater<LoHi> LO_UPDATER = AtomicIntegerFieldUpdater.newUpdater(LoHi.class, "lo");
64+
private final long hi;
65+
private volatile long lo;
66+
67+
LoHi(long hi) {
68+
this.hi = hi;
69+
this.lo = 1;
70+
}
71+
72+
public long next(int blockSize) {
73+
if (lo >= blockSize) {
74+
return -1;
75+
}
76+
final long nextLo = LO_UPDATER.getAndIncrement(this);
77+
if (nextLo < blockSize) {
78+
return hi + nextLo;
79+
}
80+
return -1;
81+
}
82+
}
83+
84+
private volatile LoHi loHi;
85+
86+
public long hi(long hi) {
87+
loHi = new LoHi(hi);
88+
return hi;
89+
}
90+
91+
public long next(int blockSize) {
92+
final LoHi loHi = this.loHi;
93+
if (loHi == null) {
94+
return -1;
95+
}
96+
return loHi.next(blockSize);
97+
}
6198
}
6299

63100
//Critical section: needs to be accessed exclusively via the CombinerExecutor
64101
//when there's contention; direct invocation is allowed in the fast path.
65-
private synchronized long next() {
66-
return state.loValue > 0 && state.loValue < getBlockSize()
67-
? state.hiValue + state.loValue++
68-
: -1; //flag value indicating that we need to hit db
102+
private long next() {
103+
return state.next(getBlockSize());
69104
}
70105

71106
//Critical section: needs to be accessed exclusively via the CombinerExecutor
72-
private synchronized long next(long hi) {
73-
state.hiValue = hi;
74-
state.loValue = 1;
107+
private long next(long hi) {
108+
state.hi(hi);
75109
return hi;
76110
}
77111

@@ -90,8 +124,7 @@ public CompletionStage<Long> generate(ReactiveConnectionSupplier connectionSuppl
90124
//if it were to happen we should be better off with direct execution rather than using
91125
//the co-operative executor:
92126
if ( getBlockSize() <= 1 ) {
93-
return nextHiValue( connectionSupplier )
94-
.thenApply( i -> next( i ) );
127+
return nextHiValue( connectionSupplier ).thenApply( i -> next( i ) );
95128
}
96129

97130
final CompletableFuture<Long> resultForThisEventLoop = new CompletableFuture<>();
@@ -108,8 +141,7 @@ public CompletionStage<Long> generate(ReactiveConnectionSupplier connectionSuppl
108141
} else {
109142
context.runOnContext( ( v ) -> resultForThisEventLoop.complete( id ) );
110143
}
111-
}
112-
else {
144+
} else {
113145
if ( t != null ) {
114146
resultForThisEventLoop.completeExceptionally( t );
115147
} else {
@@ -137,32 +169,29 @@ public Task execute(GeneratorState state) {
137169
// We don't need to update or initialize the hi
138170
// value in the table, so just increment the lo
139171
// value and return the next id in the block
140-
completedFuture( local )
141-
.whenComplete( this::acceptAsReturnValue );
172+
completedFuture( local ).whenComplete( this::acceptAsReturnValue );
142173
return null;
143174
} else {
144-
nextHiValue( connectionSupplier )
145-
.whenComplete( (newlyGeneratedHi, throwable) -> {
146-
if ( throwable != null ) {
147-
result.completeExceptionally( throwable );
148-
} else {
149-
//We ignore the state argument as we actually use the field directly
150-
//for convenience, but they are the same object.
151-
executor.submit( stateIgnored -> {
152-
result.complete( next( newlyGeneratedHi ) );
153-
return null;
154-
});
155-
}
156-
} );
175+
nextHiValue( connectionSupplier ).whenComplete( (newlyGeneratedHi, throwable) -> {
176+
if ( throwable != null ) {
177+
result.completeExceptionally( throwable );
178+
} else {
179+
//We ignore the state argument as we actually use the field directly
180+
//for convenience, but they are the same object.
181+
executor.submit( stateIgnored -> {
182+
result.complete( next( newlyGeneratedHi ) );
183+
return null;
184+
});
185+
}
186+
} );
157187
return null;
158188
}
159189
}
160190

161191
private void acceptAsReturnValue(final Long aLong, final Throwable throwable) {
162192
if ( throwable != null ) {
163193
result.completeExceptionally( throwable );
164-
}
165-
else {
194+
} else {
166195
result.complete( aLong );
167196
}
168197
}

0 commit comments

Comments
 (0)