Skip to content

Commit

Permalink
Allow Snappy to be loaded from a shared classloader
Browse files Browse the repository at this point in the history
This commit introduces a solution for loading the Snappy native library across multiple test profiles. Due to the constraint that native libraries can only be loaded from a single classloader, a shared classloader is now utilized for loading Snappy in test mode.

Please note, this feature is exclusively applicable when running tests.

Fixes: quarkusio#39767
  • Loading branch information
cescoffier committed Apr 26, 2024
1 parent 20f148b commit 3ffe398
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public class KafkaBuildTimeConfig {
@ConfigItem(name = "snappy.enabled", defaultValue = "false")
public boolean snappyEnabled;

/**
* Whether to load the Snappy native library from the shared classloader.
* This setting is only used in tests if the tests are using different profiles, which would lead to
* unsatisfied link errors when loading Snappy.
*/
@ConfigItem(name = "snappy.load-from-shared-classloader", defaultValue = "false")
public boolean snappyLoadFromSharedClassLoader;

/**
* Configuration for DevServices. DevServices allows Quarkus to automatically start Kafka in dev and test mode.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LogCategoryBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
Expand Down Expand Up @@ -304,8 +305,12 @@ public void handleSnappyInNative(NativeImageRunnerBuildItem nativeImageRunner,

@BuildStep(onlyIf = HasSnappy.class)
@Record(ExecutionTime.RUNTIME_INIT)
void loadSnappyIfEnabled(SnappyRecorder recorder, KafkaBuildTimeConfig config) {
recorder.loadSnappy();
void loadSnappyIfEnabled(LaunchModeBuildItem launch, SnappyRecorder recorder, KafkaBuildTimeConfig config) {
boolean loadFromSharedClassLoader = false;
if (launch.isTest()) {
loadFromSharedClassLoader = config.snappyLoadFromSharedClassLoader;
}
recorder.loadSnappy(loadFromSharedClassLoader);
}

@Consume(RuntimeConfigSetupCompleteBuildItem.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.kafka.client.runtime;

import java.io.File;

public class SnappyLoader {

/*
* This class is intended to be loaded from a shared classloader (e.g., the system classloader) to avoid
* unsatisfied link errors when the native library is loaded from a different classloader.
* See https://github.com/quarkusio/quarkus/issues/39767.
*
* This class is only used in tests if the `quarkus.kafka.snappy.load-from-shared-classloader=true` is set.
*/
static {
File out = SnappyRecorder.getLibraryFile();
System.load(out.getAbsolutePath());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,32 @@
import java.net.URL;

import org.xerial.snappy.OSInfo;
import org.xerial.snappy.SnappyLoader;

import io.quarkus.runtime.Application;
import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class SnappyRecorder {

public void loadSnappy() {
public void loadSnappy(boolean loadFromSharedClassLoader) {
if (loadFromSharedClassLoader) {
try {
Application.class.getClassLoader().loadClass(SnappyLoader.class.getName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
File out = getLibraryFile();
try {
System.load(out.getAbsolutePath());
} catch (UnsatisfiedLinkError e) {
// Try to load the library from the system library path
throw new RuntimeException("Failed to load Snappy native library", e);
}
}
}

static File getLibraryFile() {
// Resolve the library file name with a suffix (e.g., dll, .so, etc.)
String snappyNativeLibraryName = System.mapLibraryName("snappyjava");
String snappyNativeLibraryPath = "/org/xerial/snappy/native/" + OSInfo.getNativeLibFolderPathForCurrentOS();
Expand All @@ -27,18 +45,16 @@ public void loadSnappy() {
throw new RuntimeException(errorMessage);
}

File out = extractLibraryFile(
return extractLibraryFile(
SnappyLoader.class.getResource(snappyNativeLibraryPath + "/" + snappyNativeLibraryName),
snappyNativeLibraryName);

System.load(out.getAbsolutePath());
}

private static boolean hasResource(String path) {
static boolean hasResource(String path) {
return SnappyLoader.class.getResource(path) != null;
}

private static File extractLibraryFile(URL library, String name) {
static File extractLibraryFile(URL library, String name) {
String tmp = System.getProperty("java.io.tmpdir");
File extractedLibFile = new File(tmp, name);

Expand Down

0 comments on commit 3ffe398

Please sign in to comment.