Skip to content

Commit 8850c5e

Browse files
authored
StackOverflowError when using PrometheusOptions (#254)
Fixes #251 When the embedded server is enabled, the backend registry creates an internal Vert.x instance, for the sole purpose of serving metrics data over HTTP. In general, it's not a problem because the default Vert.x options don't enable metrics. But if metrics are enabled with the vertx.metrics.options.enabled system property, the internal Vert.x instance will have metrics enabled too (because of code introduced to retrofit Vert.x builder in Vert.x 4). And then the application fail to start with a StackOverflow error (because the default PrometheusBackendRegistry is reused and reinitialized). So, we can avoid that situation by making sure the internal Vert.x instance uses dummy metrics. This problem doesn't apply to Vert.x 5. Signed-off-by: Thomas Segismont <[email protected]>
1 parent ff75188 commit 8850c5e

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

Diff for: src/main/java/io/vertx/micrometer/backends/PrometheusBackendRegistry.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727
import io.vertx.core.http.HttpHeaders;
2828
import io.vertx.core.http.HttpServerOptions;
2929
import io.vertx.core.http.HttpServerRequest;
30+
import io.vertx.core.impl.VertxBuilder;
3031
import io.vertx.core.impl.logging.Logger;
3132
import io.vertx.core.impl.logging.LoggerFactory;
33+
import io.vertx.core.metrics.impl.DummyVertxMetrics;
34+
import io.vertx.micrometer.MicrometerMetricsFactory;
3235
import io.vertx.micrometer.VertxPrometheusOptions;
3336

3437
/**
@@ -70,7 +73,16 @@ public MeterRegistry getMeterRegistry() {
7073
@Override
7174
public void init() {
7275
if (options.isStartEmbeddedServer()) {
73-
this.vertx = Vertx.vertx();
76+
this.vertx = Vertx.builder()
77+
// Make sure this internal Vert.x instance does not reuse the default Prometheus backend registry
78+
// Otherwise, the app can fail to start with java.lang.StackOverflowError
79+
.withMetrics(new MicrometerMetricsFactory() {
80+
@Override
81+
public void init(VertxBuilder builder) {
82+
builder.metrics(DummyVertxMetrics.INSTANCE);
83+
}
84+
})
85+
.build();
7486
// Start dedicated server
7587
HttpServerOptions serverOptions = options.getEmbeddedServerOptions();
7688
if (serverOptions == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.vertx.micrometer.backend;
2+
3+
import io.vertx.core.Launcher;
4+
import io.vertx.core.VertxOptions;
5+
import io.vertx.core.http.HttpServerOptions;
6+
import io.vertx.micrometer.MicrometerMetricsOptions;
7+
import io.vertx.micrometer.MyVerticle;
8+
import io.vertx.micrometer.VertxPrometheusOptions;
9+
import org.junit.After;
10+
import org.junit.Before;
11+
import org.junit.Rule;
12+
import org.junit.Test;
13+
import org.junit.rules.TemporaryFolder;
14+
15+
import java.io.File;
16+
import java.nio.file.Files;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
import static java.util.concurrent.TimeUnit.*;
21+
import static org.junit.Assert.fail;
22+
23+
public class PrometheusEmbeddedLauncherITest {
24+
25+
@Rule
26+
public TemporaryFolder folder = new TemporaryFolder();
27+
28+
private File output;
29+
private Process process;
30+
31+
@Before
32+
public void setUp() throws Exception {
33+
output = folder.newFile();
34+
}
35+
36+
@After
37+
public void tearDown() throws Exception {
38+
if (process != null && process.isAlive()) {
39+
process.destroyForcibly();
40+
}
41+
}
42+
43+
public static final class ShouldNotFailWithStackOverflowError extends Launcher {
44+
public static void main(String[] args) {
45+
ShouldNotFailWithStackOverflowError launcher = new ShouldNotFailWithStackOverflowError();
46+
launcher.dispatch(new String[]{"run", MyVerticle.class.getName(), "--java-opts", "-Dvertx.metrics.options.enabled=true"});
47+
}
48+
49+
@Override
50+
public void beforeStartingVertx(VertxOptions options) {
51+
options.setMetricsOptions(
52+
(new MicrometerMetricsOptions())
53+
.setEnabled(true)
54+
.setPrometheusOptions(
55+
(new VertxPrometheusOptions())
56+
.setEnabled(true)
57+
.setStartEmbeddedServer(true)
58+
.setEmbeddedServerOptions((new HttpServerOptions()).setPort(8181))));
59+
}
60+
}
61+
62+
@Test
63+
public void shouldNotFailWithStackOverflowError() throws Exception {
64+
String javaHome = System.getProperty("java.home");
65+
String classpath = System.getProperty("java.class.path");
66+
67+
List<String> command = new ArrayList<>();
68+
command.add(javaHome + File.separator + "bin" + File.separator + "java");
69+
command.add("-classpath");
70+
command.add(classpath);
71+
command.add(ShouldNotFailWithStackOverflowError.class.getName());
72+
73+
process = new ProcessBuilder(command)
74+
.redirectOutput(output)
75+
.redirectErrorStream(true)
76+
.start();
77+
78+
long start = System.nanoTime();
79+
do {
80+
MILLISECONDS.sleep(500);
81+
if (SECONDS.convert(System.nanoTime() - start, NANOSECONDS) > 5) {
82+
fail("Verticle couldn't be deployed");
83+
}
84+
} while (!verticleDeployed());
85+
}
86+
87+
private boolean verticleDeployed() throws Exception {
88+
if (!process.isAlive()) {
89+
return false;
90+
}
91+
return Files.readAllLines(output.toPath()).stream().anyMatch(s -> s.contains("Succeeded in deploying verticle"));
92+
}
93+
}

0 commit comments

Comments
 (0)