{"record":{"id":"5da7ebbd42d50cd6","repo":"microg/GmsCore","slug":"duration-limit-reached-active-for-systemclock","errorCode":null,"errorMessage":"duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})","messagePattern":"duration limit reached \\(active for (.+?), duration (.+?)\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt","lineNumber":457,"sourceCode":"            }\n\n            fun start(): LocationRequestHolder {\n                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException(\"Lack of permission\")\n                return this\n            }\n\n            fun cancel() {\n                try {\n                    callback?.cancel()\n                } catch (e: Exception) {\n                    Log.w(TAG, e)\n                }\n            }\n\n            fun check() {\n                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException(\"Lack of permission\")\n                if (effectiveGranularity > permissionGranularity) throw RuntimeException(\"Lack of permission\")\n                if (timePendingMillis < 0) throw RuntimeException(\"duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})\")\n                if (updatesPending <= 0) throw RuntimeException(\"max updates reached\")\n                if (callback?.asBinder()?.isBinderAlive == false) throw RuntimeException(\"Binder died\")\n            }\n\n            fun processNewLocation(location: Location): Boolean {\n                check()\n                if (lastLocation != null && location.elapsedMillis - lastLocation!!.elapsedMillis < request.minUpdateIntervalMillis) return false\n                if (lastLocation != null && location.distanceTo(lastLocation!!) < request.minUpdateDistanceMeters) return false\n                if (lastLocation == location) return false\n                val returnedLocation = if (effectiveGranularity > permissionGranularity) {\n                    throw RuntimeException(\"Lack of permission\")\n                } else {\n                    if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {\n                        throw RuntimeException(\"app op denied\")\n                    } else if (clientIdentity.isSelfProcess()) {\n                        Location(location)\n                    } else {\n                        Location(location).apply { provider = \"fused\" }","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt#L439-L475","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nval request = LocationRequest.Builder(priority, 1000)\n    .setExpirationDuration(60_000) // expires after 1 min\n    .build()\n// after\nval request = LocationRequest.Builder(priority, 1000)\n    .setExpirationDuration(TimeUnit.HOURS.toMillis(2)) // or don't set expiration\n    .build()","handlingStrategy":"retry","validationCode":"val duration = request.durationMillis\nif (duration in 1..(elapsedSinceStart)) {\n    // request already expired — rebuild it before registering\n    request = rebuildWithFreshExpiration()\n}","typeGuard":"fun isStillValid(holder: LocationRequestHolder): Boolean = holder.timePendingMillis > 0","tryCatchPattern":"// expiration failures surface as a failed status/callback in the client API\ncallback = object : LocationCallback() {\n    override fun onLocationAvailability(a: LocationAvailability) {\n        if (a != null && !a.isLocationAvailable) { reRegisterRequest() }\n    }\n}","preventionTips":["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"],"tags":["location","expiration","duration-limit","request-lifecycle"],"backgroundTag":"request-timeout","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"}