microg/GmsCore · error · RuntimeException

max updates reached

Error message

max updates reached

What it means

microG's LocationRequestManager tracks how many location updates a client request is still entitled to (updatesPending, derived from the request's update budget). Each incoming location is validated by check() before delivery. When the request's update count is exhausted (updatesPending <= 0), delivering further updates would violate the client's LocationRequest contract, so a RuntimeException 'max updates reached' is thrown.

Source

Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt:458

            fun start(): LocationRequestHolder {
                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission")
                return this
            }

            fun cancel() {
                try {
                    callback?.cancel()
                } catch (e: Exception) {
                    Log.w(TAG, e)
                }
            }

            fun check() {
                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission")
                if (effectiveGranularity > permissionGranularity) throw RuntimeException("Lack of permission")
                if (timePendingMillis < 0) throw RuntimeException("duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})")
                if (updatesPending <= 0) throw RuntimeException("max updates reached")
                if (callback?.asBinder()?.isBinderAlive == false) throw RuntimeException("Binder died")
            }

            fun processNewLocation(location: Location): Boolean {
                check()
                if (lastLocation != null && location.elapsedMillis - lastLocation!!.elapsedMillis < request.minUpdateIntervalMillis) return false
                if (lastLocation != null && location.distanceTo(lastLocation!!) < request.minUpdateDistanceMeters) return false
                if (lastLocation == location) return false
                val returnedLocation = if (effectiveGranularity > permissionGranularity) {
                    throw RuntimeException("Lack of permission")
                } else {
                    if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {
                        throw RuntimeException("app op denied")
                    } else if (clientIdentity.isSelfProcess()) {
                        Location(location)
                    } else {
                        Location(location).apply { provider = "fused" }
                    }

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Verify the LocationRequest's numUpdates is set high enough (or left unlimited) for the app's use case
  2. Call setNumUpdates(Integer.MAX_VALUE) if continuous updates are intended instead of a fixed count
  3. Re-register a fresh LocationRequest via FusedLocationProviderClient.requestLocationUpdates once the old one's budget is spent
  4. Check for duplicate request registrations that drain the shared update budget faster than expected
  5. Remove the listener with removeLocationUpdates when done so exhausted requests are not re-invoked

Example fix

// before
LocationRequest.Builder(builder)
    .setNumUpdates(1)
    .build() // throws on the 2nd incoming location
// after
LocationRequest.Builder(builder)
    .setNumUpdates(10) // or Integer.MAX_VALUE for continuous
    .build()
Defensive patterns

Strategy: try-catch

Validate before calling

// compute remaining updates before registering
val updates = request.numUpdates
if (updates <= 0) throw IllegalArgumentException("LocationRequest already exhausted; create a new request")

Type guard

fun LocationRequest.hasUpdateBudget(): Boolean = numUpdates > 0 || durationPending

Try / catch

try {
    client.requestLocationUpdates(request, listener, looper)
} catch (e: RuntimeException) {
    if (e.message == "max updates reached") {
        client.requestLocationUpdates(request.toUnboundedBuilder().build(), listener, looper)
    } else throw e
}

Prevention

When it happens

Trigger: A client registered a LocationRequest with a bounded number of updates (e.g. setNumUpdates(n) or a duration-limited request) and all n updates have already been delivered; a new location arrives and processNewLocation() calls check() which sees updatesPending <= 0.

Common situations: Apps that forgot the update count is cumulative across the request lifetime; reusing a stale LocationRequest after its budget ran out; requesting numUpdates=1 expecting continuous updates; duration-limited foreground-only requests kept alive after expiry.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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