Skip to content

Commit 344cf3b

Browse files
authored
Graceful shutdown - defaults for io.ebean.Database to shutdown() last (#820)
For graceful shutdown, we desire components to shutdown in an appropriate order. For io.ebean.Database it should be shutdown last. Adds a default shutdown method and shutdown priority for special components such that they default to doing "the right thing" wrt graceful shutdown. For ebean: ```java @bean Database database() ``` We desire that to default to: ```java @bean(destroyMethod = "shutdown", destroyPriority = Integer.MAX_VALUE) Database database() ```
1 parent 43196ca commit 344cf3b

File tree

5 files changed

+85
-6
lines changed

5 files changed

+85
-6
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.ebean;
2+
3+
/**
4+
* Simulate the Ebean io.ebean.Database interface with a shutdown() method.
5+
* <p>
6+
* For "Graceful Shutdown" we desire this to be shutdown last.
7+
*/
8+
public interface Database {
9+
10+
/** Shutdown including underlying DataSources. We want this to occur LAST. */
11+
void shutdown();
12+
13+
/** Test only method */
14+
boolean isShutdown();
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.ebean;
2+
3+
public class MyDatabase implements Database {
4+
5+
public boolean shutdownCalled = false;
6+
7+
@Override
8+
public void shutdown() {
9+
shutdownCalled = true;
10+
}
11+
12+
@Override
13+
public boolean isShutdown() {
14+
return shutdownCalled;
15+
}
16+
}

blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.example.myapp.config;
22

33
import io.avaje.inject.*;
4+
import io.ebean.Database;
5+
import io.ebean.MyDatabase;
46
import jakarta.inject.Inject;
57
import jakarta.inject.Named;
68
import org.example.myapp.HelloData;
@@ -14,6 +16,12 @@ public class AppConfig {
1416

1517
public static AtomicBoolean BEAN_AUTO_CLOSED = new AtomicBoolean();
1618

19+
/** Because this is a io.ebean.Database, we know it should be shutdown() last */
20+
@Bean // Effectively default to @Bean(destroyMethod = "shutdown", destroyPriority = Integer.MAX_VALUE)
21+
Database database() {
22+
return new MyDatabase();
23+
}
24+
1725
@PreDestroy(priority = 999)
1826
void close() {
1927
MyDestroyOrder.add("AppConfig");
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.example.myapp;
2+
3+
import io.avaje.inject.BeanScope;
4+
import io.ebean.Database;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
class DefaultDestroyTest {
10+
11+
@Test
12+
void expect_ebeanDatabase_hasShutdownByDefault() {
13+
try (BeanScope beanScope = BeanScope.builder().build()) {
14+
Database database = beanScope.get(Database.class);
15+
16+
assertThat(database.isShutdown()).isFalse();
17+
beanScope.close();
18+
19+
assertThat(database.isShutdown()).isTrue();
20+
}
21+
}
22+
}

inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import javax.lang.model.element.*;
44
import javax.lang.model.type.TypeKind;
55
import javax.lang.model.type.TypeMirror;
6-
import java.util.ArrayList;
7-
import java.util.Collections;
8-
import java.util.List;
9-
import java.util.Set;
6+
import java.util.*;
107

118
import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY;
129
import static io.avaje.inject.generator.ProcessingContext.asElement;
@@ -15,6 +12,13 @@ final class MethodReader {
1512

1613
private static final String CODE_COMMENT_BUILD_FACTORYBEAN = " /**\n * Create and register %s via factory bean method %s#%s().\n */";
1714

15+
private static final Map<String,String> DEFAULT_DESTROY_METHODS = Map.of(
16+
"io.ebean.Database", "shutdown"
17+
);
18+
private static final Map<String,Integer> DEFAULT_DESTROY_PRIORITY = Map.of(
19+
"io.ebean.Database", Integer.MAX_VALUE
20+
);
21+
1822
private final TypeExtendsInjection factory;
1923
private final ExecutableElement element;
2024
private final String factoryType;
@@ -85,8 +89,8 @@ final class MethodReader {
8589
this.factoryShortName = Util.shortName(factoryType);
8690
this.isVoid = Util.isVoid(mainType);
8791
String initMethod = bean == null ? null : bean.initMethod();
88-
String destroyMethod = bean == null ? null : bean.destroyMethod();
89-
this.destroyPriority = bean == null ? null : bean.destroyPriority();
92+
String destroyMethod = bean == null ? null : defaultDestroyMethod(bean.destroyMethod());
93+
this.destroyPriority = bean == null ? null : defaultDestroyPriority(bean.destroyPriority());
9094
this.beanCloseable = bean != null && bean.autoCloseable();
9195
// for multiRegister we desire a qualifier name such that builder.isBeanAbsent() uses it and allows
9296
// other beans of the same type to also register, otherwise it defaults to slightly confusing behaviour
@@ -113,6 +117,20 @@ final class MethodReader {
113117
}
114118
}
115119

120+
private String defaultDestroyMethod(String destroyMethod) {
121+
if (!destroyMethod.isEmpty()) {
122+
return destroyMethod;
123+
}
124+
return DEFAULT_DESTROY_METHODS.get(returnTypeRaw);
125+
}
126+
127+
private Integer defaultDestroyPriority(Integer destroyPriority) {
128+
if (destroyPriority != null && destroyPriority != 1000) {
129+
return destroyPriority;
130+
}
131+
return DEFAULT_DESTROY_PRIORITY.get(returnTypeRaw);
132+
}
133+
116134
@Override
117135
public String toString() {
118136
return "MethodReader{" +

0 commit comments

Comments
 (0)