-
Notifications
You must be signed in to change notification settings - Fork 619
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
7150: Fix issues with class loading for external exporters relying on the Thread's context class loader r=npepinpe a=npepinpe ## Description This PR fixes issues when an external exporter (i.e. a self-contained JAR) wants to load classes via the current thread's context class loader. This occurred, for example, in the Kafka exporter, as the Kafka library will use the current thread's context class loader instead of just the current class loader. The fix is to wrap every exporter call and set/reset the context class loader before/after. Part of this PR brings improvements to the exporter tests, mostly relating to external exporters. It introduces ByteBuddy as a simpler alternative to building external exporters: ByteBuddy is a battle tested library which gives you an easy to use API to implement classes on the fly and package them as JARs. Additionally, by using this instead of pre-made classes and building JARs out of them, we can guarantee that the class ByteBuddy creates does not exist in the current class loader, which increases the robustness of our tests. This PR also introduces junit5 to the broker and migrates the modified tests to it, as part of our long term goals to migrate from junit4 to junit5. I recommend the reviewer to check commit-by-commit, but keep in mind that the last commit is the "goal" of the PR, and all the work prior to it is essentially building up to it. I'm also happy to split this into multiple PR if that's easier to review instead, don't hesitate to ask! I hesitated on this as I felt it was important to keep the context, but in the end I think the reviewer will have better insight in what's easier for them. I have some worries about the PR, in that while using ByteBuddy is probably more reliable than writing out own code to churn out classes on the fly and create JARs for them, it's also a little "arcane" if you've never used it, and I fear the test is maybe more complex than desired. I'd be happy to hear thoughts on how we could avoid this complexity while still testing the functionality. Although working on this really impressed on me that we may want to rethink exporters, and instead of running arbitrary code in the broker, we may just want to provide an API to get data out. ## Related issues closes #4196 Co-authored-by: Nicolas Pepin-Perreault <nicolas.pepin-perreault@camunda.com>
- Loading branch information
Showing
19 changed files
with
637 additions
and
385 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
broker/src/main/java/io/camunda/zeebe/broker/exporter/jar/ThreadContextUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under | ||
* one or more contributor license agreements. See the NOTICE file distributed | ||
* with this work for additional information regarding copyright ownership. | ||
* Licensed under the Zeebe Community License 1.1. You may not use this file | ||
* except in compliance with the Zeebe Community License 1.1. | ||
*/ | ||
package io.camunda.zeebe.broker.exporter.jar; | ||
|
||
import io.camunda.zeebe.util.CheckedRunnable; | ||
import org.agrona.LangUtil; | ||
|
||
/** | ||
* A collection of utilities to run an arbitrary {@link Runnable} with a specific thread context | ||
* class loader. This is required when side loading external code via the {@link | ||
* ExporterJarClassLoader}, as that code may be using the {@link Thread#getContextClassLoader()}. | ||
* | ||
* <p>As the same thread may be reused, it's also important to reset the thread afterwards to avoid | ||
* operations being run on the wrong class loader. | ||
*/ | ||
public final class ThreadContextUtil { | ||
|
||
/** | ||
* Executes the given {@code runnable}, swapping the thread context class loader for the given | ||
* class loader, and swapping it back with the previous class loader afterwards. | ||
* | ||
* @param runnable the operation to execute | ||
* @param classLoader the class loader to temporarily assign to the current thread's context class | ||
* loader | ||
*/ | ||
public static void runWithClassLoader(final Runnable runnable, final ClassLoader classLoader) { | ||
try { | ||
runCheckedWithClassLoader(runnable::run, classLoader); | ||
} catch (final Exception e) { | ||
LangUtil.rethrowUnchecked(e); | ||
} | ||
} | ||
|
||
/** | ||
* Executes the given {@code runnable}, swapping the thread context class loader for the given | ||
* class loader, and swapping it back with the previous class loader afterwards. | ||
* | ||
* <p>Use this method if you want your operation to throw exceptions; the class loader is | ||
* guaranteed to be reset even if an exception is thrown. | ||
* | ||
* @param runnable the operation to execute | ||
* @param classLoader the class loader to temporarily assign to the current thread's context class | ||
* loader | ||
*/ | ||
public static void runCheckedWithClassLoader( | ||
final CheckedRunnable runnable, final ClassLoader classLoader) throws Exception { | ||
final var currentThread = Thread.currentThread(); | ||
final var contextClassLoader = currentThread.getContextClassLoader(); | ||
|
||
try { | ||
currentThread.setContextClassLoader(classLoader); | ||
runnable.run(); | ||
} finally { | ||
currentThread.setContextClassLoader(contextClassLoader); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.