microg/GmsCore · warning · RuntimeException
duration limit reached (active for ${(SystemClock.elapsedRea
Error message
duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()}) What it means
LocationRequestHolder.check() throws RuntimeException('duration limit reached (active for X, duration Y)') when the request's expirationDuration has elapsed: timePendingMillis went negative once the holder's active time exceeded request.durationMillis. It enforces the client-requested duration limit (setExpirationDuration) before delivering further locations; the exception is caught upstream to cancel the holder.
Source
Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt:457
}
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
- Increase the expiration via LocationRequest.Builder#setExpirationDuration with a value covering the session, or omit it for continuous updates.
- Re-register the location request when the expiration elapses (handle the Status failure in the callback and restart updates).
- If long-term tracking is needed, implement your own periodic re-request loop instead of one long-duration request.
Example fix
// before
val request = LocationRequest.Builder(priority, 1000)
.setExpirationDuration(60_000) // expires after 1 min
.build()
// after
val request = LocationRequest.Builder(priority, 1000)
.setExpirationDuration(TimeUnit.HOURS.toMillis(2)) // or don't set expiration
.build() Defensive patterns
Strategy: retry
Validate before calling
val duration = request.durationMillis
if (duration in 1..(elapsedSinceStart)) {
// request already expired — rebuild it before registering
request = rebuildWithFreshExpiration()
} Type guard
fun isStillValid(holder: LocationRequestHolder): Boolean = holder.timePendingMillis > 0
Try / catch
// expiration failures surface as a failed status/callback in the client API
callback = object : LocationCallback() {
override fun onLocationAvailability(a: LocationAvailability) {
if (a != null && !a.isLocationAvailable) { reRegisterRequest() }
}
} Prevention
- Set expiration durations longer than the expected session, or none at all
- Re-register the request on expiration failure rather than assuming permanent failure
- Never reuse LocationRequest objects that carry an already-elapsed expiration
When it happens
Trigger: A location request created with setExpirationDuration(millis) has been active longer than that duration and another location arrives, triggering processNewLocation -> check(); also triggered when a request is registered without clearing a previously set expiration that has already passed.
Common situations: Long-running navigation sessions exceeding the expiration set on the LocationRequest; reusing a stale LocationRequest object whose duration was set at build time; clock/elapsedRealtime edge cases after deep sleep causing immediate expiry.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
Related errors
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/5da7ebbd42d50cd6.
Report an issue: GitHub.