Skip to content

Commit 3680d05

Browse files
committed
microprofile-health-128 org.eclipse.microprofile.health.HealthCheckResponse leaks
fixes #128 with multiple providers support addition Signed-off-by: Antoine Sabot-Durand <antoine@sabot-durand.net>
1 parent 9226f34 commit 3680d05

File tree

11 files changed

+398
-93
lines changed

11 files changed

+398
-93
lines changed

api/pom.xml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
<artifactId>org.osgi.annotation.versioning</artifactId>
3838
<scope>provided</scope>
3939
</dependency>
40+
<dependency>
41+
<groupId>org.testng</groupId>
42+
<artifactId>testng</artifactId>
43+
</dependency>
4044
</dependencies>
4145

4246
<build>
@@ -97,13 +101,28 @@
97101
</configuration>
98102
<executions>
99103
<execution>
100-
<id>baseline</id>
104+
<id>baseline</id>
101105
<goals>
102106
<goal>baseline</goal>
103107
</goals>
104108
</execution>
105109
</executions>
106110
</plugin>
111+
<plugin>
112+
<groupId>org.apache.maven.plugins</groupId>
113+
<artifactId>maven-surefire-plugin</artifactId>
114+
<version>${maven-surefire-plugin.version}</version>
115+
<executions>
116+
<execution>
117+
<id>default-test</id>
118+
<configuration>
119+
<systemPropertyVariables>
120+
<serviceDir>${project.build.testOutputDirectory}/META-INF/services/</serviceDir>
121+
</systemPropertyVariables>
122+
</configuration>
123+
</execution>
124+
</executions>
125+
</plugin>
107126
</plugins>
108127
</build>
109128
</project>

api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponse.java

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,15 @@
2424

2525
import org.eclipse.microprofile.health.spi.HealthCheckResponseProvider;
2626

27-
import java.security.AccessController;
28-
import java.security.PrivilegedAction;
2927
import java.util.Map;
3028
import java.util.Optional;
31-
import java.util.ServiceLoader;
32-
import java.util.logging.Level;
3329
import java.util.logging.Logger;
3430

3531
/**
3632
* The response to a health check invocation.
3733
* <p>
3834
* The {@link HealthCheckResponse} class is reserved for an extension by implementation providers.
39-
* An application should use one of the static methods to create a Response instance using a
35+
* An application should use one of the static methods to create a Response instance using a
4036
* {@link HealthCheckResponseBuilder}.
4137
* When used on the consuming end, The class can also be instantiated directly.
4238
* </p>
@@ -45,8 +41,6 @@ public class HealthCheckResponse {
4541

4642
private static final Logger LOGGER = Logger.getLogger(HealthCheckResponse.class.getName());
4743

48-
private static volatile HealthCheckResponseProvider provider = null;
49-
5044
private final String name;
5145

5246
private final Status status;
@@ -78,9 +72,10 @@ public HealthCheckResponse() {
7872
* Used by OSGi environment where the service loader pattern is not supported.
7973
*
8074
* @param provider the provider instance to use.
75+
* @deprecated use {{@link HealthCheckResponseProviderResolver#setProvider}} instead
8176
*/
8277
public static void setResponseProvider(HealthCheckResponseProvider provider) {
83-
HealthCheckResponse.provider = provider;
78+
HealthCheckResponseProviderResolver.setProvider(provider);
8479
}
8580

8681
/**
@@ -91,7 +86,7 @@ public static void setResponseProvider(HealthCheckResponseProvider provider) {
9186
*/
9287
public static HealthCheckResponseBuilder named(String name) {
9388

94-
return getProvider().createResponseBuilder().name(name);
89+
return HealthCheckResponseProviderResolver.getProvider().createResponseBuilder().name(name);
9590
}
9691

9792
/**
@@ -102,12 +97,12 @@ public static HealthCheckResponseBuilder named(String name) {
10297
* @return a new, empty health check builder
10398
*/
10499
public static HealthCheckResponseBuilder builder() {
105-
return getProvider().createResponseBuilder();
100+
return HealthCheckResponseProviderResolver.getProvider().createResponseBuilder();
106101
}
107102

108103
/**
109104
* Creates a successful health check with a name.
110-
*
105+
*
111106
* @param name the check name
112107
* @return a new sucessful health check response with a name
113108
*/
@@ -117,33 +112,14 @@ public static HealthCheckResponse up(String name) {
117112

118113
/**
119114
* Creates a failed health check with a name.
120-
*
115+
*
121116
* @param name the check name
122117
* @return a new failed health check response with a name
123118
*/
124119
public static HealthCheckResponse down(String name) {
125120
return HealthCheckResponse.named(name).down().build();
126121
}
127122

128-
private static HealthCheckResponseProvider getProvider() {
129-
if (provider == null) {
130-
synchronized (HealthCheckResponse.class) {
131-
if (provider != null) {
132-
return provider;
133-
}
134-
135-
HealthCheckResponseProvider newInstance = find(HealthCheckResponseProvider.class);
136-
137-
if (newInstance == null) {
138-
throw new IllegalStateException("No HealthCheckResponseProvider implementation found!");
139-
}
140-
141-
provider = newInstance;
142-
}
143-
}
144-
return provider;
145-
}
146-
147123
// the actual contract
148124

149125
public enum Status {UP, DOWN}
@@ -160,61 +136,4 @@ public Optional<Map<String, Object>> getData() {
160136
return data;
161137
}
162138

163-
private static <T> T find(Class<T> service) {
164-
165-
T serviceInstance = find(service, HealthCheckResponse.getContextClassLoader());
166-
167-
// alternate classloader
168-
if (null == serviceInstance) {
169-
serviceInstance = find(service, HealthCheckResponse.class.getClassLoader());
170-
}
171-
172-
// service cannot be found
173-
if (null == serviceInstance) {
174-
throw new IllegalStateException("Unable to find service " + service.getName());
175-
}
176-
177-
return serviceInstance;
178-
}
179-
180-
private static <T> T find(Class<T> service, ClassLoader cl) {
181-
182-
T serviceInstance = null;
183-
184-
try {
185-
ServiceLoader<T> services = ServiceLoader.load(service, cl);
186-
187-
for (T spi : services) {
188-
if (serviceInstance != null) {
189-
throw new IllegalStateException(
190-
"Multiple service implementations found: "
191-
+ spi.getClass().getName() + " and "
192-
+ serviceInstance.getClass().getName());
193-
}
194-
serviceInstance = spi;
195-
}
196-
}
197-
catch (Throwable t) {
198-
LOGGER.log(Level.SEVERE, "Error loading service " + service.getName() + ".", t);
199-
}
200-
201-
return serviceInstance;
202-
}
203-
204-
205-
private static ClassLoader getContextClassLoader() {
206-
return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
207-
ClassLoader cl = null;
208-
try {
209-
cl = Thread.currentThread().getContextClassLoader();
210-
}
211-
catch (SecurityException ex) {
212-
LOGGER.log(
213-
Level.WARNING,
214-
"Unable to get context classloader instance.",
215-
ex);
216-
}
217-
return cl;
218-
});
219-
}
220139
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (c) 2020 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICES file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
* SPDX-License-Identifier: Apache-2.0
20+
*
21+
*/
22+
23+
package org.eclipse.microprofile.health;
24+
25+
import java.util.Collections;
26+
import java.util.ServiceConfigurationError;
27+
import java.util.ServiceLoader;
28+
import java.util.Set;
29+
import java.util.HashSet;
30+
import java.util.function.Predicate;
31+
32+
import org.eclipse.microprofile.health.spi.HealthCheckResponseProvider;
33+
34+
35+
/**
36+
* This class an helper to retrieve the Microprofile HealthCheck implementation
37+
* by using Java {@link ServiceLoader} api.
38+
* It allows the usage of multiple implementations at the same time.
39+
*
40+
* @author Antoine Sabot-Durand
41+
* @since 3.0
42+
*/
43+
public class HealthCheckResponseProviderResolver {
44+
45+
private static final Object LOCK = new Object();
46+
47+
private static volatile HealthCheckResponseProvider provider = null;
48+
49+
private static volatile Set<HealthCheckResponseProvider> discoveredProviders = null;
50+
51+
private static volatile Predicate<HealthCheckResponseProvider> providerPredicate = p -> true;
52+
53+
protected HealthCheckResponseProviderResolver() {
54+
}
55+
56+
/**
57+
* @return the selected {@link HealthCheckResponseProvider} in cache. If not available it tries to resolve it first
58+
*/
59+
public static HealthCheckResponseProvider getProvider() {
60+
if (provider == null) {
61+
if (discoveredProviders == null) {
62+
synchronized (LOCK) {
63+
if (discoveredProviders == null) {
64+
findAllProviders();
65+
}
66+
}
67+
}
68+
provider = discoveredProviders.stream()
69+
.filter(providerPredicate)
70+
.findFirst().orElseThrow(() -> new IllegalStateException("No HealthCheckResponseProvider implementation found!"));
71+
}
72+
return provider;
73+
}
74+
75+
/**
76+
*
77+
* Used to manually set the {@link HealthCheckResponseProvider}.
78+
* Also used by OSGi environment where the service loader pattern is not supported.
79+
*
80+
* @param provider the provider instance to use.
81+
*/
82+
public static void setProvider(HealthCheckResponseProvider provider) {
83+
HealthCheckResponseProviderResolver.provider = provider;
84+
}
85+
86+
/**
87+
*
88+
* Other way than {@link #setProvider} to set the {@link HealthCheckResponseProvider}
89+
*
90+
* @param providerPredicate a predicate to choose the matching provider
91+
*/
92+
public static void setProviderPredicate(Predicate<HealthCheckResponseProvider> providerPredicate) {
93+
HealthCheckResponseProviderResolver.providerPredicate = providerPredicate;
94+
setProvider(null);
95+
}
96+
97+
private static void findAllProviders() {
98+
ServiceLoader<HealthCheckResponseProvider> providerLoader;
99+
Set<HealthCheckResponseProvider> providers = new HashSet<>();
100+
101+
Class<HealthCheckResponseProvider> clazz = HealthCheckResponseProvider.class;
102+
ClassLoader cl = SecurityActions.getContextClassLoader();
103+
if (cl == null) {
104+
cl = clazz.getClassLoader();
105+
}
106+
providerLoader = SecurityActions.loadService(HealthCheckResponseProvider.class, cl);
107+
108+
if (!providerLoader.iterator().hasNext()) {
109+
throw new IllegalStateException("Unable to locate HealthCheckResponseProvider");
110+
}
111+
112+
try {
113+
providerLoader.forEach(providers::add);
114+
}
115+
catch (ServiceConfigurationError e) {
116+
throw new IllegalStateException(e);
117+
}
118+
discoveredProviders = Collections.unmodifiableSet(providers);
119+
}
120+
121+
protected static void reset() {
122+
provider = null;
123+
discoveredProviders =null;
124+
providerPredicate = p -> true;
125+
126+
127+
}
128+
129+
}

0 commit comments

Comments
 (0)