Skip to content

Commit 8451fe0

Browse files
committed
HHH-18326 Also reset collection instance ids
1 parent cc76f75 commit 8451fe0

File tree

4 files changed

+49
-16
lines changed

4 files changed

+49
-16
lines changed

hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ the following fields are used in all circumstances, and are not worth (or not su
134134
private InstanceIdentityMap<PersistentCollection<?>, CollectionEntry> collectionEntries;
135135

136136
// Current collection instance id
137-
private transient int currentCollectionInstanceId;
137+
private transient int currentCollectionInstanceId = 1;
138138

139139
// Collection wrappers, by the CollectionKey
140140
private HashMap<CollectionKey, PersistentCollection<?>> collectionsByKey;
@@ -258,7 +258,10 @@ public void clear() {
258258

259259
final SharedSessionContractImplementor session = getSession();
260260
if ( collectionEntries != null ) {
261-
collectionEntries.forEach( (k, v) -> k.unsetSession( session ) );
261+
collectionEntries.forEach( (k, v) -> {
262+
k.$$_hibernate_setInstanceId( 0 );
263+
k.unsetSession( session );
264+
} );
262265
}
263266

264267
arrayHolders = null;
@@ -270,7 +273,7 @@ public void clear() {
270273
collectionsByKey = null;
271274
nonlazyCollections = null;
272275
collectionEntries = null;
273-
currentCollectionInstanceId = 0;
276+
currentCollectionInstanceId = 1;
274277
unownedCollections = null;
275278
nullifiableEntityKeys = null;
276279
deletedUnloadedEntityKeys = null;
@@ -1133,6 +1136,7 @@ private void putCollectionEntry(PersistentCollection<?> collection, CollectionEn
11331136
if ( this.collectionEntries == null ) {
11341137
this.collectionEntries = new InstanceIdentityMap<>();
11351138
}
1139+
assert collection.$$_hibernate_getInstanceId() == 0;
11361140
collection.$$_hibernate_setInstanceId( nextCollectionInstanceId() );
11371141
this.collectionEntries.put( collection, entry );
11381142
}
@@ -2203,7 +2207,9 @@ public int getCollectionEntriesSize() {
22032207
@Override
22042208
public CollectionEntry removeCollectionEntry(PersistentCollection<?> collection) {
22052209
if ( collectionEntries != null ) {
2206-
return collectionEntries.remove( collection.$$_hibernate_getInstanceId(), collection );
2210+
final int instanceId = collection.$$_hibernate_getInstanceId();
2211+
collection.$$_hibernate_setInstanceId( 0 );
2212+
return collectionEntries.remove( instanceId, collection );
22072213
}
22082214
else {
22092215
return null;

hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ else if ( association instanceof PersistentCollection<?> collection ) {
10331033
}
10341034
}
10351035
finally {
1036+
collection.$$_hibernate_setInstanceId( 0 );
10361037
collection.unsetSession( this );
10371038
if ( persistenceContext.isLoadFinished() ) {
10381039
persistenceContext.clear();

hibernate-core/src/main/java/org/hibernate/internal/util/collections/InstanceIdentityMap.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
* but, contrary to the store, it initializes {@link Map.Entry}s eagerly to optimize iteration
2828
* performance and avoid type-pollution issues when checking the type of contained objects.
2929
* <p>
30+
* Instance ids are considered to start from 1, and if two instances are found to have the
31+
* same identifier a {@link java.util.ConcurrentModificationException will be thrown}.
32+
* <p>
3033
* Methods accessing / modifying the map with {@link Object} typed parameters will need
3134
* to type check against the instance identity interface which might be inefficient,
3235
* so it's recommended to use the position (int) based variant of those methods.
@@ -107,9 +110,21 @@ private boolean containsMapping(Object key, Object value) {
107110
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
108111
*/
109112
public @Nullable V get(int instanceId, Object key) {
110-
final Entry<K, V> entry = get( instanceId );
111-
if ( entry != null && entry.getKey() == key ) {
112-
return entry.getValue();
113+
if ( instanceId <= 0 ) {
114+
return null;
115+
}
116+
117+
final Entry<K, V> entry = get( instanceId - 1 );
118+
if ( entry != null ) {
119+
if ( entry.getKey() == key ) {
120+
return entry.getValue();
121+
}
122+
else {
123+
throw new ConcurrentModificationException(
124+
"Found a different instance corresponding to instanceId [" + instanceId +
125+
"], this might indicate a concurrent access to this persistence context."
126+
);
127+
}
113128
}
114129
return null;
115130
}
@@ -133,8 +148,12 @@ private boolean containsMapping(Object key, Object value) {
133148
throw new NullPointerException( "This map does not support null keys" );
134149
}
135150

136-
final int instanceId = key.$$_hibernate_getInstanceId();
137-
final Map.Entry<K, V> old = set( instanceId, new AbstractMap.SimpleImmutableEntry<>( key, value ) );
151+
final int index = key.$$_hibernate_getInstanceId() - 1;
152+
if ( index < 0 ) {
153+
throw new IllegalArgumentException( "Instance ID must be a positive value" );
154+
}
155+
156+
final Map.Entry<K, V> old = set( index, new AbstractMap.SimpleImmutableEntry<>( key, value ) );
138157
if ( old == null ) {
139158
size++;
140159
return null;
@@ -154,9 +173,14 @@ private boolean containsMapping(Object key, Object value) {
154173
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
155174
*/
156175
public @Nullable V remove(int instanceId, Object key) {
157-
final Page<Map.Entry<K, V>> page = getPage( instanceId );
176+
if ( instanceId <= 0 ) {
177+
return null;
178+
}
179+
180+
final int index = instanceId - 1;
181+
final Page<Map.Entry<K, V>> page = getPage( index );
158182
if ( page != null ) {
159-
final int pageOffset = toPageOffset( instanceId );
183+
final int pageOffset = toPageOffset( index );
160184
final Map.Entry<K, V> entry = page.set( pageOffset, null );
161185
// Check that the provided instance really matches with the key contained in the map
162186
if ( entry != null ) {
@@ -165,8 +189,10 @@ private boolean containsMapping(Object key, Object value) {
165189
return entry.getValue();
166190
}
167191
else {
168-
// If it doesn't, reset the array value to the old key
169-
page.set( pageOffset, entry );
192+
throw new ConcurrentModificationException(
193+
"Found a different instance corresponding to instanceId [" + instanceId +
194+
"], this might indicate a concurrent access to this persistence context."
195+
);
170196
}
171197
}
172198
}

hibernate-core/src/test/java/org/hibernate/orm/test/util/InstanceIdentityMapTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ public void testSimpleMapOperations() {
7070

7171
@Test
7272
public void testMapIteration() {
73-
for ( int i = 0; i < 100; i++ ) {
73+
for ( int i = 1; i <= 100; i++ ) {
7474
testMap.put( new TestInstance( i ), "instance_" + i );
7575
}
7676

7777
assertThat( testMap ).hasSize( 100 );
7878

7979
final AtomicInteger count = new AtomicInteger();
8080
testMap.forEach( (k, v) -> {
81-
assertThat( k.$$_hibernate_getInstanceId() ).isBetween( 0, 99 );
81+
assertThat( k.$$_hibernate_getInstanceId() ).isBetween( 1, 100 );
8282
assertThat( v ).isEqualTo( "instance_" + k.$$_hibernate_getInstanceId() );
8383
count.getAndIncrement();
8484
} );
@@ -88,7 +88,7 @@ public void testMapIteration() {
8888
@Test
8989
public void testSets() {
9090
final Map<TestInstance, String> map = new HashMap<TestInstance, String>();
91-
for ( int i = 0; i < 100; i++ ) {
91+
for ( int i = 1; i <= 100; i++ ) {
9292
map.put( new TestInstance( i ), "instance_" + i );
9393
}
9494

0 commit comments

Comments
 (0)