java.lang.RuntimeException: WakeLock under-locked C2DM_LIB
Asked Answered
M

4

67

I have uploaded my application on google play but users have reported the following exception

java.lang.RuntimeException: WakeLock under-locked C2DM_LIB. This exception occurs when I try to release the WakeLock. Can anyone tell what could be the problem.

Mercurochrome answered 27/8, 2012 at 11:8 Comment(0)
S
65

I have traced same exception in new GCM Library too. Actually old C2DM Android library have same error, same crash, and Google hasn't fixed it yet. As I can see by our statistics, about 0.1% of users experiencing this crash.

My investigations shows that problem is in incorrect releasing of network WakeLock in GCM library, when library tries to release WakeLock that holds nothing (internal lock counter becomes negative).

I was satisfied with simple solution - just catch this exception and do nothing, because we don't need to do any extra job then our wakelock hold nothing.

In order to do this you need to import GCM library sources in your project, rather than already compiled .jar file. You can find GCM library sources under "$Android_SDK_Home$/extras/google/gcm/gcm-client/src" folder (you need to download it first using Android SDK Manager).

Next open GCMBaseIntentService class, find line

sWakeLock.release();

and surround it with try-catch.

It should look like this:

    synchronized (LOCK) {
        // sanity check for null as this is a public method
        if (sWakeLock != null) {
            Log.v(TAG, "Releasing wakelock");
            try {
                sWakeLock.release();
            } catch (Throwable th) {
                // ignoring this exception, probably wakeLock was already released
            }
        } else {
            // should never happen during normal workflow
            Log.e(TAG, "Wakelock reference is null");
        }
    }

UPDATE: Alternativally, as suggested @fasti in his answer, you can use mWakeLock.isHeld() method to check if wakelock actually holding this lock.

Spinoza answered 27/8, 2012 at 12:30 Comment(7)
Yes, I've implemented this solution in all our projects, it works perfectly (userbase more than 2M users)Spinoza
I went ahead and made this change in a fork of Google's repo and put it on Github: github.com/ajlyon/gcmRuffian
cool, can i just replace the GCM Library jar with this one ( github.com/ajlyon/gcm/blob/master/gcm-client/dist/gcm-src.jar ) and go on with that bug fixed? is it updated with the last release of GCM lib?Lizethlizette
This is an old thread, so I am not sure if there any continual interest, but what i dont understand is that how can this happen. AFAI can see, this only possible if onHandle intent is called from outside of runIntentInService. Which should not happen ever correct?Areca
This solution is heavy handed and sweeps the problem under a rug. The one proposed by fasti below is the correct way of dealing with it.Constitutive
A yes, Catch Throwable, the sign of quality javaNaphthalene
Catching and ignoring an exception is almost never a good practice. Especially so when, as in this case, there is a simple boolean check to handle the error case (see fasti's answer).Millham
T
201

You didn't post your code, so I don't know if you've already done what I will suggest here, but I also had that exception and all I added to fix it was a simple "if" to make sure the WakeLock is actually being held, before trying to release it.

All I added in my onPause was this "if" statement (before the "release()"):

if (mWakeLock.isHeld())
    mWakeLock.release();

and the exception was gone.

Thimbleweed answered 19/2, 2013 at 3:32 Comment(8)
This solution seems a lot cleaner to me than the accepted one.Mallard
That's because it is -and- the right way to do it. This should have been the accepted answer.Laniary
I have no .release() in my code (no mWakeLock what so ever) but I am still getting this error. The only stacktrace I see is: java.lang.RuntimeException: WakeLock under-locked GCM_LIB at [...]com.google.android.gcm.GCMBaseIntentService.onHandleIntent(GCMBaseIntentService.java:252) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)Reenareenforce
I agree that this should be the accepted answer but don't forget to put above code in a synchronized statement. I had rare cases where the wake lock was released by another thread between the calls to isHeld and release.Colossal
do you have an example of the syncronized code? I think the safest is to use all 3 methods.. Catch Throwable, and isHeld, and Synchronized.. haha if an exception is raised at runtime, it is expensive.. save battery power by checking the isHeld first, it is more efficient, by a few microseconds, haha.Conduplicate
This solution failed in my case, I keep getting traces: crashes.to/s/9b51ed151b2Hooke
This may look cleaner, but your app may still crash if the system releases your wakelock due to the time expiring in between the isHeld() and release() callsImpacted
doesn't work for me on the rare phone @Synchronized open fun stop() { if (wakeLock != null && wakeLock!!.isHeld()) { wakeLock!!.release() wakeLock = null } }Kiernan
S
65

I have traced same exception in new GCM Library too. Actually old C2DM Android library have same error, same crash, and Google hasn't fixed it yet. As I can see by our statistics, about 0.1% of users experiencing this crash.

My investigations shows that problem is in incorrect releasing of network WakeLock in GCM library, when library tries to release WakeLock that holds nothing (internal lock counter becomes negative).

I was satisfied with simple solution - just catch this exception and do nothing, because we don't need to do any extra job then our wakelock hold nothing.

In order to do this you need to import GCM library sources in your project, rather than already compiled .jar file. You can find GCM library sources under "$Android_SDK_Home$/extras/google/gcm/gcm-client/src" folder (you need to download it first using Android SDK Manager).

Next open GCMBaseIntentService class, find line

sWakeLock.release();

and surround it with try-catch.

It should look like this:

    synchronized (LOCK) {
        // sanity check for null as this is a public method
        if (sWakeLock != null) {
            Log.v(TAG, "Releasing wakelock");
            try {
                sWakeLock.release();
            } catch (Throwable th) {
                // ignoring this exception, probably wakeLock was already released
            }
        } else {
            // should never happen during normal workflow
            Log.e(TAG, "Wakelock reference is null");
        }
    }

UPDATE: Alternativally, as suggested @fasti in his answer, you can use mWakeLock.isHeld() method to check if wakelock actually holding this lock.

Spinoza answered 27/8, 2012 at 12:30 Comment(7)
Yes, I've implemented this solution in all our projects, it works perfectly (userbase more than 2M users)Spinoza
I went ahead and made this change in a fork of Google's repo and put it on Github: github.com/ajlyon/gcmRuffian
cool, can i just replace the GCM Library jar with this one ( github.com/ajlyon/gcm/blob/master/gcm-client/dist/gcm-src.jar ) and go on with that bug fixed? is it updated with the last release of GCM lib?Lizethlizette
This is an old thread, so I am not sure if there any continual interest, but what i dont understand is that how can this happen. AFAI can see, this only possible if onHandle intent is called from outside of runIntentInService. Which should not happen ever correct?Areca
This solution is heavy handed and sweeps the problem under a rug. The one proposed by fasti below is the correct way of dealing with it.Constitutive
A yes, Catch Throwable, the sign of quality javaNaphthalene
Catching and ignoring an exception is almost never a good practice. Especially so when, as in this case, there is a simple boolean check to handle the error case (see fasti's answer).Millham
C
5

Although the isHeld() solution seems nicer, it can actually fail - because it is not atomic (i.e. not thread safe). If you have more than one thread that might release the lock then between the check (isHeld) and the call to relase another thread may release the lock... and then you fail.

By using try/catch you disguise the bug, but in a thread-safe way.

Closestool answered 5/6, 2014 at 10:39 Comment(3)
Is there a good option to make a WakeLock release atomic in a reusable way? It should be an atomic operation. It literally has "Lock" in the name.Charla
Are you sure? Looking at the PowerManager.java source code, it looks like these functions are synchronized.Ethelynethene
I added Synchronized for release-claim wakelock, but this not help, there must be another problemPremiere
C
1

I don't have this problem as long as I don't reinitialize the wake lock and call acquire on the new Object. You should only keep one instance of wakeLock (so make it a field variable). Then you know you're always releasing that one wakeLock.

So....

 if (mWakeLock == null) {
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
                | PowerManager.ON_AFTER_RELEASE, "MyWakeLock");
    }

try{
        mWakeLock.release();//always release before acquiring for safety just in case
    }
    catch(Exception e){
        //probably already released
        Log.e(TAG, e.getMessage());
    }
    mWakeLock.acquire();
Changeable answered 12/7, 2014 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.