Fazecast / jSerialComm

Platform-independent serial port access for Java
GNU Lesser General Public License v3.0
1.35k stars 287 forks source link

libjSerialComm.jnilib already loaded in another classloader #552

Closed KaYOLii closed 7 months ago

KaYOLii commented 8 months ago

Hello,

we used Version 1.3.11 for some time without any problems.

Now we want to upgrade to Version 2.10.4, because we want to use this very helpful methods: serialPort.getVendorID(); serialPort.getProductID();

But unfortunately we run into this error, when we deploy our war into tomcat 9 on production system (Ubuntu 16). 2024-03-13 16:39:35 ERROR SpringApplication - Application run failed org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaApp': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaSystemService' defined in file [/opt/tomcat/webapps/display/WEB-INF/classes/de/yolii/yoda/services/YodaSystemService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serialDeviceConnectionService': Invocation of init method failed; nested exception is java.lang.UnsatisfiedLinkError: com.fazecast.jSerialComm.SerialPort.getCommPortsNative()[Lcom/fazecast/jSerialComm/SerialPort;

I can reproduce this error on my local dev machine (MacBook with MacOS 14.1.1) with more output: 2024-03-14 12:26:54 WARN AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaApp': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaSystemService' defined in file [/Users/xxx/Dev/apache-tomcat-9.0.74-OhneIntellij/webapps/display/WEB-INF/classes/de/yolii/yoda/services/YodaSystemService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serialDeviceConnectionService': Invocation of init method failed; nested exception is java.lang.UnsatisfiedLinkError: Cannot load native library. Errors as follows: [1]: no jSerialComm in java.library.path [2]: Native Library /Users/xxx/.jSerialComm/2.10.3/libjSerialComm.jnilib already loaded in another classloader It happens on third autodeploy(delete war, copy war into webapps folder) in tomcat.

We tried other versions. We see, the first version where serialPort.getVendorID() is available, is Version 2.10.0. With this version we also see this error.

Do you have any tip for us?

Thanks in Advance

KaYOLii commented 8 months ago

I analysed it a bit more. What I found out: This misbehaviour was introduced in Version 2.9.2.

When I use Version 2.9.1 there is no classloading problem in Tomcat, when doing a redeployment (copy war to webapps, after deleting the old one before). The Native Lib shows up in Tomcat Temp Folder. As you can see in the pictures, with every new deployment, the native lib gets updated.

Deploy Number 1 image

Deploy Number 2 image

But in Version 2.9.2 this process is broken.

Deploy Number 1 image

Deploy Number 2 image The result of the second deployment is an empty folder. I think, this is a result of the error.

When I see this discussion: https://stackoverflow.com/questions/53444627/java-lang-unsatisfiedlinkerror-native-library-already-loaded-in-another-classloa and see, what I found out, then I guess, that we run into this problem, because since Version 2.9.2 the name of the native lib does not change anymore. Like it did with Version 2.9.1 in every deployment.

Is there any Workaround possible?

hedgecrw commented 8 months ago

I see an issue that might be causing your problem. Please test the following SNAPSHOT version and see if your problem is resolved:

SNAPSHOT Version: 2.10.5-SNAPSHOT SNAPSHOT Direct Download Link SNAPSHOT Instructions

KaYOLii commented 8 months ago

Thank you very much for your response and the SNAPSHOT version!

I just tested it, but unfortunately the problem still exists.

2024-03-25 09:22:03 WARN AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaApp': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'yodaSystemService' defined in file [/Users/xxx/Dev/apache-tomcat-9.0.74-OhneIntellij/webapps/display/WEB-INF/classes/de/yolii/yoda/services/YodaSystemService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serialDeviceConnectionService': Invocation of init method failed; nested exception is java.lang.UnsatisfiedLinkError: Cannot load native library. Errors as follows: [1]: no jSerialComm in java.library.path [2]: Native Library /Users/xxx/.jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [3]: Loading for arch: aarch64 [4]: Native Library /Users/xxx/Dev/apache-tomcat-9.0.74-OhneIntellij/temp/jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [5]: Loading for arch: x86_64 [6]: Native Library /Users/xxx/Dev/apache-tomcat-9.0.74-OhneIntellij/temp/jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [7]: Loading for arch: x86 [8]: Native Library /Users/xxx/Dev/apache-tomcat-9.0.74-OhneIntellij/temp/jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [9]: Loading for arch: aarch64 [10]: Native Library /Users/xxx/.jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [11]: Loading for arch: x86_64 [12]: Native Library /Users/xxx/.jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader [13]: Loading for arch: x86 [14]: Native Library /Users/xxx/.jSerialComm/2.10.5-4d76b4c-SNAPSHOT/libjSerialComm.jnilib already loaded in another classloader

Deploy Number 1 Screenshot 2024-03-25 at 09 20 44

Deploy Number 2 Screenshot 2024-03-25 at 09 21 37

hedgecrw commented 7 months ago

The more I'm looking into this, the more it's looking like a Tomcat issue. From Googling other similar issues like this, it looks like Tomcat just doesn't like have native libraries that are extracted at runtime due to the way it handles loading those libraries (multiple times, which isn't allowed in Java). The solution seems to be to manually extract the native library and put it somewhere that Tomcat knows about (I've never used Tomcat, so I'm not much help here about how to configure it.) Once you do that, you may need to start up the app with the jSerialComm.library.path flag set to let jSerialComm know where to expect to find its native library.

The only alternative I can think of would be to make jSerialComm try to extract its native library to a location with a randomized number in the file name (like it was doing many releases earlier) after failing in all other cases. I'd prefer not to do that as it has the potential to litter non-Tomcat user's temp directories with lots of jSerialComm files over time, but it's a potential option. Actually, maybe I could create another startup flag (like jSerialComm.library.randomizeNativeName) that you could set to enable this behavior.

KaYOLii commented 7 months ago

Thanks for the response!

What we did now: Use jSerialComm Version 2.9.1 (the last version with the randomized number in the file name). Use jSerialComm to get all serial Ports, for each port execute Linux command to get VendorId and based on that, execute the necessary code.

Another startup flag (like jSerialComm.library.randomizeNativeName) would be great. Then we could use the newest version including getVendorId().

hedgecrw commented 7 months ago

This has been added. Please test the most recent SNAPSHOT version of the library and see if this resolves the issue:

SNAPSHOT Version: 2.10.5-SNAPSHOT SNAPSHOT Direct Download Link SNAPSHOT Instructions

You will now need to either set the system property as a command-line flag

-DjSerialComm.library.randomizeNativeName="true"

or by calling the following within your own application before accessing any SerialPort functionality:

System.setProperty("jSerialComm.library.randomizeNativeName", "true")
KaYOLii commented 7 months ago

Hey Will, thank you very much for adding the feature! I tested it, it works! No exceptions to see after redeploy and the native library is available with the new name.

Deploy Number 1 Screenshot 2024-04-15 at 12 14 22

Deploy Number 2 Screenshot 2024-04-15 at 12 15 38

Do you know already, when this feature will be available in the official version?

hedgecrw commented 7 months ago

Available now with the release of v2.11.0.

KaYOLii commented 7 months ago

Great! Thank you very much!