{"record":{"id":"94d75d95a9d3618a","repo":"microg/GmsCore","slug":"error-backoff","errorCode":"ERROR_BACKOFF","errorMessage":"ERROR_BACKOFF","messagePattern":"ERROR_BACKOFF","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdRpc.java","lineNumber":275,"sourceCode":"\n    private synchronized PendingIntent getSelfAuthToken() {\n        if (selfAuthToken == null) {\n            Intent intent = new Intent();\n            intent.setPackage(\"com.google.example.invalidpackage\");\n            selfAuthToken = PendingIntent.getBroadcast(context, 0, intent, 0);\n        }\n        return selfAuthToken;\n    }\n\n    private static synchronized String getRequestId() {\n        return Integer.toString(lastRequestId++);\n    }\n\n    private void sendRegisterMessage(Bundle data, KeyPair keyPair, String requestId) throws IOException {\n        long elapsedRealtime = SystemClock.elapsedRealtime();\n        if (nextAttempt != 0 && elapsedRealtime <= nextAttempt) {\n            Log.w(TAG, \"Had to wait for \" + interval + \", that's still \" + (nextAttempt - elapsedRealtime));\n            throw new IOException(ERROR_BACKOFF);\n        }\n        initialize();\n        if (iidPackageName == null) {\n            throw new IOException(ERROR_MISSING_INSTANCEID_SERVICE);\n        }\n        Intent intent = new Intent(ACTION_C2DM_REGISTER);\n        intent.setPackage(iidPackageName);\n        data.putString(EXTRA_GMS_VERSION, Integer.toString(getGmsVersionCode(context)));\n        data.putString(EXTRA_OS_VERSION, Integer.toString(SDK_INT));\n        data.putString(EXTRA_APP_VERSION_CODE, Integer.toString(getSelfVersionCode(context)));\n        data.putString(EXTRA_APP_VERSION_NAME, getSelfVersionName(context));\n        data.putString(EXTRA_CLIENT_VERSION, \"iid-\" + GMS_VERSION_CODE);\n        data.putString(EXTRA_APP_ID, InstanceID.sha1KeyPair(keyPair));\n        String pub = base64encode(keyPair.getPublic().getEncoded());\n        data.putString(EXTRA_PUBLIC_KEY, pub);\n        data.putString(EXTRA_SIGNATURE, sign(keyPair, context.getPackageName(), pub));\n        intent.putExtras(data);\n        intent.putExtra(EXTRA_APP, getSelfAuthToken());","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdRpc.java#L257-L293","documentation":"InstanceIdRpc.sendRegisterMessage throws ERROR_BACKOFF when the device is still within an exponential-backoff window from a previous failed registration attempt. The RPC tracks nextAttempt (based on SystemClock.elapsedRealtime()) and refuses to send new register messages until that time passes, to avoid hammering the registration service.","triggerScenarios":"Calling getToken or sendRegisterMessageBlockingInternal while nextAttempt != 0 and elapsedRealtime <= nextAttempt, i.e. retrying token registration before the backoff interval computed after earlier failures has expired.","commonSituations":"Tight retry loops around getToken after a transient network failure or service-not-available response; an app restarting frequently and re-requesting tokens before the backoff window elapses; retry logic that ignores the recommended exponential backoff schedule.","solutions":["Wait until the backoff interval elapses before retrying — schedule the retry with a delay rather than calling immediately","Implement exponential backoff (start ~500ms, double on each failure, cap e.g. 60s) in the retry wrapper","Clear app data / reinstall only as a last resort to reset in-memory backoff state; usually just retrying later suffices"],"exampleFix":"// before\nwhile (true) { try { return getToken(); } catch (IOException e) { continue; } }\n// after\nlong backoffMs = 500;\nfor (int i = 0; i < MAX_RETRIES; i++) {\n    try { return getToken(); }\n    catch (IOException e) {\n        SystemClock.sleep(backoffMs);\n        backoffMs = Math.min(backoffMs * 2, 60000);\n    }\n}","handlingStrategy":"retry","validationCode":"// cannot query nextAttempt; wrap with time-since-last-failure tracking\nlong sinceLastFail = SystemClock.elapsedRealtime() - lastFailureAt;\nif (lastFailureAt != 0 && sinceLastFail < 5000) return; // still cooling down","typeGuard":null,"tryCatchPattern":"try {\n    String token = instanceId.getToken(senderId, \"GCM\");\n} catch (IOException e) {\n    if (\"ERROR_BACKOFF\".equals(e.getMessage())) {\n        handler.postDelayed(this::retry, currentBackoffMs());\n    }\n}","preventionTips":["Always retry with exponential backoff, never in a tight loop","Persist failure timestamps so restarts don't immediately re-hit the backoff","Only re-request tokens when actually invalidated or missing"],"tags":["android","backoff","retry","registration"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}