Skip to content

fix(plugin-openlineage-event-listener): Fix NoClassDefFoundError when emitting query statistics - #28463

Open
jja725 wants to merge 2 commits into
prestodb:masterfrom
jja725:jj/fix-openlineage-blackbird
Open

jja725 wants to merge 2 commits into
prestodb:masterfrom
jja725:jj/fix-openlineage-blackbird

Conversation

@jja725

@jja725 jja725 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Description

The OpenLineage event listener built its QueryStatistics mapper with ObjectMapper.findAndRegisterModules(). On a packaged server this service-loads jackson-module-blackbird, a transitive runtime dependency of openlineage-java. Blackbird generates its accessor lambdas inside the class loader of the serialized class. QueryStatistics is an SPI class loaded by the server's application class loader, which cannot see the plugin's jars, so serializing the first boolean property fails with NoClassDefFoundError: com/fasterxml/jackson/module/blackbird/ser/ToBooleanFunction and no COMPLETE event is emitted.

This PR:

  • registers Jdk8Module and JavaTimeModule explicitly instead of discovering modules through ServiceLoader, and declares the two dependencies. They were the only functional modules discovered before, so the emitted presto_query_statistics facet is unchanged;
  • adds TestOpenLineageEventListenerPluginIsolation, a unit test that rebuilds the server's plugin / SPI class loader split in-process (same delegation rules as PluginClassLoader) and runs getCompletedEvent through it. On master it fails with the exact error from the issue; it runs with the regular module tests, so this class of bug no longer needs a packaged server to show up;
  • adds TestPrestoContainerOpenLineageEventListener to the existing container tests in presto-native-execution. It uses ContainerQueryRunner in Java-cluster mode with the listener enabled on the coordinator (new Config.setEventListenerProperties), posts events over HTTP to a small sink container on the cluster network, runs a query, and asserts that the COMPLETE event with the presto_query_statistics facet arrives and that the coordinator log has no NoClassDefFoundError. It is picked up by the existing prestocpp-linux-container-tests workflow, whose path filter now also includes the plugin module.

Motivation and Context

Fixes #28460. The existing unit tests run everything from one flat class path where Blackbird is visible, so the failure only appeared on a real server.

Impact

Bug fix, no user-facing configuration change. The content of the presto_query_statistics facet is unchanged.

Test Plan

  • TestOpenLineageEventListenerPluginIsolation fails on master with java.lang.InternalError: java.lang.NoClassDefFoundError: com/fasterxml/jackson/module/blackbird/ser/ToBooleanFunction and passes with the fix.
  • ./mvnw install -pl presto-openlineage-event-listener (all checks and tests) passes; the isolation test also passes when run from the reactor with presto-spi and presto-common as class directories.
  • TestPrestoContainerOpenLineageEventListener runs in the prestocpp-linux-container-tests workflow on this PR.

Contributor checklist

  • Please make sure your submission complies with our contributing guide, in particular code style and commit standards.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.
  • If adding new dependencies, verified they have an OpenSSF Scorecard score of 5.0 or higher (or obtained explicit TSC approval for lower scores).

Release Notes

Please follow release notes guidelines and fill in the release notes below.

== RELEASE NOTES ==

General Changes
* Fix ``NoClassDefFoundError`` in the OpenLineage event listener when emitting query completion events on a Presto server. See :doc:`/develop/openlineage-event-listener`.

🤖 Generated with Claude Code

Summary by Sourcery

Fix OpenLineage query completion event serialization across Presto plugin class-loader boundaries.

Bug Fixes:

  • Prevent the OpenLineage event listener from failing with NoClassDefFoundError when emitting query statistics on packaged Presto servers.

Enhancements:

  • Use explicit Jackson module registration to preserve query statistics facet output while avoiding incompatible plugin class-loader discovery.

Build:

  • Add explicit Jackson JDK 8 and Java time module dependencies and test support for Jackson databind.

CI:

  • Run the native container test workflow when changes affect the OpenLineage event listener plugin.

Tests:

  • Add isolated class-loader coverage for completed OpenLineage events.
  • Add packaged coordinator integration coverage that verifies delivery of completion events, query statistics, dataset metadata, and absence of class-loading errors.

… emitting query statistics

ObjectMapper.findAndRegisterModules() service-loads every Jackson module
on the plugin class path, including jackson-module-blackbird, a runtime
dependency of openlineage-java. Blackbird generates its accessor lambdas
inside the class loader of the serialized class. QueryStatistics is an
SPI class loaded by the server's application class loader, which cannot
see the plugin's jars, so serializing it on a real server fails with
NoClassDefFoundError: com/fasterxml/jackson/module/blackbird/ser/ToBooleanFunction
and no COMPLETE event is emitted.

Register Jdk8Module and JavaTimeModule explicitly instead; they were the
only functional modules discovered before, so the emitted facet is
unchanged. Add a test that runs the listener through an isolated plugin
class loader mirroring PluginClassLoader, so this class of bug shows up
in the regular unit tests instead of only on a packaged server.

Fixes prestodb#28460

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jja725
jja725 requested review from a team, czentgr and unidevel as code owners September 9, 2026 21:12
@jja725
jja725 requested a review from amitkdutta September 9, 2026 21:12
@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR fixes packaged-server OpenLineage failures by replacing Jackson ServiceLoader discovery with explicit functional modules, then verifies the class-loader behavior both in-process and against a Docker-packaged coordinator through a dedicated CI workflow.

Sequence diagram for isolated OpenLineage query statistics emission

sequenceDiagram
    participant Coordinator as PrestoCoordinator
    participant Listener as OpenLineageEventListener
    participant Mapper as QUERY_STATISTICS_MAPPER
    participant SPI as QueryStatistics
    participant Receiver as OpenLineageReceiver

    Coordinator->>Listener: getCompletedEvent
    Listener->>Mapper: writeValueAsString
    Mapper->>SPI: serialize QueryStatistics
    Note over Mapper,SPI: Explicit Jdk8Module and JavaTimeModule avoid Blackbird ServiceLoader discovery
    Mapper-->>Listener: JSON presto_query_statistics facet
    Listener->>Receiver: emit COMPLETE event
Loading

File-Level Changes

Change Details Files
Avoid Jackson module auto-discovery so plugin serialization cannot load Blackbird into the SPI class loader.
  • Replace findAndRegisterModules() with explicit JDK 8 and Java Time module registration.
  • Declare the explicitly used Jackson datatype dependencies.
  • Preserve the existing query-statistics facet serialization behavior.
presto-openlineage-event-listener/src/main/java/com/facebook/presto/plugin/openlineage/OpenLineageEventListener.java
presto-openlineage-event-listener/pom.xml
Add an in-process regression test that reproduces the server/plugin class-loader boundary.
  • Create isolated SPI and plugin URL class loaders using PluginClassLoader-like delegation rules.
  • Execute getCompletedEvent through the isolated listener and verify query-statistics fields are emitted.
  • Ensure the test would expose the Blackbird NoClassDefFoundError hidden by a flat test classpath.
presto-openlineage-event-listener/src/test/java/com/facebook/presto/plugin/openlineage/TestOpenLineageEventListenerPluginIsolation.java
Add packaged-server coverage for OpenLineage completion events.
  • Start the coordinator Docker image with the listener configured to post to an HTTP receiver in the test JVM.
  • Run a CTAS using the image's presto-cli and validate the COMPLETE event, metadata, statistics facet, and output dataset.
  • Check coordinator logs for class-loading failures and clean up container, receiver, and temporary configuration resources.
presto-openlineage-event-listener/src/test/java/com/facebook/presto/plugin/openlineage/TestPrestoContainerOpenLineageEventListener.java
presto-openlineage-event-listener/pom.xml
Automate container-based Java integration tests in CI.
  • Build Java artifacts and the coordinator image from the repository Dockerfile.
  • Run the TestPrestoContainer* suite in a dedicated workflow with Docker, Maven caching, timeouts, and path-based triggering.
  • Keep container tests excluded from the default Surefire run while allowing explicit execution.
.github/workflows/java-container-tests.yml
presto-openlineage-event-listener/pom.xml

Assessment against linked issues

Issue Objective Addressed Explanation
#28460 Prevent the OpenLineage event listener from loading Jackson Blackbird in a way that causes a NoClassDefFoundError for ToBooleanFunction when serializing QueryStatistics on a packaged Presto server.
#28460 Ensure completed queries are successfully processed and a COMPLETE OpenLineage event containing the presto_query_statistics facet is emitted.
#28460 Add regression coverage for the real plugin/SPI class-loader separation and packaged-server behavior that triggers the failure.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

…penLineage event listener

Run the listener inside the packaged coordinator image with ContainerQueryRunner
in Java-cluster mode and send its events to a real Marquez server started on the
cluster network, following Trino's TestOpenLineageEventListenerMarquezIntegration.
Marquez validates events against the OpenLineage specification before storing
them, so the test asserts through the Marquez API that the query's job reached
COMPLETED, that the presto_metadata and presto_query_statistics run facets
arrived, and that the tpch.tiny.nation input dataset was registered with its
schema. The coordinator log must not contain NoClassDefFoundError or a stack
trace from the plugin.

ContainerQueryRunner.Config gains setEventListenerProperties to write the
coordinator's event-listener.properties. The existing
prestocpp-linux-container-tests workflow discovers the test automatically; its
path filter now also covers the plugin module.
@jja725
jja725 force-pushed the jj/fix-openlineage-blackbird branch from 92b6c5c to 54f286b Compare September 10, 2026 12:07
@jja725

jja725 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@Dilli-Babu-Godari @imjalpreet could u take a look at this when you are free? Thanks. I also added a container test for openlineage #28467.

@imjalpreet imjalpreet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix, @jja725.

I have a few suggestions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jja725, any reason for including this test in the presto-native-execution module?

Since .github/workflows/prestocpp-linux-container-tests.yml adds presto-openlineage-event-listener/** to the paths filter, every future one-line change to the OpenLineage plugin now triggers a multi-hour native C++ build, just to run a test that explicitly sets setNativeCluster(false) and never touches a native worker.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test class also has nothing to do with native execution. I would recommend presto-product-tests as the module. That module is the home for many E2E docker based integration tests

Comment on lines +64 to +74
private static final List<String> SPI_PACKAGES = ImmutableList.of(
"com.facebook.presto.spi.",
"com.fasterxml.jackson.annotation.",
"com.fasterxml.jackson.module.afterburner.",
"io.airlift.slice.",
"com.facebook.airlift.units.",
"org.openjdk.jol.",
"com.facebook.presto.common",
"com.facebook.drift.annotations.",
"com.facebook.drift.TException",
"com.facebook.drift.TApplicationException");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we directly use PluginManagerUtil.SPI_PACKAGES here? It's already public, so a test-scope dependency on presto-main-base should be fine.

Comment on lines +178 to +187
public ContainerQueryRunner(
int coordinatorPort,
String catalog,
String schema,
int numberOfWorkers,
boolean isNativeCluster,
boolean isSidecarEnabled,
int functionServerPort,
boolean enableFunctionServer,
Map<String, String> eventListenerProperties)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer adding the new parameter to the existing constructor since the current constructor is anyway unused.

assertTrue(fields.containsAll(ImmutableSet.of("n_nationkey", "n_regionkey")), "dataset fields: " + fields);

String coordinatorLogs = ((ContainerQueryRunner) getQueryRunner()).getCoordinatorLogs();
assertFalse(coordinatorLogs.contains("NoClassDefFoundError"), "coordinator log contains NoClassDefFoundError");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail on any unrelated NoClassDefFoundError anywhere in the coordinator log, from any plugin. Scoping it to the OpenLineage package might be better, what do you think?

tail(marquez.getLogs()),
tail(((ContainerQueryRunner) getQueryRunner()).getCoordinatorLogs())));
}
MILLISECONDS.sleep(500);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's preferred to not have Thread.sleep() in tests. A bounded poll with a deadline might be better.

return job;
}
}
if (System.nanoTime() > deadline) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
if (System.nanoTime() > deadline) {
if (System.nanoTime() - deadline > 0) {

protected GenericContainer<?> functionServer;
protected int functionServerPort;
protected boolean enableFunctionServer;
protected final Map<String, String> eventListenerProperties;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe move this before protected GenericContainer<?> functionServer; to maintain the current ordering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenLineage connector fails with NoClassDefFoundError for Jackson Blackbird class

2 participants