ionic-team / capacitor-plugins

Official plugins for Capacitor ⚡️
508 stars 577 forks source link

@capacitor/geolocation : RuntimeException - java.util.ConcurrentModificationException #1957

Open neogenz opened 9 months ago

neogenz commented 9 months ago

Bug Report

Plugin(s)

Capacitor Version

npx cap doctor
💊   Capacitor Doctor  💊

Latest Dependencies:

  @capacitor/cli: 5.5.1
  @capacitor/core: 5.5.1
  @capacitor/android: 5.5.1
  @capacitor/ios: 5.5.1

Installed Dependencies:

  @capacitor/ios: not installed
  @capacitor/cli: 5.5.1
  @capacitor/core: 5.5.1
  @capacitor/android: 5.5.1

[success] Android looking great! 👌

Platform(s)

Current Behavior

When the application, which uses the Capacitor Geolocation plugin, is put into the background for a few minutes and then brought back to the foreground, the app crashes with a java.util.ConcurrentModificationException. This exception occurs in the GeolocationPlugin.handleOnResume method, as detailed in the stack trace provided.

java.util.ConcurrentModificationException: null
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1441)
    at java.util.HashMap$ValueIterator.next(HashMap.java:1470)
    at com.capacitorjs.plugins.geolocation.GeolocationPlugin.handleOnResume(GeolocationPlugin.java:49)
    at com.getcapacitor.Bridge.onResume(Bridge.java:1254)
    at com.getcapacitor.BridgeActivity.onResume(BridgeActivity.java:86)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1364)
    at android.app.Activity.performResume(Activity.java:7490)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4256)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4328)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2047)
    at android.os.Handler.dispatchMessage(Handler.java:108)
    at android.os.Looper.loop(Looper.java:166)
    at android.app.ActivityThread.main(ActivityThread.java:7529)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
java.lang.RuntimeException: Unable to resume activity {ch.tpg.boldor/ch.tpg.boldor.MainActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4288)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4328)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2047)
    at android.os.Handler.dispatchMessage(Handler.java:108)
    at android.os.Looper.loop(Looper.java:166)
    at android.app.ActivityThread.main(ActivityThread.java:7529)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

Exemple of scenario, it is always more or less same

image

We have lot of crash due to this error in production on our app.

Expected Behavior

The expected behavior is that the app should resume normally from the background without any crash or exception related to the Geolocation plugin.

Additional Context

This issue seems to occur due to modifications in the HashMap within the GeolocationPlugin while iterating over it. This problem surfaces specifically when handling the onResume lifecycle event in Android.

To reproduce

https://github.com/neogenz/capacitor-geolocation-sample

The scenario is simple : on Android app, pass app in background, wait (only some ms is enough) then put it back in foreground -> crash

The issue is really complicated to reproduce. To "force" it, modify java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java and update this code :

@Override
    protected void handleOnResume() {
      super.handleOnResume();

      // This loop is to force crash when switch background/foreground more times
      for (int i = 0; i < 5; i++) {
        new Thread(() -> {
          try {
            Thread.sleep(10); // Réduire le délai
            if (!watchingCalls.isEmpty()) {
              watchingCalls.remove(watchingCalls.keySet().iterator().next());
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }).start();
      }

      for (PluginCall call : watchingCalls.values()) {
          startWatch(call);
      }
    }

And I reproduce it more easily on physical device (on virtual too, but it is less frequent)

We have tried a fix with our team but we don't know if it is correct without knowing all context of Geolocation / Capacitor code :

@Override
    protected void handleOnResume() {
        super.handleOnResume();
        List<PluginCall> watchingCallsValues = new ArrayList<PluginCall>(watchingCalls.values());
        for (PluginCall call : watchingCallsValues) {
            startWatch(call);
        }
    }
neogenz commented 7 months ago

Any news ?

neogenz commented 6 months ago

Any news ?

neogenz commented 2 months ago

any news ?

neogenz commented 1 month ago

We are experiencing errors on iOS in production for many clients. I don't know if it is due to the plugin itself or something related to our usage of it. image