Skip to content

Commit 32047ae

Browse files
committed
Config: detect injected config value mismatch during static init
- record the values injected during static intialization phase - if the runtime value differs from the injected value the app startup fails - also introduce ExecutionMode to easily detect the STATIC_INIT bootstrap phase
1 parent 7c26158 commit 32047ae

File tree

18 files changed

+520
-6
lines changed

18 files changed

+520
-6
lines changed

core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import io.quarkus.gizmo.TryBlock;
7878
import io.quarkus.runtime.Application;
7979
import io.quarkus.runtime.ApplicationLifecycleManager;
80+
import io.quarkus.runtime.ExecutionModeManager;
8081
import io.quarkus.runtime.LaunchMode;
8182
import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder;
8283
import io.quarkus.runtime.PreventFurtherStepsException;
@@ -106,6 +107,14 @@ public class MainClassBuildStep {
106107
void.class, StartupContext.class);
107108
public static final MethodDescriptor CONFIGURE_STEP_TIME_ENABLED = ofMethod(StepTiming.class.getName(), "configureEnabled",
108109
void.class);
110+
public static final MethodDescriptor RUNTIME_EXECUTION_STATIC_INIT = ofMethod(ExecutionModeManager.class.getName(),
111+
"staticInit", void.class);
112+
public static final MethodDescriptor RUNTIME_EXECUTION_RUNTIME_INIT = ofMethod(ExecutionModeManager.class.getName(),
113+
"runtimeInit", void.class);
114+
public static final MethodDescriptor RUNTIME_EXECUTION_RUNNING = ofMethod(ExecutionModeManager.class.getName(),
115+
"running", void.class);
116+
public static final MethodDescriptor RUNTIME_EXECUTION_UNSET = ofMethod(ExecutionModeManager.class.getName(),
117+
"unset", void.class);
109118
public static final MethodDescriptor CONFIGURE_STEP_TIME_START = ofMethod(StepTiming.class.getName(), "configureStart",
110119
void.class);
111120
private static final DotName QUARKUS_APPLICATION = DotName.createSimple(QuarkusApplication.class.getName());
@@ -170,6 +179,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
170179
lm);
171180

172181
mv.invokeStaticMethod(CONFIGURE_STEP_TIME_ENABLED);
182+
mv.invokeStaticMethod(RUNTIME_EXECUTION_STATIC_INIT);
173183

174184
mv.invokeStaticMethod(ofMethod(Timing.class, "staticInitStarted", void.class, boolean.class),
175185
mv.load(launchMode.isAuxiliaryApplication()));
@@ -227,6 +237,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
227237
mv.load(i.getKey()), mv.load(i.getValue()));
228238
}
229239
mv.invokeStaticMethod(ofMethod(NativeImageRuntimePropertiesRecorder.class, "doRuntime", void.class));
240+
mv.invokeStaticMethod(RUNTIME_EXECUTION_RUNTIME_INIT);
230241

231242
// Set the SSL system properties
232243
if (!javaLibraryPathAdditionalPaths.isEmpty()) {
@@ -268,6 +279,8 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
268279
loaders, constants, gizmoOutput, startupContext, tryBlock);
269280
}
270281

282+
tryBlock.invokeStaticMethod(RUNTIME_EXECUTION_RUNNING);
283+
271284
// Startup log messages
272285
List<String> featureNames = new ArrayList<>();
273286
for (FeatureBuildItem feature : features) {
@@ -324,6 +337,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
324337

325338
mv = file.getMethodCreator("doStop", void.class);
326339
mv.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
340+
mv.invokeStaticMethod(RUNTIME_EXECUTION_UNSET);
327341
startupContext = mv.readStaticField(scField.getFieldDescriptor());
328342
mv.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext);
329343
mv.returnValue(null);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.quarkus.runtime;
2+
3+
/**
4+
* The runtime execution mode.
5+
*/
6+
public enum ExecutionMode {
7+
8+
/**
9+
* Static initializiation.
10+
*/
11+
STATIC_INIT,
12+
13+
/**
14+
* Runtime initialization.
15+
*/
16+
RUNTIME_INIT,
17+
18+
/**
19+
* The application is running.
20+
*/
21+
RUNNING,
22+
23+
UNSET,
24+
;
25+
26+
public static ExecutionMode current() {
27+
return ExecutionModeManager.getExecutionMode();
28+
}
29+
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.quarkus.runtime;
2+
3+
public final class ExecutionModeManager {
4+
5+
private static volatile ExecutionMode executionMode = ExecutionMode.UNSET;
6+
7+
public static void staticInit() {
8+
executionMode = ExecutionMode.STATIC_INIT;
9+
}
10+
11+
public static void runtimeInit() {
12+
executionMode = ExecutionMode.RUNTIME_INIT;
13+
}
14+
15+
public static void running() {
16+
executionMode = ExecutionMode.RUNNING;
17+
}
18+
19+
public static void unset() {
20+
executionMode = ExecutionMode.UNSET;
21+
}
22+
23+
public static ExecutionMode getExecutionMode() {
24+
return executionMode;
25+
}
26+
}

core/runtime/src/main/java/io/quarkus/runtime/annotations/StaticInitSafe.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package io.quarkus.runtime.annotations;
22

3+
import static java.lang.annotation.ElementType.FIELD;
4+
import static java.lang.annotation.ElementType.PARAMETER;
35
import static java.lang.annotation.ElementType.TYPE;
46
import static java.lang.annotation.RetentionPolicy.RUNTIME;
57

68
import java.lang.annotation.Documented;
79
import java.lang.annotation.Retention;
810
import java.lang.annotation.Target;
911

12+
import org.eclipse.microprofile.config.inject.ConfigProperty;
13+
1014
/**
11-
* Used to mark a {@link org.eclipse.microprofile.config.spi.ConfigSource},
12-
* {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider} or {@link io.smallrye.config.ConfigSourceFactory}
13-
* as safe to be initialized during STATIC INIT.
15+
* Used to mark a configuration object as safe to be initialized during the STATIC INIT phase.
16+
* <p>
17+
* The target configuration objects include {@link org.eclipse.microprofile.config.spi.ConfigSource},
18+
* {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider}, {@link io.smallrye.config.ConfigSourceFactory} and
19+
* {@link io.smallrye.config.ConfigMapping}. Moreover, this annotation can be used for
20+
* {@link org.eclipse.microprofile.config.inject.ConfigProperty} injection points.
21+
* <p>
1422
*
1523
* When a Quarkus application is starting up, Quarkus will execute first a static init method which contains some
1624
* extensions actions and configurations. Example:
@@ -36,7 +44,7 @@
3644
* previous code example and a ConfigSource that requires database access. In this case, it is impossible to properly
3745
* initialize such ConfigSource, because the database services are not yet available so the ConfigSource in unusable.
3846
*/
39-
@Target(TYPE)
47+
@Target({ TYPE, FIELD, PARAMETER })
4048
@Retention(RUNTIME)
4149
@Documented
4250
public @interface StaticInitSafe {

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class ConfigBuildStep {
8383
private static final Logger LOGGER = Logger.getLogger(ConfigBuildStep.class.getName());
8484

8585
private static final DotName MP_CONFIG = DotName.createSimple(Config.class.getName());
86-
private static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName());
86+
static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName());
8787
private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName());
8888
private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName());
8989

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.quarkus.arc.deployment;
2+
3+
import org.jboss.jandex.DotName;
4+
5+
import io.quarkus.arc.processor.AnnotationsTransformer;
6+
import io.quarkus.arc.processor.DotNames;
7+
import io.quarkus.arc.runtime.ConfigStaticInitCheck;
8+
import io.quarkus.arc.runtime.ConfigStaticInitCheckInterceptor;
9+
import io.quarkus.arc.runtime.ConfigStaticInitValues;
10+
import io.quarkus.deployment.annotations.BuildStep;
11+
12+
public class ConfigStaticInitBuildSteps {
13+
14+
@BuildStep
15+
AdditionalBeanBuildItem registerBeans() {
16+
return AdditionalBeanBuildItem.builder()
17+
.addBeanClasses(ConfigStaticInitCheckInterceptor.class, ConfigStaticInitValues.class,
18+
ConfigStaticInitCheck.class)
19+
.build();
20+
}
21+
22+
@BuildStep
23+
AnnotationsTransformerBuildItem transformConfigProducer() {
24+
DotName configProducerName = DotName.createSimple("io.smallrye.config.inject.ConfigProducer");
25+
26+
return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToMethod().whenMethod(m -> {
27+
// Apply to all producer methods declared on io.smallrye.config.inject.ConfigProducer
28+
return m.declaringClass().name().equals(configProducerName)
29+
&& m.hasAnnotation(DotNames.PRODUCES)
30+
&& m.hasAnnotation(ConfigBuildStep.MP_CONFIG_PROPERTY_NAME);
31+
}).thenTransform(t -> {
32+
t.add(ConfigStaticInitCheck.class);
33+
}));
34+
}
35+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.quarkus.arc.test.config.staticinit;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.context.Initialized;
5+
import jakarta.enterprise.event.Observes;
6+
import jakarta.inject.Singleton;
7+
8+
import org.eclipse.microprofile.config.inject.ConfigProperty;
9+
10+
@Singleton
11+
public class StaticInitBean {
12+
13+
@ConfigProperty(name = "apfelstrudel")
14+
String value;
15+
16+
// bean is instantiated during STATIC_INIT
17+
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
18+
}
19+
20+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.quarkus.arc.test.config.staticinit;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.junit.jupiter.api.Assertions.fail;
5+
6+
import org.eclipse.microprofile.config.spi.ConfigSource;
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.test.QuarkusUnitTest;
12+
13+
public class StaticInitConfigInjectionFailureTest {
14+
15+
@RegisterExtension
16+
static final QuarkusUnitTest config = new QuarkusUnitTest()
17+
.withApplicationRoot(root -> root
18+
.addClasses(StaticInitBean.class, StaticInitEagerBean.class, UnsafeConfigSource.class)
19+
.addAsServiceProvider(ConfigSource.class, UnsafeConfigSource.class)
20+
// the value from application.properties should be injected during STATIC_INIT
21+
.addAsResource(new StringAsset("apfelstrudel=jandex"), "application.properties"))
22+
.assertException(t -> {
23+
assertThat(t).isInstanceOf(IllegalStateException.class)
24+
.hasMessageContainingAll(
25+
"A runtime config property value differs from the value that was injected during the static intialization phase",
26+
"the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitBean#value",
27+
"the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitEagerBean#value");
28+
});
29+
30+
@Test
31+
public void test() {
32+
fail();
33+
}
34+
35+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.quarkus.arc.test.config.staticinit;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.context.Initialized;
5+
import jakarta.enterprise.event.Observes;
6+
import jakarta.enterprise.inject.Instance;
7+
import jakarta.inject.Singleton;
8+
9+
import org.eclipse.microprofile.config.inject.ConfigProperty;
10+
11+
@Singleton
12+
public class StaticInitEagerBean {
13+
14+
@ConfigProperty(name = "apfelstrudel")
15+
Instance<String> value;
16+
17+
// bean is instantiated during STATIC_INIT
18+
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
19+
// this should trigger the failure
20+
value.get();
21+
}
22+
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.quarkus.arc.test.config.staticinit;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.context.Initialized;
5+
import jakarta.enterprise.event.Observes;
6+
import jakarta.enterprise.inject.Instance;
7+
import jakarta.inject.Singleton;
8+
9+
import org.eclipse.microprofile.config.inject.ConfigProperty;
10+
11+
@Singleton
12+
public class StaticInitLazyBean {
13+
14+
@ConfigProperty(name = "apfelstrudel")
15+
Instance<String> value;
16+
17+
// bean is instantiated during STATIC_INIT
18+
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
19+
// value is not obtained...
20+
}
21+
22+
}

0 commit comments

Comments
 (0)