Loading native things not twice

Hi anyone,

we wrote a plugin to handle sever lifecycles (e.g. database and so on). For the windows part we use winp library of jenkins to determine the list of processes in windows. This lib contains native parts so we get the following error when using it:

Caused by: java.lang.UnsatisfiedLinkError: Native Library C:…gradle\caches\modules-2\files-2.1\org.jvnet.winp\winp\1.23\7ea543f14a8108a0e20491202d7e25a2f72859c5\winp.59602D8CF2F93C3D220D46F4C7564C94.dll already loaded in another classloader

So far it is pretty clear. My question: Is there a mechanism in gradle, that can handle such things, especially when we want to use the gradle daemon?

Looking forward to your replies.
Cheers
Markus

1 Like

Hello,
I ran into the same problem. I try to make a custom task which depends on a native jar, so I get the message “Library is already loaded in another ClassLoader”.
Did you find any solution to this problem? (except than disabling the daemon mode?)

@Ealrann if I understand correctly, this should be solved by only putting only one “native jar” on the classpath depending on the operating system used. The best way to do that is to make sure the module you depend on provides different variants for different operating systems so that Gradle can automatically select the right one. You can add the variants to libraries with POM metadata using a component metadata rule. In the docs, we even use LWGJ as an example. Maybe you can use that directly. :slight_smile:

https://docs.gradle.org/current/userguide/component_metadata_rules.html#adding_variants_for_native_jars

@jendrik
Indeed, it’s working! Thank you very much.

However, I’m not sure we talked about the same problem (well, maybe I don’t understand everything haha):
We don’t declare the natives for different platforms in our dependencies (otherwise our code would simply crash). The task works the first time we launch it, but when we run it again, Gradle try to reload the dependencies, so the task fail: “Library is already loaded in another ClassLoader”.
A temporary solution was to:

  • gradle --stop
  • disable the gradle daemon mode

For the log (could help somebody else): I replaced this code:

project.ext.lwjglVersion = "3.2.3"

switch (OperatingSystem.current()) {
	case OperatingSystem.LINUX:
		project.ext.lwjglNatives = "natives-linux"
		break
	case OperatingSystem.MAC_OS:
		project.ext.lwjglNatives = "natives-macos"
		break
	case OperatingSystem.WINDOWS:
		project.ext.lwjglNatives = "natives-windows"
		break
}

dependencies {
	implementation "org.lwjgl:lwjgl:$lwjglVersion"
	implementation "org.lwjgl:lwjgl-shaderc:$lwjglVersion"
	runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives"
	runtimeOnly "org.lwjgl:lwjgl-shaderc:$lwjglVersion:$lwjglNatives"
}

By this one:

project.ext.lwjglVersion = "3.2.3"
def osArch = System.getProperty("os.arch")
project.ext.arch = osArch.startsWith("armv8") || osArch.contains("64") ? "x86-64" : "x86"

switch ( OperatingSystem.current() ) {
	case OperatingSystem.WINDOWS:
		project.ext.OS = "windows"
		break
	case OperatingSystem.LINUX:
		project.ext.OS = "linux"
		break
	case OperatingSystem.MAC_OS:
		project.ext.OS = "macos"
		break
}

class LwjglRule implements ComponentMetadataRule { //val os: String, val arch: String, val classifier: String)
	private def nativeVariants = [
		[os: OperatingSystemFamily.LINUX,   arch: "arm32",  classifier: "natives-linux-arm32"],
		[os: OperatingSystemFamily.LINUX,   arch: "arm64",  classifier: "natives-linux-arm64"],
		[os: OperatingSystemFamily.LINUX,   arch: "x86-64", classifier: "natives-linux"],
		[os: OperatingSystemFamily.WINDOWS, arch: "x86",    classifier: "natives-windows-x86"],
		[os: OperatingSystemFamily.WINDOWS, arch: "x86-64", classifier: "natives-windows"],
		[os: OperatingSystemFamily.MACOS,   arch: "x86-64", classifier: "natives-macos"]
	]

	@javax.inject.Inject
	ObjectFactory getObjects() { }

	void execute(ComponentMetadataContext context) {
		context.details.withVariant("runtime") {
			attributes {
				attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "none"))
				attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, "none"))
			}
		}
		nativeVariants.each { variantDefinition ->
			context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
				attributes {
					attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, variantDefinition.os))
					attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, variantDefinition.arch))
				}
				withFiles {
					addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
				}
			}
		}
	}
}

configurations["runtimeClasspath"].attributes {
	attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "$project.ext.OS"))
	attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, "$project.ext.arch"))
}

dependencies {
	components {
		withModule("org.lwjgl:lwjgl", LwjglRule)
		withModule("org.lwjgl:lwjgl-shaderc", LwjglRule)
	}
	implementation "org.lwjgl:lwjgl:$lwjglVersion"
	implementation "org.lwjgl:lwjgl-shaderc:$lwjglVersion"
}

@Ealrann good to hear that it works! Although I indeed misunderstood the issue. Curious why this fixed it. How are the inputs to your custom task defined? Are you using configurations.runtimeClasspath as input of some sorts?

Yeah, I don’t understand neither. So I double checked, and both times, I had a better behavior with the dependencies { components { withModule("org.lwjgl:lwjgl", LwjglRule). But well, maybe I did something wrong somewhere…

I define first an incremental task:

public abstract class GradleShaderc extends DefaultTask
{
	@Incremental
	@PathSensitive(PathSensitivity.NAME_ONLY)
	@InputDirectory
	abstract DirectoryProperty getInputDir();

	@OutputDirectory
	abstract DirectoryProperty getOutputDir();
	
	@TaskAction
	void compileShader(InputChanges inputChanges)
	{
		final var inputDir = getInputDir();
		final var outputDir = getOutputDir();

And then I configure the task in a build.gradle:

shadercCompile {
	inputDir = file("src/main/shader/")
	outputDir = file("src/main/resources/")
}

No.