Skip to content

Commit ac0bc37

Browse files
committed
HHH-18326 Also reset collection instance ids
1 parent 9a2908f commit ac0bc37

File tree

5 files changed

+63
-20
lines changed

5 files changed

+63
-20
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) {
673673
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
674674
// need to think about implications for memory leaks here if we don't removed reference to EntityEntry
675675
if ( entityEntry == null ) {
676-
if ( canClearEntityEntryReference() ) {
676+
if ( canClearEntityEntryReference( managedEntity.$$_hibernate_getEntityEntry() ) ) {
677677
managedEntity.$$_hibernate_setEntityEntry( null );
678678
}
679679
// otherwise, do nothing.
@@ -731,10 +731,9 @@ public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) {
731731

732732
// Check instance type of EntityEntry and if type is ImmutableEntityEntry,
733733
// check to see if entity is referenced cached in the second level cache
734-
private boolean canClearEntityEntryReference() {
735-
EntityEntry entityEntry = managedEntity.$$_hibernate_getEntityEntry();
734+
private static boolean canClearEntityEntryReference(EntityEntry entityEntry) {
736735
return !(entityEntry instanceof ImmutableEntityEntry)
737-
|| !entityEntry.getPersister().canUseReferenceCacheEntries();
736+
|| !isReferenceCachingEnabled( entityEntry.getPersister() );
738737
}
739738

740739
@Override
@@ -746,6 +745,17 @@ private boolean canClearEntityEntryReference() {
746745
public void $$_hibernate_setInstanceId(int id) {
747746
managedEntity.$$_hibernate_setInstanceId( id );
748747
}
748+
749+
@Override
750+
public EntityEntry $$_hibernate_setPersistenceInfo(EntityEntry entityEntry, ManagedEntity previous, ManagedEntity next, int instanceId) {
751+
if ( entityEntry == null ) {
752+
final EntityEntry oldEntry = managedEntity.$$_hibernate_getEntityEntry();
753+
if ( !canClearEntityEntryReference( oldEntry ) ) {
754+
entityEntry = oldEntry;
755+
}
756+
}
757+
return managedEntity.$$_hibernate_setPersistenceInfo( entityEntry, previous, next, instanceId );
758+
}
749759
}
750760

751761
/**

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;
@@ -1132,6 +1135,7 @@ private void putCollectionEntry(PersistentCollection<?> collection, CollectionEn
11321135
if ( this.collectionEntries == null ) {
11331136
this.collectionEntries = new InstanceIdentityMap<>();
11341137
}
1138+
assert collection.$$_hibernate_getInstanceId() == 0;
11351139
collection.$$_hibernate_setInstanceId( nextCollectionInstanceId() );
11361140
this.collectionEntries.put( collection, entry );
11371141
}
@@ -2183,7 +2187,9 @@ public int getCollectionEntriesSize() {
21832187
@Override
21842188
public CollectionEntry removeCollectionEntry(PersistentCollection<?> collection) {
21852189
if ( collectionEntries != null ) {
2186-
return collectionEntries.remove( collection.$$_hibernate_getInstanceId(), collection );
2190+
final int instanceId = collection.$$_hibernate_getInstanceId();
2191+
collection.$$_hibernate_setInstanceId( 0 );
2192+
return collectionEntries.remove( instanceId, collection );
21872193
}
21882194
else {
21892195
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
@@ -1038,6 +1038,7 @@ else if ( association instanceof PersistentCollection<?> collection ) {
10381038
}
10391039
}
10401040
finally {
1041+
collection.$$_hibernate_setInstanceId( 0 );
10411042
collection.unsetSession( this );
10421043
if ( persistenceContext.isLoadFinished() ) {
10431044
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)