apache / jena

Apache Jena
https://jena.apache.org/
Apache License 2.0
1.11k stars 650 forks source link

Missing Maven Prometheus dependency in 5.1.0 #2660

Closed DavideRossi closed 1 month ago

DavideRossi commented 2 months ago

Version

5.1.0

What happened?

I have a Helidon MP-based app that uses an embedded Fuseki instance. My pom.xml contains:

<dependency>
  <groupId>org.apache.jena</groupId>
  <artifactId>apache-jena-libs</artifactId>
  <type>pom</type>
  <version>${jena.version}</version>
<dependency>
<dependency>
  <groupId>org.apache.jena</groupId>
  <artifactId>jena-fuseki-main</artifactId>
  <version>${jena.version}</version>
<dependency>

where jena.version is 5.1.0. As soon as my code executes DatasetFactory.createTxnMem() it crashes with a java.lang.NoClassDefFoundError: io/micrometer/prometheusmetrics/PrometheusMeterRegistry exception. The code works just fine with previous Jena/Fuseki versions. Maybe a missing Prometheus dependency?

Relevant output and stacktrace

2024.08.27 19:10:04 WARNING org.glassfish.jersey.server.ServerRuntime$Responder VirtualThread[#51,[0x3c1d1ea1 0x2cee3b67] WebServer socket]/runnable@ForkJoinPool-1-worker-1: An exception mapping did not successfully produce and processed a response. Logging the exception propagated to the default exception mapper.
java.lang.NoClassDefFoundError: io/micrometer/prometheusmetrics/PrometheusMeterRegistry
    at org.apache.jena.fuseki.metrics.prometheus.InitPrometheus.start(InitPrometheus.java:27)
    at org.apache.jena.base.module.Subsystem.lambda$initialize$1(Subsystem.java:117)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at org.apache.jena.base.module.Subsystem.forEach(Subsystem.java:193)
    at org.apache.jena.base.module.Subsystem.forEach(Subsystem.java:169)
    at org.apache.jena.base.module.Subsystem.initialize(Subsystem.java:115)
    at org.apache.jena.sys.JenaSystem.init(JenaSystem.java:89)
    at org.apache.jena.sparql.core.DatasetGraphFactory.<clinit>(DatasetGraphFactory.java:34)
    at org.apache.jena.query.DatasetFactory.createTxnMem(DatasetFactory.java:69)
    at net.morcilab.wot.wt.resources.ThingsResource.retrieveThingById(ThingsResource.java:65)
    at net.morcilab.wot.wt.resources.ThingsResource$Proxy$_$$_WeldClientProxy.retrieveThingById(Unknown Source)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:146)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:189)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:93)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:274)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:266)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:253)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:696)
    at io.helidon.microprofile.server.JaxRsService.doHandle(JaxRsService.java:228)
    at io.helidon.microprofile.server.JaxRsService.lambda$handle$2(JaxRsService.java:185)
    at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
    at io.helidon.microprofile.server.JaxRsService.handle(JaxRsService.java:185)
    at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.doRoute(HttpRoutingImpl.java:165)
    at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.call(HttpRoutingImpl.java:124)
    at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.call(HttpRoutingImpl.java:102)
    at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:76)
    at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:121)
    at io.helidon.webserver.observe.metrics.MetricsFeature.lambda$configureVendorMetrics$2(MetricsFeature.java:90)
    at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:119)
    at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
    at io.helidon.webserver.context.ContextRoutingFeature.filter(ContextRoutingFeature.java:50)
    at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:119)
    at io.helidon.webserver.http.Filters.executeFilters(Filters.java:87)
    at io.helidon.webserver.http.Filters.lambda$filter$0(Filters.java:83)
    at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:76)
    at io.helidon.webserver.http.Filters.filter(Filters.java:83)
    at io.helidon.webserver.http.HttpRoutingImpl.route(HttpRoutingImpl.java:73)
    at io.helidon.webserver.http1.Http1Connection.route(Http1Connection.java:356)
    at io.helidon.webserver.http1.Http1Connection.handle(Http1Connection.java:194)
    at io.helidon.webserver.ConnectionHandler.run(ConnectionHandler.java:165)
    at io.helidon.common.task.InterruptableTask.call(InterruptableTask.java:47)
    at io.helidon.webserver.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:239)
    at java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
Caused by: java.lang.ClassNotFoundException: io.micrometer.prometheusmetrics.PrometheusMeterRegistry
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
    ... 55 more

Are you interested in making a pull request?

None

afs commented 2 months ago

May be your application is picking up an older micrometer via a difference dependency route. The maven resolution rule is "least depth" and not version resolving.

Micrometer 1.12.5 to 1.13.0 made a packing change: This happened at https://github.com/apache/jena/pull/2483/files

io.micrometer.prometheus -> io.micrometer.prometheusmetrics

https://github.com/micrometer-metrics/micrometer/wiki/1.13-Migration-Guide

That's consistent with:

java.lang.NoClassDefFoundError: io/micrometer/prometheusmetrics/PrometheusMeterRegistry

means Jena was compiled against the new package but at runtime it was not found which would be the case if a different micrometer version is being pickjd up.

See what mvn dependency:tree says for your application.

jena-fuseki-main on its own is (current development code base - 1.13.3 is latest micrometer from 1.13.0.

[INFO] --- dependency:3.7.1:tree (default-cli) @ jena-fuseki-main ---
[INFO] org.apache.jena:jena-fuseki-main:jar:5.2.0-SNAPSHOT
[INFO] +- org.apache.jena:jena-fuseki-core:jar:5.2.0-SNAPSHOT:compile
...
[INFO] |  +- io.micrometer:micrometer-core:jar:1.13.3:compile
[INFO] |  |  +- io.micrometer:micrometer-commons:jar:1.13.3:compile
[INFO] |  |  +- io.micrometer:micrometer-observation:jar:1.13.3:compile
[INFO] |  |  +- org.hdrhistogram:HdrHistogram:jar:2.2.2:runtime
[INFO] |  |  \- org.latencyutils:LatencyUtils:jar:2.0.3:runtime
[INFO] |  \- io.micrometer:micrometer-registry-prometheus:jar:1.13.3:compile
[INFO] |     +- io.prometheus:prometheus-metrics-core:jar:1.2.1:compile
[INFO] |     |  +- io.prometheus:prometheus-metrics-model:jar:1.2.1:compile
[INFO] |     |  \- io.prometheus:prometheus-metrics-config:jar:1.2.1:compile
[INFO] |     +- io.prometheus:prometheus-metrics-tracer-common:jar:1.2.1:compile
[INFO] |     \- io.prometheus:prometheus-metrics-exposition-formats:jar:1.2.1:runtime
[INFO] |        \- io.prometheus:prometheus-metrics-shaded-protobuf:jar:1.2.1:runtime
DavideRossi commented 2 months ago

Yup, it's a problem with clashing prometheus versions between fuseki and helidon's metrics provider (the latter depending on io.micrometer:micrometer-core:jar:1.11.3. Given the relative ease of this happening with many other setups, wouldn't it be a reasonable solution making the prometheus dep optional and put it in a different artifact (something like fuseki-prometheus)?

afs commented 2 months ago

It is a nuisance.

We could make it optional and disable Fuseki metrics. If we added an artifact then we'd end up having to keep providing that artifact. Dropping an artifact would break downstream uses.

While it manifests as a java class problem, it is no different really to any other changes between versions.

Suppose 1.11.3 has a NPE and 1.13.x has fixed it. You'll still get the NPE. Or if 1.11.3 gave different answers to 1.13.x - you'd still get wrong answers.

Can you <exclusion> it from Helidon, or add a dependency in your POM for 1.13.x? That way, your app will use a Fuseki compatible one, or your choice, and it should be compatible with Helidon if it is semantic versioning.

Adding a dependency for exactly the version you want seems to be a common way round these multiple version inclusions.

DavideRossi commented 2 months ago

No semantic versioning (see https://github.com/micrometer-metrics/micrometer/wiki/1.13-Migration-Guide API changes/PrometheusMeterRegistry), the guys at micrometer like to change the API across minor versions...

afs commented 2 months ago

For Jena, we haven't encountered much change across versions. The use of micrometer/prometheus is not complicated.

Adding a dependency for exactly the version, or greater, should resolve the issue for you, and it is the one that can be undone without any other disturbance.