microg/GmsCore · error · RuntimeException
max updates reached
Error message
max updates reached
What it means
DeviceOrientationManager's processNewDeviceOrientation throws RuntimeException("max updates reached") once the number of delivered updates has consumed request.numUpdates (updatesPending <= 0). This enforces the one-shot/limited-update semantics of the orientation request.
Source
Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt:341
private val request: DeviceOrientationRequest,
private val listener: IDeviceOrientationListener,
) {
private var updates = 0
private var lastOrientation: DeviceOrientation? = null
val updatesPending: Int
get() = request.numUpdates - updates
val timePendingMillis: Long
get() = request.expirationTime - SystemClock.elapsedRealtime()
val workSource = WorkSource().also { WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) }
fun processNewDeviceOrientation(deviceOrientation: DeviceOrientation) {
if (timePendingMillis < 0) throw RuntimeException("duration limit reached (expired at ${request.expirationTime}, now is ${SystemClock.elapsedRealtime()})")
if (lastOrientation != null && abs(lastOrientation!!.headingDegrees - deviceOrientation.headingDegrees) < Math.toDegrees(request.smallestAngleChangeRadians.toDouble())) return
if (lastOrientation == deviceOrientation) return
listener.onDeviceOrientationChanged(deviceOrientation)
if (request.numUpdates != Int.MAX_VALUE) updates++
if (updatesPending <= 0) throw RuntimeException("max updates reached")
}
}
}
}View on GitHub (pinned to 157c9d86ac)
Solutions
- Unregister the listener after receiving numUpdates callbacks
- Set numUpdates = Int.MAX_VALUE if continuous updates are desired
- Catch the RuntimeException and clean up/re-register as needed
- Ensure only one request per client increments the update counter (avoid duplicate registrations)
Example fix
// before
manager.addDeviceOrientationListener(request, executor, callback) // numUpdates = 1, never removed
// after
val callback = object : DeviceOrientationCallback {
override fun onDeviceOrientationChanged(o: DeviceOrientation) {
handle(o)
if (++received >= request.numUpdates) manager.removeDeviceOrientationListener(this)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (receivedUpdates >= request.numUpdates) {
manager.removeDeviceOrientationListener(listener)
} Type guard
fun DeviceOrientationRequest.isUpdateQuotaExhausted(received: Int): Boolean =
numUpdates != Int.MAX_VALUE && received >= numUpdates Try / catch
try {
handleOrientation(orientation)
} catch (e: RuntimeException) {
if (e.message == "max updates reached") unregisterAndCleanup()
else throw e
} Prevention
- Unregister the listener after numUpdates callbacks arrive
- Use numUpdates = Int.MAX_VALUE for continuous tracking
- Avoid duplicate registrations incrementing the same counter
- Treat the exception as an end-of-session signal, not a failure
When it happens
Trigger: A DeviceOrientationRequest specifying a finite numUpdates receives orientation changes beyond that count — the client keeps receiving events after the update quota is exhausted.
Common situations: Requests created with small numUpdates (e.g. 1) while the sensor keeps reporting; callers not unregistering after receiving all updates; duplicated sensor registrations each incrementing the shared counter.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- duration limit reached (expired at ${request.expirationTime}
- invalid radius:
- invalid latitude:
- invalid longitude:
- maxUpdateAgeMillis must be greater than 0
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/4cbf502fcb49a94b.
Report an issue: GitHub.