Skip to content

Commit cc7fb72

Browse files
committed
Introduce strategy for running HealthIndicators
1 parent b6a2a46 commit cc7fb72

File tree

5 files changed

+222
-5
lines changed

5 files changed

+222
-5
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
2323
import org.springframework.boot.actuate.health.HealthAggregator;
2424
import org.springframework.boot.actuate.health.HealthIndicator;
25+
import org.springframework.boot.actuate.health.HealthIndicatorRunner;
2526
import org.springframework.core.ResolvableType;
2627

2728
/**
@@ -31,19 +32,26 @@
3132
* @param <H> the health indicator type
3233
* @param <S> the bean source type
3334
* @author Stephane Nicoll
35+
* @author Vedran Pavic
3436
* @since 2.0.0
3537
*/
3638
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {
3739

3840
@Autowired
3941
private HealthAggregator healthAggregator;
4042

43+
@Autowired(required = false)
44+
private HealthIndicatorRunner healthIndicatorRunner;
45+
4146
protected HealthIndicator createHealthIndicator(Map<String, S> beans) {
4247
if (beans.size() == 1) {
4348
return createHealthIndicator(beans.values().iterator().next());
4449
}
4550
CompositeHealthIndicator composite = new CompositeHealthIndicator(
4651
this.healthAggregator);
52+
if (this.healthIndicatorRunner != null) {
53+
composite.setHealthIndicatorRunner(this.healthIndicatorRunner);
54+
}
4755
beans.forEach((name, source) -> composite.addHealthIndicator(name,
4856
createHealthIndicator(source)));
4957
return composite;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2012-2018 the original author or 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+
* http://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+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.concurrent.Future;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
import org.springframework.core.task.AsyncTaskExecutor;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* A {@link HealthIndicatorRunner} implementation that uses {@link AsyncTaskExecutor} to
31+
* invoke {@link HealthIndicator} instances.
32+
*
33+
* @author Vedran Pavic
34+
* @since 2.1.0
35+
*/
36+
public class AsyncHealthIndicatorRunner implements HealthIndicatorRunner {
37+
38+
private static final Log logger = LogFactory.getLog(AsyncHealthIndicatorRunner.class);
39+
40+
private final AsyncTaskExecutor taskExecutor;
41+
42+
/**
43+
* Create an {@link AsyncHealthIndicatorRunner} instance.
44+
* @param taskExecutor task executor used to run {@link HealthIndicator}s
45+
*/
46+
public AsyncHealthIndicatorRunner(AsyncTaskExecutor taskExecutor) {
47+
Assert.notNull(taskExecutor, "TaskExecutor must not be null");
48+
this.taskExecutor = taskExecutor;
49+
}
50+
51+
@Override
52+
public Map<String, Health> run(Map<String, HealthIndicator> healthIndicators) {
53+
Map<String, Health> healths = new HashMap<>(healthIndicators.size());
54+
Map<String, Future<Health>> futures = new HashMap<>();
55+
for (final Map.Entry<String, HealthIndicator> entry : healthIndicators
56+
.entrySet()) {
57+
Future<Health> future = this.taskExecutor
58+
.submit(() -> entry.getValue().health());
59+
futures.put(entry.getKey(), future);
60+
}
61+
for (Map.Entry<String, Future<Health>> entry : futures.entrySet()) {
62+
try {
63+
healths.put(entry.getKey(), entry.getValue().get());
64+
}
65+
catch (Exception e) {
66+
logger.warn("Error invoking health indicator '" + entry.getKey() + "'",
67+
e);
68+
healths.put(entry.getKey(), Health.down(e).build());
69+
}
70+
}
71+
return healths;
72+
}
73+
74+
}

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
* @author Tyler J. Frederick
2828
* @author Phillip Webb
2929
* @author Christian Dupuis
30+
* @author Vedran Pavic
3031
* @since 1.1.0
3132
*/
3233
public class CompositeHealthIndicator implements HealthIndicator {
@@ -35,6 +36,8 @@ public class CompositeHealthIndicator implements HealthIndicator {
3536

3637
private final HealthAggregator healthAggregator;
3738

39+
private HealthIndicatorRunner healthIndicatorRunner = new SequentialHealthIndicatorRunner();
40+
3841
/**
3942
* Create a new {@link CompositeHealthIndicator}.
4043
* @param healthAggregator the health aggregator
@@ -61,13 +64,34 @@ public void addHealthIndicator(String name, HealthIndicator indicator) {
6164
this.indicators.put(name, indicator);
6265
}
6366

67+
/**
68+
* Set the health indicator runner to invoke the health indicators.
69+
* @param healthIndicatorRunner the health indicator runner
70+
*/
71+
public void setHealthIndicatorRunner(HealthIndicatorRunner healthIndicatorRunner) {
72+
Assert.notNull(healthIndicatorRunner, "HealthIndicatorRunner must not be null");
73+
this.healthIndicatorRunner = healthIndicatorRunner;
74+
}
75+
6476
@Override
6577
public Health health() {
66-
Map<String, Health> healths = new LinkedHashMap<>();
67-
for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
68-
healths.put(entry.getKey(), entry.getValue().health());
69-
}
78+
Map<String, Health> healths = this.healthIndicatorRunner.run(this.indicators);
7079
return this.healthAggregator.aggregate(healths);
7180
}
7281

82+
/**
83+
* {@link HealthIndicatorRunner} for sequential execution of {@link HealthIndicator}s.
84+
*/
85+
private static class SequentialHealthIndicatorRunner
86+
implements HealthIndicatorRunner {
87+
88+
@Override
89+
public Map<String, Health> run(Map<String, HealthIndicator> healthIndicators) {
90+
Map<String, Health> healths = new LinkedHashMap<>();
91+
healthIndicators.forEach((key, value) -> healths.put(key, value.health()));
92+
return healths;
93+
}
94+
95+
}
96+
7397
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2012-2018 the original author or 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+
* http://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+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.Map;
20+
21+
/**
22+
* Strategy interface used by {@link CompositeHealthIndicator} to invoke
23+
* {@link HealthIndicator} instances.
24+
* <p>
25+
* This is useful for customization of invocation in scenarios with many
26+
* {@link HealthIndicator} instances in the system and/or resource demanding ones.
27+
*
28+
* @author Vedran Pavic
29+
* @since 2.1.0
30+
*/
31+
public interface HealthIndicatorRunner {
32+
33+
Map<String, Health> run(Map<String, HealthIndicator> healthIndicators);
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2012-2018 the original author or 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+
* http://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+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import org.junit.Before;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
26+
27+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.BDDMockito.given;
31+
import static org.mockito.Mockito.mock;
32+
33+
/**
34+
* Tests for {@link AsyncHealthIndicatorRunner}.
35+
*
36+
* @author Vedran Pavic
37+
*/
38+
public class AsyncHealthIndicatorRunnerTests {
39+
40+
@Rule
41+
public ExpectedException thrown = ExpectedException.none();
42+
43+
private HealthIndicator one = mock(HealthIndicator.class);
44+
45+
private HealthIndicator two = mock(HealthIndicator.class);
46+
47+
@Before
48+
public void setUp() {
49+
given(this.one.health()).willReturn(new Health.Builder().up().build());
50+
given(this.two.health()).willReturn(new Health.Builder().unknown().build());
51+
}
52+
53+
@Test
54+
public void createAndRun() {
55+
Map<String, HealthIndicator> indicators = new HashMap<>();
56+
indicators.put("one", this.one);
57+
indicators.put("two", this.two);
58+
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
59+
taskExecutor.setMaxPoolSize(2);
60+
taskExecutor.afterPropertiesSet();
61+
HealthIndicatorRunner healthIndicatorRunner = new AsyncHealthIndicatorRunner(
62+
taskExecutor);
63+
Map<String, Health> healths = healthIndicatorRunner.run(indicators);
64+
assertThat(healths.size()).isEqualTo(2);
65+
assertThat(healths.get("one").getStatus()).isEqualTo(Status.UP);
66+
assertThat(healths.get("two").getStatus()).isEqualTo(Status.UNKNOWN);
67+
}
68+
69+
@Test
70+
public void createWithNullTaskExecutor() {
71+
this.thrown.expect(IllegalArgumentException.class);
72+
this.thrown.expectMessage("TaskExecutor must not be null");
73+
new AsyncHealthIndicatorRunner(null);
74+
}
75+
76+
}

0 commit comments

Comments
 (0)