Skip to content

Commit ed0da60

Browse files
authored
Merge pull request #11866 from micronaut-projects/4.10.x-upd
Merge 4.9.x
2 parents 77e0a29 + 3a7cccf commit ed0da60

File tree

953 files changed

+53891
-543
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

953 files changed

+53891
-543
lines changed

build.gradle

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,20 @@ if (System.getenv("SONAR_TOKEN") != null) {
2727
"**/GroovyClassWriterOutputVisitor.java",
2828
"**/MutableHttpRequestWrapper.java",
2929
"**/tck/**",
30-
"**/test/support/**"
30+
"**/test/support/**",
31+
"**/micronaut/annotation/processing/test/**"
3132
]
3233
sonarqube {
34+
skipProject = project.name.contains("test")
3335
properties {
3436
property "sonar.exclusions", coverageExcludes.join(",")
3537
}
3638
}
3739
}
40+
41+
configurations.jacocoAggregation.dependencies.removeIf {
42+
it instanceof ProjectDependency && it.path.startsWith(":test-")
43+
}
44+
configurations.javadocAggregatorBase.dependencies.removeIf {
45+
it instanceof ProjectDependency && it.path.startsWith(":test-")
46+
}

buildSrc/src/main/groovy/io.micronaut.build.internal.convention-quality.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ tasks.named("docs") {
1818
reporting {
1919
reports {
2020
testAggregateTestReport(AggregateTestReport) {
21-
testType = TestSuiteType.UNIT_TEST
21+
testSuiteName = "test"
2222
}
2323
}
2424
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
plugins {
22
id "io.micronaut.build.internal.convention-base"
33
}
4+
5+
afterEvaluate {
6+
tasks.withType(Test).configureEach {
7+
jacoco {
8+
enabled = false
9+
}
10+
}
11+
}

context/src/main/java/io/micronaut/runtime/context/scope/refresh/RefreshScope.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.micronaut.context.BeanContext;
2020
import io.micronaut.context.BeanRegistration;
2121
import io.micronaut.context.LifeCycle;
22-
import io.micronaut.context.annotation.ConfigurationProperties;
2322
import io.micronaut.context.annotation.ConfigurationReader;
2423
import io.micronaut.context.annotation.Requires;
2524
import io.micronaut.context.event.ApplicationEventListener;
@@ -171,13 +170,19 @@ protected ReadWriteLock getLock(Object object) {
171170

172171
private void refreshSubsetOfConfigurationProperties(Set<String> keySet) {
173172
Collection<BeanRegistration<?>> registrations =
174-
beanContext.getActiveBeanRegistrations(Qualifiers.byStereotype(ConfigurationProperties.class));
173+
beanContext.getActiveBeanRegistrations(Qualifiers.byStereotype(ConfigurationReader.class));
175174
for (BeanRegistration<?> registration : registrations) {
176175
BeanDefinition<?> definition = registration.getBeanDefinition();
177176
Optional<String> value = definition.stringValue(ConfigurationReader.class, "prefix");
178177
if (value.isPresent()) {
179178
String configPrefix = value.get();
180-
if (keySet.stream().anyMatch(key -> key.startsWith(configPrefix))) {
179+
if (configPrefix.endsWith(".*")) {
180+
configPrefix = configPrefix.substring(0, configPrefix.length() - 2);
181+
} else if (configPrefix.endsWith("[*]")) {
182+
configPrefix = configPrefix.substring(0, configPrefix.length() - 3);
183+
}
184+
String finalConfigPrefix = configPrefix;
185+
if (keySet.stream().anyMatch(key -> key.startsWith(finalConfigPrefix))) {
181186
beanContext.refreshBean(registration);
182187
}
183188
}
@@ -186,7 +191,7 @@ private void refreshSubsetOfConfigurationProperties(Set<String> keySet) {
186191

187192
private void refreshAllConfigurationProperties() {
188193
Collection<BeanRegistration<?>> registrations =
189-
beanContext.getActiveBeanRegistrations(Qualifiers.byStereotype(ConfigurationProperties.class));
194+
beanContext.getActiveBeanRegistrations(Qualifiers.byStereotype(ConfigurationReader.class));
190195
for (BeanRegistration<?> registration : registrations) {
191196
beanContext.refreshBean(registration);
192197
}

context/src/main/java/io/micronaut/runtime/converters/time/TimeConverterRegistrar.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ private BiFunction<CharSequence, ConversionContext, Optional<Duration>> duration
355355
final String seq = g2 + matcher.group(3);
356356
if (seq.equals("ns")) {
357357
return Optional.of(Duration.ofNanos(Integer.parseInt(amount)));
358+
} else if (seq.equals("us")) {
359+
return Optional.of(Duration.ofNanos(Integer.parseInt(amount) * 1000L));
358360
}
359361
context.reject(
360362
value,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2017-2024 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.runtime.graceful;
17+
18+
import io.micronaut.core.annotation.Internal;
19+
import io.micronaut.core.annotation.NonNull;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.util.Optional;
24+
import java.util.OptionalLong;
25+
import java.util.concurrent.CompletableFuture;
26+
import java.util.concurrent.CompletionStage;
27+
import java.util.stream.Stream;
28+
29+
/**
30+
* Interface implemented by beans that support graceful shutdown.
31+
*
32+
* @author Jonas Konrad
33+
* @since 4.9.0
34+
*/
35+
public interface GracefulShutdownCapable {
36+
37+
/**
38+
* Trigger a graceful shutdown. The returned {@link CompletionStage} will complete when the
39+
* shutdown is complete.
40+
* <p>
41+
* Note that the completion of the returned future may be user-dependent. If a user does not
42+
* close their connection, the future may never terminate. Always add a timeout for a hard
43+
* shutdown.
44+
* <p>
45+
* This method should not throw an exception, nor should the returned stage complete
46+
* exceptionally. Just log an error instead.
47+
*
48+
* @return A future that completes when this bean is fully shut down
49+
*/
50+
@NonNull
51+
CompletionStage<?> shutdownGracefully();
52+
53+
/**
54+
* After a call to {@link #shutdownGracefully()} report the state of the shutdown. If
55+
* {@link #shutdownGracefully()} has not been called the behavior of this method is undefined.
56+
*
57+
* @return The current number of still-active tasks before the shutdown completes, or
58+
* {@link Optional#empty()} if no state can be reported
59+
*/
60+
default OptionalLong reportActiveTasks() {
61+
return OptionalLong.empty();
62+
}
63+
64+
/**
65+
* Combine the given futures.
66+
*
67+
* @param stages The input futures
68+
* @return A future that completes when all inputs have completed
69+
*/
70+
@NonNull
71+
static CompletionStage<?> allOf(@NonNull Stream<CompletionStage<?>> stages) {
72+
return CompletableFuture.allOf(stages.map(CompletionStage::toCompletableFuture).toArray(CompletableFuture[]::new));
73+
}
74+
75+
/**
76+
* Shutdown all the given lifecycles.
77+
*
78+
* @param stages The input lifecycles
79+
* @return A future that completes when all inputs have completed shutdown
80+
*/
81+
@NonNull
82+
static CompletionStage<?> shutdownAll(@NonNull Stream<? extends GracefulShutdownCapable> stages) {
83+
return CompletableFuture.allOf(stages.map(l -> {
84+
CompletionStage<?> s;
85+
try {
86+
s = l.shutdownGracefully();
87+
} catch (Exception e) {
88+
LogHolder.LOG.warn("Exception when attempting graceful shutdown", e);
89+
return CompletableFuture.completedFuture(null);
90+
}
91+
return s.toCompletableFuture();
92+
}).toArray(CompletableFuture[]::new));
93+
}
94+
95+
@NonNull
96+
static OptionalLong combineActiveTasks(@NonNull Iterable<? extends GracefulShutdownCapable> delegates) {
97+
long sum = 0;
98+
boolean anyPresent = false;
99+
for (GracefulShutdownCapable delegate : delegates) {
100+
OptionalLong r = delegate.reportActiveTasks();
101+
if (r.isPresent()) {
102+
anyPresent = true;
103+
sum += r.getAsLong();
104+
}
105+
}
106+
return anyPresent ? OptionalLong.of(sum) : OptionalLong.empty();
107+
}
108+
}
109+
110+
@Internal
111+
final class LogHolder {
112+
static final Logger LOG = LoggerFactory.getLogger(GracefulShutdownCapable.class);
113+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.runtime.graceful;
17+
18+
import io.micronaut.context.annotation.ConfigurationProperties;
19+
import io.micronaut.context.annotation.Requires;
20+
import io.micronaut.core.annotation.NonNull;
21+
import io.micronaut.core.bind.annotation.Bindable;
22+
import io.micronaut.core.util.StringUtils;
23+
import io.micronaut.core.util.Toggleable;
24+
25+
import java.time.Duration;
26+
27+
/**
28+
* Configuration for graceful shutdown.
29+
*
30+
* @since 4.9.0
31+
* @author Jonas Konrad
32+
*/
33+
@ConfigurationProperties(GracefulShutdownConfiguration.PREFIX)
34+
@Requires(property = GracefulShutdownConfiguration.ENABLED, value = StringUtils.TRUE, defaultValue = StringUtils.FALSE)
35+
public final class GracefulShutdownConfiguration implements Toggleable {
36+
public static final String PREFIX = "micronaut.lifecycle.graceful-shutdown";
37+
public static final String ENABLED = PREFIX + ".enabled";
38+
39+
private boolean enabled;
40+
@NonNull
41+
private Duration gracePeriod = Duration.ofSeconds(15);
42+
43+
/**
44+
* Whether to enable graceful shutdown on normal shutdown. Off by default.
45+
*
46+
* @return {@code true} to enable graceful shutdown
47+
*/
48+
@Bindable(defaultValue = StringUtils.FALSE)
49+
public boolean isEnabled() {
50+
return enabled;
51+
}
52+
53+
/**
54+
* Whether to enable graceful shutdown on normal shutdown. Off by default.
55+
*
56+
* @param enabled {@code true} to enable graceful shutdown
57+
*/
58+
public void setEnabled(boolean enabled) {
59+
this.enabled = enabled;
60+
}
61+
62+
/**
63+
* Duration to wait until forcing a shutdown.
64+
*
65+
* @return The maximum graceful shutdown duration
66+
*/
67+
@NonNull
68+
public Duration getGracePeriod() {
69+
return gracePeriod;
70+
}
71+
72+
/**
73+
* Duration to wait until forcing a shutdown.
74+
*
75+
* @param gracePeriod The maximum graceful shutdown duration
76+
*/
77+
public void setGracePeriod(@NonNull Duration gracePeriod) {
78+
this.gracePeriod = gracePeriod;
79+
}
80+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.runtime.graceful;
17+
18+
import io.micronaut.context.annotation.Requires;
19+
import io.micronaut.context.event.ApplicationEventListener;
20+
import io.micronaut.context.event.ShutdownEvent;
21+
import io.micronaut.core.annotation.Experimental;
22+
import io.micronaut.core.order.Ordered;
23+
import io.micronaut.core.util.StringUtils;
24+
import jakarta.inject.Singleton;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import java.time.Duration;
29+
import java.util.concurrent.ExecutionException;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.concurrent.TimeoutException;
32+
33+
/**
34+
* Listener that intercepts {@link ShutdownEvent} to initiate and wait for a graceful shutdown, if
35+
* configured.
36+
*
37+
* @since 4.9.0
38+
* @author Jonas Konrad
39+
*/
40+
@Singleton
41+
@Requires(bean = GracefulShutdownManager.class)
42+
@Requires(property = GracefulShutdownConfiguration.ENABLED, value = StringUtils.TRUE, defaultValue = StringUtils.FALSE)
43+
@Experimental
44+
public final class GracefulShutdownListener implements ApplicationEventListener<ShutdownEvent>, Ordered {
45+
private static final Logger LOG = LoggerFactory.getLogger(GracefulShutdownListener.class);
46+
47+
private final GracefulShutdownManager manager;
48+
private final GracefulShutdownConfiguration config;
49+
50+
GracefulShutdownListener(GracefulShutdownManager manager, GracefulShutdownConfiguration config) {
51+
this.manager = manager;
52+
this.config = config;
53+
}
54+
55+
@Override
56+
public void onApplicationEvent(ShutdownEvent event) {
57+
long start = 0;
58+
if (LOG.isDebugEnabled()) {
59+
start = System.nanoTime();
60+
LOG.debug("Starting graceful shutdown...");
61+
}
62+
Duration gracePeriod = config.getGracePeriod();
63+
try {
64+
manager.shutdownGracefully()
65+
.toCompletableFuture()
66+
.get(gracePeriod.toMillis(), TimeUnit.MILLISECONDS);
67+
68+
if (LOG.isDebugEnabled()) {
69+
long end = System.nanoTime();
70+
LOG.debug("Graceful shutdown complete in {}ms", TimeUnit.NANOSECONDS.toMillis(end - start));
71+
}
72+
} catch (InterruptedException e) {
73+
Thread.currentThread().interrupt();
74+
} catch (ExecutionException e) {
75+
LOG.warn("Error in graceful shutdown. This is against the GracefulShutdownCapable contract!", e);
76+
} catch (TimeoutException e) {
77+
LOG.warn("Timeout hit in graceful shutdown, forcing stop");
78+
}
79+
}
80+
81+
@Override
82+
public int getOrder() {
83+
return Ordered.HIGHEST_PRECEDENCE;
84+
}
85+
}

0 commit comments

Comments
 (0)