microg/GmsCore · error · RuntimeException

duration limit reached (expired at ${request.expirationTime}

Error message

duration limit reached (expired at ${request.expirationTime}, now is ${SystemClock.elapsedRealtime()})

What it means

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.

Source

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

        const val SAMPLING_PERIOD_US = 20_000
        const val MAX_REPORT_LATENCY_US = 200_000

        private class DeviceOrientationRequestHolder(
            val clientIdentity: ClientIdentity,
            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

  1. Listen for the expiration and request a new DeviceOrientationRequest before it expires
  2. Set expirationTime/numUpdates generously (or Int.MAX_VALUE updates, long duration) for continuous use
  3. Catch the RuntimeException around the update callback and re-register the request
  4. Verify elapsedRealtime vs wall-clock assumptions if events arrive at unexpected times

Example fix

// before
listener = manager.addDeviceOrientationListener(request, executor, listener)
// keep listening forever...
// after
scope.launch {
    delay(request.expirationTime - SystemClock.elapsedRealtime())
    manager.removeDeviceOrientationListener(listener)
    registerNewRequest() // fresh expiration
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (SystemClock.elapsedRealtime() >= expirationTime) {
    reRegisterRequest() // skip starting/stopping on an expired request
}

Type guard

fun DeviceOrientationRequest.isExpired(): Boolean =
    expirationTime - SystemClock.elapsedRealtime() <= 0

Try / catch

try {
    handleOrientation(orientation)
} catch (e: RuntimeException) {
    if (e.message?.startsWith("duration limit reached") == true) reRegisterRequest()
    else throw e
}

Prevention

When it happens

Trigger: A DeviceOrientationRequest with an expirationTime (durationMs) receives an orientation event after that elapsed-realtime deadline — the client kept listening without refreshing the request.

Common situations: 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.

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/644bb5bd5b01fafb. Report an issue: GitHub.