Skip to content

Commit bfd2587

Browse files
committed
Observe shutdown event to close persistent units
instead of relaying on a Destroyer. This way other components e.g. datasource should still be available.
1 parent 0e013fa commit bfd2587

File tree

4 files changed

+105
-36
lines changed

4 files changed

+105
-36
lines changed

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.hibernate.orm.deployment;
22

3+
import java.lang.annotation.Annotation;
34
import java.util.ArrayList;
45
import java.util.Arrays;
56
import java.util.List;
@@ -8,30 +9,29 @@
89
import java.util.stream.Collectors;
910

1011
import jakarta.enterprise.context.ApplicationScoped;
11-
import jakarta.enterprise.inject.Any;
1212
import jakarta.enterprise.inject.Default;
13-
import jakarta.enterprise.inject.Instance;
1413
import jakarta.inject.Singleton;
1514
import jakarta.persistence.AttributeConverter;
1615
import jakarta.transaction.TransactionManager;
1716

1817
import org.hibernate.Session;
1918
import org.hibernate.SessionFactory;
2019
import org.hibernate.StatelessSession;
21-
import org.jboss.jandex.AnnotationInstance;
2220
import org.jboss.jandex.AnnotationTarget.Kind;
2321
import org.jboss.jandex.AnnotationValue;
2422
import org.jboss.jandex.ClassType;
2523
import org.jboss.jandex.DotName;
2624
import org.jboss.jandex.FieldInfo;
27-
import org.jboss.jandex.ParameterizedType;
2825
import org.jboss.jandex.Type;
2926

30-
import io.agroal.api.AgroalDataSource;
3127
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
28+
import io.quarkus.arc.Arc;
29+
import io.quarkus.arc.ArcContainer;
30+
import io.quarkus.arc.InstanceHandle;
3231
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
3332
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
3433
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
34+
import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem;
3535
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
3636
import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator;
3737
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
@@ -47,6 +47,8 @@
4747
import io.quarkus.deployment.annotations.ExecutionTime;
4848
import io.quarkus.deployment.annotations.Record;
4949
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
50+
import io.quarkus.gizmo.MethodDescriptor;
51+
import io.quarkus.gizmo.ResultHandle;
5052
import io.quarkus.hibernate.orm.PersistenceUnit;
5153
import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder;
5254
import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig;
@@ -56,6 +58,7 @@
5658
import io.quarkus.hibernate.orm.runtime.RequestScopedStatelessSessionHolder;
5759
import io.quarkus.hibernate.orm.runtime.TransactionSessions;
5860
import io.quarkus.hibernate.orm.runtime.cdi.QuarkusArcBeanContainer;
61+
import io.quarkus.runtime.ShutdownEvent;
5962

6063
@BuildSteps(onlyIf = HibernateOrmEnabled.class)
6164
public class HibernateOrmCdiProcessor {
@@ -131,7 +134,6 @@ public void transform(TransformationContext transformationContext) {
131134
@BuildStep
132135
@Record(ExecutionTime.RUNTIME_INIT)
133136
void generateJpaConfigBean(HibernateOrmRecorder recorder,
134-
Capabilities capabilities,
135137
HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig,
136138
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer) {
137139
ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
@@ -140,28 +142,45 @@ void generateJpaConfigBean(HibernateOrmRecorder recorder,
140142
.scope(Singleton.class)
141143
.unremovable()
142144
.setRuntimeInit()
143-
.supplier(recorder.jpaConfigSupplier(hibernateOrmRuntimeConfig))
144-
.destroyer(JPAConfig.Destroyer.class);
145-
146-
// Add a synthetic dependency from JPAConfig to any datasource/pool,
147-
// so that JPAConfig is destroyed before the datasource/pool.
148-
// The alternative would be adding an application destruction observer
149-
// (@Observes @BeforeDestroyed(ApplicationScoped.class)) to JPAConfig,
150-
// but that would force initialization of JPAConfig upon application shutdown,
151-
// which may cause cascading failures if the shutdown happened before JPAConfig was initialized.
152-
if (capabilities.isPresent(Capability.HIBERNATE_REACTIVE)) {
153-
configurator.addInjectionPoint(ParameterizedType.create(DotName.createSimple(Instance.class),
154-
new Type[] { ClassType.create(DotName.createSimple("io.vertx.sqlclient.Pool")) }, null),
155-
AnnotationInstance.builder(Any.class).build());
156-
} else {
157-
configurator.addInjectionPoint(ParameterizedType.create(DotName.createSimple(Instance.class),
158-
new Type[] { ClassType.create(DotName.createSimple(AgroalDataSource.class)) }, null),
159-
AnnotationInstance.builder(Any.class).build());
160-
}
145+
.supplier(recorder.jpaConfigSupplier(hibernateOrmRuntimeConfig));
161146

162147
syntheticBeanBuildItemBuildProducer.produce(configurator.done());
163148
}
164149

150+
@BuildStep
151+
@Record(ExecutionTime.RUNTIME_INIT)
152+
void generateJpaConfigBeanObserver(
153+
HibernateOrmRecorder recorder,
154+
ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
155+
BuildProducer<ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem> observerConfigurationRegistry) {
156+
observerConfigurationRegistry.produce(
157+
new ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem(observerRegistrationPhase.getContext()
158+
.configure()
159+
.beanClass(DotName.createSimple("io.quarkus.hibernate.orm.runtime.JPAConfig"))
160+
.observedType(ShutdownEvent.class)
161+
.notify(mc -> {
162+
// Essentially do the following:
163+
// Arc.container().instance( JPAConfig.class ).get().shutdown();
164+
ResultHandle arcContainer = mc.invokeStaticMethod(
165+
MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class));
166+
ResultHandle jpaConfigInstance = mc.invokeInterfaceMethod(
167+
MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class,
168+
Class.class, Annotation[].class),
169+
arcContainer,
170+
mc.loadClassFromTCCL(JPAConfig.class),
171+
mc.newArray(Annotation.class, 0));
172+
ResultHandle jpaConfig = mc.invokeInterfaceMethod(
173+
MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class),
174+
jpaConfigInstance);
175+
176+
mc.invokeVirtualMethod(
177+
MethodDescriptor.ofMethod(JPAConfig.class, "shutdown", void.class),
178+
jpaConfig);
179+
180+
mc.returnValue(null);
181+
})));
182+
}
183+
165184
// These beans must be initialized at runtime because their initialization
166185
// depends on runtime configuration (to activate/deactivate a persistence unit)
167186
@Record(ExecutionTime.RUNTIME_INIT)

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@
1010
import java.util.concurrent.CompletableFuture;
1111
import java.util.concurrent.ExecutionException;
1212

13-
import jakarta.enterprise.context.spi.CreationalContext;
13+
import io.quarkus.logging.Log;
1414
import jakarta.inject.Inject;
1515
import jakarta.persistence.EntityManagerFactory;
1616
import jakarta.persistence.Persistence;
1717

1818
import org.jboss.logging.Logger;
1919

20-
import io.quarkus.arc.BeanDestroyer;
2120
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDescriptor;
2221

2322
public class JPAConfig {
@@ -120,18 +119,17 @@ public Set<String> getDeactivatedPersistenceUnitNames() {
120119
return deactivatedPersistenceUnitNames;
121120
}
122121

123-
public static class Destroyer implements BeanDestroyer<JPAConfig> {
124-
@Override
125-
public void destroy(JPAConfig instance, CreationalContext<JPAConfig> creationalContext, Map<String, Object> params) {
126-
for (LazyPersistenceUnit factory : instance.persistenceUnits.values()) {
127-
try {
128-
factory.close();
129-
} catch (Exception e) {
130-
LOGGER.warn("Unable to close the EntityManagerFactory: " + factory, e);
131-
}
122+
void shutdown() {
123+
LOGGER.trace("Starting to shut down Hibernate ORM persistence units.");
124+
for (LazyPersistenceUnit factory : this.persistenceUnits.values()) {
125+
try {
126+
factory.close();
127+
} catch (Exception e) {
128+
LOGGER.warn("Unable to close the EntityManagerFactory: " + factory, e);
132129
}
133-
instance.persistenceUnits.clear();
134130
}
131+
this.persistenceUnits.clear();
132+
LOGGER.trace("Finished shutting down Hibernate ORM persistence units.");
135133
}
136134

137135
static final class LazyPersistenceUnit {

integration-tests/hibernate-search-orm-elasticsearch-outbox-polling/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
<artifactId>quarkus-junit5</artifactId>
5151
<scope>test</scope>
5252
</dependency>
53+
<dependency>
54+
<groupId>io.quarkus</groupId>
55+
<artifactId>quarkus-junit5-internal</artifactId>
56+
<scope>test</scope>
57+
</dependency>
5358
<dependency>
5459
<groupId>io.quarkus</groupId>
5560
<artifactId>quarkus-test-h2</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.quarkus.it.hibernate.search.orm.elasticsearch.coordination.outboxpolling;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.hamcrest.Matchers.is;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
import io.quarkus.test.QuarkusDevModeTest;
10+
import io.restassured.RestAssured;
11+
12+
public class HibernateSearchDevModeTest {
13+
14+
@RegisterExtension
15+
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
16+
.withApplicationRoot((jar) -> jar
17+
.addClasses(HibernateSearchOutboxPollingTestResource.class, Person.class, OutboxPollingTestUtils.class)
18+
.addAsResource("application.properties", "application.properties"))
19+
.setLogRecordPredicate(r -> true);
20+
21+
@Test
22+
public void smoke() {
23+
String[] schemaManagementStrategies = { "drop-and-create-and-drop", "drop-and-create" };
24+
25+
RestAssured.when().put("/test/hibernate-search-outbox-polling/check-agents-running").then()
26+
.statusCode(200)
27+
.body(is("OK"));
28+
29+
for (int i = 0; i < 3; i++) {
30+
int current = i;
31+
config.modifyResourceFile(
32+
"application.properties",
33+
s -> s.replace(
34+
"quarkus.hibernate-search-orm.schema-management.strategy="
35+
+ schemaManagementStrategies[current % 2],
36+
"quarkus.hibernate-search-orm.schema-management.strategy="
37+
+ schemaManagementStrategies[(current + 1) % 2]));
38+
39+
RestAssured.when().put("/test/hibernate-search-outbox-polling/check-agents-running").then()
40+
.statusCode(200)
41+
.body(is("OK"));
42+
}
43+
44+
assertThat(config.getLogRecords()).noneSatisfy(
45+
r -> assertThat(r.getMessage()).contains("Unable to shut down Hibernate Search"));
46+
}
47+
}

0 commit comments

Comments
 (0)