microg/GmsCore · error · IOException

ERROR_BACKOFF

ERROR_BACKOFF

Error message

ERROR_BACKOFF

What it means

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.

Source

Thrown at play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdRpc.java:275

    private synchronized PendingIntent getSelfAuthToken() {
        if (selfAuthToken == null) {
            Intent intent = new Intent();
            intent.setPackage("com.google.example.invalidpackage");
            selfAuthToken = PendingIntent.getBroadcast(context, 0, intent, 0);
        }
        return selfAuthToken;
    }

    private static synchronized String getRequestId() {
        return Integer.toString(lastRequestId++);
    }

    private void sendRegisterMessage(Bundle data, KeyPair keyPair, String requestId) throws IOException {
        long elapsedRealtime = SystemClock.elapsedRealtime();
        if (nextAttempt != 0 && elapsedRealtime <= nextAttempt) {
            Log.w(TAG, "Had to wait for " + interval + ", that's still " + (nextAttempt - elapsedRealtime));
            throw new IOException(ERROR_BACKOFF);
        }
        initialize();
        if (iidPackageName == null) {
            throw new IOException(ERROR_MISSING_INSTANCEID_SERVICE);
        }
        Intent intent = new Intent(ACTION_C2DM_REGISTER);
        intent.setPackage(iidPackageName);
        data.putString(EXTRA_GMS_VERSION, Integer.toString(getGmsVersionCode(context)));
        data.putString(EXTRA_OS_VERSION, Integer.toString(SDK_INT));
        data.putString(EXTRA_APP_VERSION_CODE, Integer.toString(getSelfVersionCode(context)));
        data.putString(EXTRA_APP_VERSION_NAME, getSelfVersionName(context));
        data.putString(EXTRA_CLIENT_VERSION, "iid-" + GMS_VERSION_CODE);
        data.putString(EXTRA_APP_ID, InstanceID.sha1KeyPair(keyPair));
        String pub = base64encode(keyPair.getPublic().getEncoded());
        data.putString(EXTRA_PUBLIC_KEY, pub);
        data.putString(EXTRA_SIGNATURE, sign(keyPair, context.getPackageName(), pub));
        intent.putExtras(data);
        intent.putExtra(EXTRA_APP, getSelfAuthToken());

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Wait until the backoff interval elapses before retrying — schedule the retry with a delay rather than calling immediately
  2. Implement exponential backoff (start ~500ms, double on each failure, cap e.g. 60s) in the retry wrapper
  3. Clear app data / reinstall only as a last resort to reset in-memory backoff state; usually just retrying later suffices

Example fix

// before
while (true) { try { return getToken(); } catch (IOException e) { continue; } }
// after
long backoffMs = 500;
for (int i = 0; i < MAX_RETRIES; i++) {
    try { return getToken(); }
    catch (IOException e) {
        SystemClock.sleep(backoffMs);
        backoffMs = Math.min(backoffMs * 2, 60000);
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// cannot query nextAttempt; wrap with time-since-last-failure tracking
long sinceLastFail = SystemClock.elapsedRealtime() - lastFailureAt;
if (lastFailureAt != 0 && sinceLastFail < 5000) return; // still cooling down

Try / catch

try {
    String token = instanceId.getToken(senderId, "GCM");
} catch (IOException e) {
    if ("ERROR_BACKOFF".equals(e.getMessage())) {
        handler.postDelayed(this::retry, currentBackoffMs());
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/94d75d95a9d3618a. Report an issue: GitHub.