{"record":{"id":"644bb5bd5b01fafb","repo":"microg/GmsCore","slug":"duration-limit-reached-expired-at-request-expir","errorCode":null,"errorMessage":"duration limit reached (expired at ${request.expirationTime}, now is ${SystemClock.elapsedRealtime()})","messagePattern":"duration limit reached \\(expired at (.+?), now is (.+?)\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt","lineNumber":336,"sourceCode":"        const val SAMPLING_PERIOD_US = 20_000\n        const val MAX_REPORT_LATENCY_US = 200_000\n\n        private class DeviceOrientationRequestHolder(\n            val clientIdentity: ClientIdentity,\n            private val request: DeviceOrientationRequest,\n            private val listener: IDeviceOrientationListener,\n        ) {\n            private var updates = 0\n            private var lastOrientation: DeviceOrientation? = null\n\n            val updatesPending: Int\n                get() = request.numUpdates - updates\n            val timePendingMillis: Long\n                get() = request.expirationTime - SystemClock.elapsedRealtime()\n            val workSource = WorkSource().also { WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) }\n\n            fun processNewDeviceOrientation(deviceOrientation: DeviceOrientation) {\n                if (timePendingMillis < 0) throw RuntimeException(\"duration limit reached (expired at ${request.expirationTime}, now is ${SystemClock.elapsedRealtime()})\")\n                if (lastOrientation != null && abs(lastOrientation!!.headingDegrees - deviceOrientation.headingDegrees) < Math.toDegrees(request.smallestAngleChangeRadians.toDouble())) return\n                if (lastOrientation == deviceOrientation) return\n                listener.onDeviceOrientationChanged(deviceOrientation)\n                if (request.numUpdates != Int.MAX_VALUE) updates++\n                if (updatesPending <= 0) throw RuntimeException(\"max updates reached\")\n            }\n        }\n    }\n}","sourceCodeStart":318,"sourceCodeEnd":345,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt#L318-L345","documentation":"DeviceOrientationManager's per-request callback processNewDeviceOrientation throws RuntimeException(\"duration limit reached ...\") when a new orientation event arrives after request.expirationTime has passed (timePendingMillis < 0). The duration-limited update session has expired and must not deliver further callbacks.","triggerScenarios":"A DeviceOrientationRequest with an expirationTime (durationMs) receives an orientation event after that elapsed-realtime deadline — the client kept listening without refreshing the request.","commonSituations":"Long-lived orientation listeners with a fixed duration request; system clock/elapsedRealtime anomalies after deep sleep; failing to re-register a new request after the previous one expires.","solutions":["Listen for the expiration and request a new DeviceOrientationRequest before it expires","Set expirationTime/numUpdates generously (or Int.MAX_VALUE updates, long duration) for continuous use","Catch the RuntimeException around the update callback and re-register the request","Verify elapsedRealtime vs wall-clock assumptions if events arrive at unexpected times"],"exampleFix":"// before\nlistener = manager.addDeviceOrientationListener(request, executor, listener)\n// keep listening forever...\n// after\nscope.launch {\n    delay(request.expirationTime - SystemClock.elapsedRealtime())\n    manager.removeDeviceOrientationListener(listener)\n    registerNewRequest() // fresh expiration\n}","handlingStrategy":"try-catch","validationCode":"if (SystemClock.elapsedRealtime() >= expirationTime) {\n    reRegisterRequest() // skip starting/stopping on an expired request\n}","typeGuard":"fun DeviceOrientationRequest.isExpired(): Boolean =\n    expirationTime - SystemClock.elapsedRealtime() <= 0","tryCatchPattern":"try {\n    handleOrientation(orientation)\n} catch (e: RuntimeException) {\n    if (e.message?.startsWith(\"duration limit reached\") == true) reRegisterRequest()\n    else throw e\n}","preventionTips":["Re-register the request before expirationTime elapses","Track expiration in the client and stop listening proactively","Use long durations for continuous monitoring needs","Account for deep sleep when computing elapsed budgets"],"tags":["android","location","device-orientation","timeout"],"backgroundTag":"request-timeout","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}