Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] ClassLoader tries to open libzenoh_jni.so twice, crashes #315

Open
ao-swarm opened this issue Dec 4, 2024 · 4 comments
Open

[Bug] ClassLoader tries to open libzenoh_jni.so twice, crashes #315

ao-swarm opened this issue Dec 4, 2024 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@ao-swarm
Copy link

ao-swarm commented Dec 4, 2024

Describe the bug

I'm developing an Android application plugin. Due to the nature of the base application, it's possible to unload and reload plugins. My plugin uses Zenoh,

When I reload my plugin, the ClassLoader attempts to open the libzenoh_jni.so. However, it was already opened the first time I loaded my plugin, and an exception is thrown. Ideally, libzenoh_jni.so shouldn't be opened if it's already open - not due to my use case, but as a general safety feature.

I imagine a conditional check or a try/catch around this logic would be fine to prevent the issue? I haven't had a chance to look around for where this is happening.

open for stacktrace
19:01:55.011 atakmap.app.civ                           W  Shared library "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64/libzenoh_jni.so" already opened by ClassLoader 0x2313(dalvik.system.DexClassLoader[DexPathList[[zip file "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/base.apk"],nativeLibraryDirectories=[/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64, /system/lib64, /system_ext/lib64]]]); can't open in ClassLoader 0x704c4833fc(dalvik.system.DexClassLoader[DexPathList[[zip file "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/base.apk"],nativeLibraryDirectories=[/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64, /system/lib64, /system_ext/lib64]]])

19:01:58.942 AndroidRuntime                            E  FATAL EXCEPTION: DefaultDispatcher-worker-5 
    Process: com.atakmap.app.civ, PID: 9588
    java.lang.UnsatisfiedLinkError: Shared library "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64/libzenoh_jni.so" already opened by ClassLoader 0x2313(dalvik.system.DexClassLoader[DexPathList[[zip file "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/base.apk"],nativeLibraryDirectories=[/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64, /system/lib64, /system_ext/lib64]]]); can't open in ClassLoader 0x704c4833fc(dalvik.system.DexClassLoader[DexPathList[[zip file "/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/base.apk"],nativeLibraryDirectories=[/data/app/~~LbneWYpt9hcdC4AQS_EfAA==/com.atakmap.myPlugin-9V5PJpMuoIuV69MN6XoV3A==/lib/arm64, /system/lib64, /system_ext/lib64]]])
    at java.lang.Runtime.loadLibrary0(Runtime.java:1081)
    at java.lang.Runtime.loadLibrary0(Runtime.java:1003)
    at java.lang.System.loadLibrary(System.java:1765)
    at io.zenoh.ZenohLoad.<clinit>(Zenoh.kt:25)
    at io.zenoh.jni.JNIConfig.<clinit>(JNIConfig.kt:28)
    at io.zenoh.Config$Companion.fromJson5-IoAF18A(Config.kt:229)
    at com.atakmap.muPlugin.ZenohClient$Companion.init(ZenohClient.kt:194)
    at com.atakmap.myPlugin.MapComponent$startZenohS$1.invokeSuspend(MapComponent.kt:89)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:33)
    at kotlinx.coroutines.DispatchedTask.run(SourceFile:106)
    at kotlinx.coroutines.internal.LimitedDispatcher.run(SourceFile:42)
    at kotlinx.coroutines.scheduling.TaskImpl.run(SourceFile:95)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(SourceFile:570)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(SourceFile:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(SourceFile:677)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(SourceFile:664)
    Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelled}@c40d51a, Dispatchers.IO]

To reproduce

I'm not sure I have clear steps for this. I run the ATAK-CIV platform with my plugin on top of it, but it's probably unreasonable to build out a sample of that.

My steps anyway:

  • start ATAK application
  • load plugin
  • open zenoh sessions
  • unload plugin
    • zenoh sessions close on unload
  • reload plugin
  • get error before new sessions are opened

What would be other ways to trigger class loading?

System info

  • Platform: Android phone, API 30
  • Zenoh: 1.0.0
@ao-swarm ao-swarm added the bug Something isn't working label Dec 4, 2024
@DariusIMP DariusIMP self-assigned this Dec 4, 2024
@ao-swarm
Copy link
Author

ao-swarm commented Dec 5, 2024

I see there's the ZenohLoad singleton, which is meant to 'load the Zenoh native library once and only once'. However, my plugin use case destroys and recreates the singleton, thus attempting to reload the native library. But what I didn't realize is there's no great way to directly check if the native library is already loaded when the singleton is (re)created. (JNI work is weird, I guess.) Let me know if you have any thoughts on a solution and if I can help.

Thought - could catch the UnsatisifiedLinkerError and look for 'already loaded' in the message? Then take loaded class? I'm not sure I know enough about JNI loading to know this works lol.

@DariusIMP
Copy link
Member

DariusIMP commented Dec 6, 2024

Yes, you resumed it pretty well, we have a singleton to avoid loading multiple times the library. On top of that, System.loadLibrary() should ignore subsequent calls to load a same library (after the doc here, and I have in fact checked this by hardcoding multiple System.loadLibrary() calls).

Reading your error, I see that somehow you end up with multiple class loaders, I think if you only had a single class loader then the subsequent call to the library would be ignored.

I wouldn't like to edit the lib to catch the UnsatisfiedLinkError because there may be situations where we want it to be thrown, for instance for Android we support a series of platforms on top of which the lib can run, what if the user wants to use it on an android device whose platform isn't supported? Then the exception should not be catched.
But maybe for your use case, you could fork the repo and add the catch, that may unblock you.

@ao-swarm
Copy link
Author

ao-swarm commented Dec 6, 2024

Yeah, I was thinking about a fork to try handling this temporarily. I agree that catching the error is not a great final solution, even if you'd filter and rethrow.

Reading your error, I see that somehow you end up with multiple class loaders

Correct. The ATAK platform supports complete loading/unloading of plugins and their dependencies within its application lifetime. So I needed to make sure that loading/unloading Zenoh with my plugin would function. When the load/unload occurs, I get new instances of things. It's an abnormal use case, I think, but I need to find a way to handle it.

If I get something working, I'll share for reference. If I don't get the time, I may just wait to see what you come up with.

@DariusIMP
Copy link
Member

The only thing that comes to my mind to properly resolve this at first glance, is for you to handle your own class loaders, and somehow expose an API here to load the native library using a provided class loader, instead of the system's class loader. Then when the plugin is dropped, the class loader would be dropped as well (effectively unloading the library) and the problem shouldn't arise again.
Maybe there is another way to provide proper plugin isolation, because to me there seems to be something wrong if by dropping the plugin, the native library isn't properly dropped, but I have never worked with plugins on Android, so I'm guessing in the air.

Tbh, we have many other priorities right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants