Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

UnsatisfiedLinkError after app is updated #38

Closed
philemonmerlet opened this issue Nov 7, 2019 · 1 comment · Fixed by #58
Closed

UnsatisfiedLinkError after app is updated #38

philemonmerlet opened this issue Nov 7, 2019 · 1 comment · Fixed by #58

Comments

@philemonmerlet
Copy link

philemonmerlet commented Nov 7, 2019

Hello, this is an issue similar to mapbox/mapbox-gl-native#15535 and all the other closed UnsatisfiedLinkError issues.

We are also experiencing this crash on a lot of devices (this is currently our top crash). It does not seem to be directly linked to the loader used, much more to the way libraries are loaded.

The problem is that library loading can take some time (we measured up to 500ms), and it is not directly performed by Mapbox INSTANCE creation, but a few lines later when NativeConnectivityListener is initialized.
In the meantime, I don't know why but it is possible that some other components initialize themselves from another process (several components have LibraryLoader.load in the static bloc). If we are unlucky, when NativeConnectivityListener reaches LibraryLoader.load section while the libraries loading is already in progress, it will believe no loading is needed (because loaded is true) and will try to initialize native component, hence the crash

java.lang.UnsatisfiedLinkError: No implementation found for void com.mapbox.mapboxsdk.net.NativeConnectivityListener.initialize() (tried Java_com_mapbox_mapboxsdk_net_NativeConnectivityListener_initialize and Java_com_mapbox_mapboxsdk_net_NativeConnectivityListener_initialize__)
        at com.mapbox.mapboxsdk.net.NativeConnectivityListener.initialize(Native Method)
        at com.mapbox.mapboxsdk.net.NativeConnectivityListener.<init>(NativeConnectivityListener.java:27)
        at com.mapbox.mapboxsdk.net.ConnectivityReceiver.instance(ConnectivityReceiver.java:43)
        at com.mappy.sample.HelloMapSample.onCreate(HelloMapSample.java:262)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2899)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3054)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1814)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:280)
        at android.app.ActivityThread.main(ActivityThread.java:6710)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

STEPS TO REPRODUCE THE CRASH

  • Set a slow library loader
class SlowLibraryLoader : LibraryLoader() {
    override fun load(libName: String?) {
        try {
            Thread.sleep(5000)
            SoLoader.init(Mapbox.getApplicationContext(), false)
            SoLoader.loadLibrary(libName)
        } catch (e : MapboxConfigurationException) {
            e.printStackTrace()
        }
    }
}
  • Setup an asynchronous call to Mapbox.getInstance, and just after call getInstance of a component which initializes native libraries, let's say ConnectivityReceiver :
Handler().postDelayed({ Mapbox.getInstance(this, getString(R.string.my_mapbox_token)) }, 1000)
ConnectivityReceiver.instance(this)
  • Launch and watch the crash

I don't know exactly why it happens in production app, but I guess there is something similar going on. Fortunately loaded libraries seem to persist a long time in memory, so the crashes happen mainly after app is updated (as app is reinstalled, everything needs to be reloaded).

On our side we found an unnecessary early call to Mapbox.getInstance() that we were able to remove, but I don't think it was the root of the problem, as it was crashing there (which means it may just crash later when we need Mapbox for real). We will have more info when this version will be released.

Anyway, as demonstrated above, the crash is not uneasy to get. Moving the loaded = true flag in LibraryLoader is not enough, because we can have cases when INSTANCE of a component is set but native libraries are not actually loaded (found this case while investigating).
In my opinion, synchronizing access to the library loading section would be a good solution ? I mean synchronizing access to this part :

      if (!loaded) {
        loaded = true;
        loader.load("mapbox-gl");
      }

But maybe there are other ways to solve the problem ?

@LukasPaczos LukasPaczos transferred this issue from mapbox/mapbox-gl-native Nov 7, 2019
@philemonmerlet
Copy link
Author

I was able to find where the problem occurred in our app. In a BroadcastReceiver (ACTION_PACKAGE_REPLACED), we were making a call to OfflineManager. As Mapbox was not initialized yet, the library loading failed (which was OK because we did not call a native functionality).
But LibraryLoaderProviderImpl has a catch block for this situation :

      } catch (MapboxConfigurationException exception) {
        Logger.e(TAG, "Couldn't load so file with relinker, application context missing, "
          + "call Mapbox.getInstance(Context context, String accessToken) first");
      }

so in LibraryLoader loader.load("mapbox-gl") does not dispatch the error, and the loaded flag stays at true. So when Mapbox is initialized, it does not try to load the native libs.

On our side we now check Mapbox.hasInstance() before calling OfflineManager, we hope it fixes the issue. But to prevent this in the future, it would be nice to throw a new UnsatisfiedLinkError in LibraryLoader implementation, so LibraryLoader can catch it and update the loaded flag. I don't have time to test this at the moment, but it would look like

      } catch (MapboxConfigurationException exception) {
        Logger.e(TAG, "Couldn't load so file with SoLoader, application context missing, "
          + "call Mapbox.getInstance(Context context, String accessToken) first");
        throw UnsatisfiedLinkError("application context missing");
      }

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant