Skip to content

Commit 461448b

Browse files
committed
HHH-19429 Fix potential ConcurrentModificationException in AbstractSqmPath
1 parent b505d6c commit 461448b

File tree

2 files changed

+198
-3
lines changed

2 files changed

+198
-3
lines changed

hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
import java.util.ArrayList;
88
import java.util.Collection;
9-
import java.util.HashMap;
109
import java.util.List;
1110
import java.util.Map;
1211
import java.util.Objects;
12+
import java.util.concurrent.ConcurrentHashMap;
1313
import java.util.function.Consumer;
1414

1515
import org.hibernate.AssertionFailure;
@@ -117,7 +117,7 @@ public void visitReusablePaths(Consumer<SqmPath<?>> consumer) {
117117
public void registerReusablePath(SqmPath<?> path) {
118118
assert path.getLhs() == this;
119119
if ( reusablePaths == null ) {
120-
reusablePaths = new HashMap<>();
120+
reusablePaths = new ConcurrentHashMap<>();
121121
}
122122
final String relativeName = path.getNavigablePath().getLocalName();
123123
final SqmPath<?> previous = reusablePaths.put( relativeName, path );
@@ -207,7 +207,7 @@ protected <X> SqmPath<X> resolvePath(String attributeName, SqmPathSource<X> path
207207
final SqmPathSource<?> intermediatePathSource =
208208
getResolvedModel().getIntermediatePathSource( pathSource );
209209
if ( reusablePaths == null ) {
210-
reusablePaths = new HashMap<>();
210+
reusablePaths = new ConcurrentHashMap<>();
211211
final SqmPath<X> path = pathSource.createSqmPath( this, intermediatePathSource );
212212
reusablePaths.put( attributeName, path );
213213
return path;
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.sqm;
6+
7+
import java.util.List;
8+
import java.util.concurrent.CompletionService;
9+
import java.util.concurrent.ExecutorCompletionService;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.TimeUnit;
13+
import java.util.function.Consumer;
14+
15+
import org.hibernate.cfg.Configuration;
16+
import org.hibernate.cfg.Environment;
17+
import org.hibernate.engine.spi.SessionImplementor;
18+
19+
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
20+
import org.hibernate.testing.orm.junit.JiraKey;
21+
import org.junit.Test;
22+
23+
import jakarta.persistence.Entity;
24+
import jakarta.persistence.GeneratedValue;
25+
import jakarta.persistence.Id;
26+
import jakarta.persistence.Inheritance;
27+
import jakarta.persistence.InheritanceType;
28+
import jakarta.persistence.Version;
29+
30+
import static org.junit.Assert.assertEquals;
31+
32+
@JiraKey(value = "HHH-19429")
33+
public class ConcurrentQueryCreationTest extends BaseCoreFunctionalTestCase {
34+
35+
private static final int NUM_THREADS = 32;
36+
37+
@Override
38+
protected void configure(Configuration configuration) {
39+
super.configure( configuration );
40+
configuration.setProperty( Environment.POOL_SIZE, Integer.toString( NUM_THREADS ) );
41+
}
42+
43+
@Test
44+
public void versionedUpdate() throws Exception {
45+
Consumer<SessionImplementor> action = session -> {
46+
SimpleEntity entity = new SimpleEntity( "jack" );
47+
session.persist( entity );
48+
session.createMutationQuery( "UPDATE VERSIONED SimpleEntity e SET e.name = :name WHERE e.id = :id" )
49+
.setParameter( "id", entity.getId() )
50+
.setParameter( "name", "new name" )
51+
.executeUpdate();
52+
};
53+
54+
runConcurrently( action );
55+
}
56+
57+
@Test
58+
public void queryWithTreat() throws Exception {
59+
Consumer<SessionImplementor> action = session -> {
60+
SpecificEntity specificEntity = new SpecificEntity( "some name" );
61+
session.persist( specificEntity );
62+
session.persist( new OtherSpecificEntity( "some name" ) );
63+
64+
List<Long> results = session.createQuery(
65+
"""
66+
SELECT COUNT(*) FROM ParentEntity e WHERE e.id = :id
67+
AND (
68+
TREAT(e as SpecificEntity).name = :name
69+
OR
70+
TREAT(e as OtherSpecificEntity).name = :name
71+
)""", Long.class
72+
)
73+
.setParameter( "id", specificEntity.getId() )
74+
.setParameter( "name", "some name" )
75+
.getResultList();
76+
77+
assertEquals( 1, results.size() );
78+
assertEquals( 1L, results.get( 0 ).longValue() );
79+
};
80+
81+
runConcurrently( action );
82+
}
83+
84+
private void runConcurrently(Consumer<SessionImplementor> action) throws Exception {
85+
ExecutorService executor = Executors.newFixedThreadPool( NUM_THREADS );
86+
try {
87+
CompletionService<Void> completionService = new ExecutorCompletionService<>( executor );
88+
89+
for ( int round = 0; round < 100; round++ ) {
90+
for ( int i = 0; i < NUM_THREADS; i++ ) {
91+
completionService.submit( () -> {
92+
inTransaction( action );
93+
return null;
94+
} );
95+
}
96+
97+
for ( int i = 0; i < NUM_THREADS; i++ ) {
98+
completionService.take().get( 1, TimeUnit.MINUTES );
99+
}
100+
101+
rebuildSessionFactory();
102+
}
103+
}
104+
finally {
105+
executor.shutdown();
106+
executor.awaitTermination( 1, TimeUnit.MINUTES );
107+
}
108+
}
109+
110+
@Override
111+
protected Class<?>[] getAnnotatedClasses() {
112+
return new Class<?>[] {
113+
SimpleEntity.class,
114+
ParentEntity.class,
115+
SpecificEntity.class,
116+
OtherSpecificEntity.class
117+
};
118+
}
119+
120+
@Override
121+
protected boolean isCleanupTestDataRequired() {
122+
return true;
123+
}
124+
125+
@Entity(name = "SimpleEntity")
126+
public static class SimpleEntity {
127+
@Id
128+
@GeneratedValue
129+
private Integer id;
130+
131+
@Version
132+
private Integer version;
133+
134+
private String name;
135+
136+
SimpleEntity() {
137+
}
138+
139+
SimpleEntity(String name) {
140+
this.name = name;
141+
}
142+
143+
public Integer getId() {
144+
return id;
145+
}
146+
147+
public void setId(Integer id) {
148+
this.id = id;
149+
}
150+
151+
public String getName() {
152+
return name;
153+
}
154+
155+
public void setName(String name) {
156+
this.name = name;
157+
}
158+
}
159+
160+
@Entity(name = "ParentEntity")
161+
@Inheritance(strategy = InheritanceType.JOINED)
162+
public static class ParentEntity {
163+
@Id
164+
@GeneratedValue
165+
private Integer id;
166+
167+
public Integer getId() {
168+
return id;
169+
}
170+
}
171+
172+
@Entity(name = "SpecificEntity")
173+
public static class SpecificEntity extends ParentEntity {
174+
private String name;
175+
176+
public SpecificEntity() {
177+
}
178+
179+
public SpecificEntity(String name) {
180+
this.name = name;
181+
}
182+
}
183+
184+
@Entity(name = "OtherSpecificEntity")
185+
public static class OtherSpecificEntity extends ParentEntity {
186+
private String name;
187+
188+
public OtherSpecificEntity() {
189+
}
190+
191+
public OtherSpecificEntity(String name) {
192+
this.name = name;
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)