Version
11.4.1 (also reproduces in 11.3.2)
Summary
RenderContextGL.dispose() treats eglDestroyContext returning false as fatal and throws RiveShutdownException. When the underlying EGL error is EGL_CONTEXT_LOST — a documented EGL state that occurs after a power-management / GPU-reset event — the context has already been reclaimed by the system, so there's nothing to destroy. Throwing here turns a normal system event into a user-visible fatal crash whenever a rememberRiveFile-owning composable leaves composition after an EGL context-loss event.
We've seen this in production on Android 15.
Stack trace
Fatal Exception: app.rive.RiveShutdownException: Unable to destroy EGL context
at app.rive.core.RenderContextGL.dispose(RenderContext.kt:260)
at app.rive.core.RenderContextGL$cppPointer$1.invoke(RenderContext.kt:245)
at app.rive.core.UniquePointer$1.invoke(UniquePointer.kt:22)
at app.rive.core.CloseOnce.close(CloseOnce.kt:34)
at app.rive.core.UniquePointer.close(UniquePointer.kt:2)
at app.rive.core.RenderContext.close(RenderContext.kt:40)
at app.rive.core.CommandQueue.dispose(CommandQueue.kt:134)
at app.rive.core.RCPointer.release(RCPointer.kt:115)
at app.rive.core.CommandQueue.release(CommandQueue.kt:145)
at app.rive.RiveFile$1.invoke(RiveFile.kt:45)
at app.rive.core.CloseOnce.close(CloseOnce.kt:34)
at app.rive.RiveFile.close(RiveFile.kt:2)
at app.rive.RiveFileKt$rememberRiveFile$1$1.invoke(RiveFile.kt:199)
at androidx.compose.runtime.ProduceStateScopeImpl.awaitDispose(ProduceState.kt:51)
...
Caused by java.lang.Throwable: EGL_CONTEXT_LOST
...same frames...
Reproduction
- Use
rememberRiveFile in a composable (e.g. feed it to Rive(...)).
- After the device has experienced an EGL context-loss event — reliably triggered on many devices by sleep/wake, aggressive power-save transitions, or GPU driver resets — cause the composable to leave composition (nav away, conditionally swap it out, etc.).
awaitDispose in rememberRiveFile calls RiveFile.close(), which chains into RenderContextGL.dispose() and throws RiveShutdownException, crashing the process.
Offending code (RenderContext.kt lines 254–272 in 11.4.1)
private fun dispose(address: Long) {
val destroyed = EGL14.eglDestroyContext(display, context)
if (!destroyed) {
val error = EGLError.errorString(EGL14.eglGetError())
throw RiveShutdownException(\"Unable to destroy EGL context\", Throwable(error))
}
val terminated = EGL14.eglTerminate(display)
if (!terminated) {
val error = EGLError.errorString(EGL14.eglGetError())
throw RiveShutdownException(\"Unable to terminate EGL display\", Throwable(error))
}
cppDelete(address)
}
The eglTerminate branch has the same issue. RiveEGLSurface.dispose and RiveEGLPBufferSurface.dispose throw on eglDestroySurface failure and are likely affected by the same root cause along related teardown paths.
Suggested fix
Check eglGetError() and treat EGL_CONTEXT_LOST as a non-fatal, already-cleaned-up state — log and continue. Something like:
private fun dispose(address: Long) {
val destroyed = EGL14.eglDestroyContext(display, context)
if (!destroyed) {
val errCode = EGL14.eglGetError()
if (errCode == EGL14.EGL_CONTEXT_LOST) {
RiveLog.w(TAG) { \"EGL context already lost; skipping destroy\" }
} else {
throw RiveShutdownException(
\"Unable to destroy EGL context\",
Throwable(EGLError.errorString(errCode))
)
}
}
// analogous handling for eglTerminate
cppDelete(address)
}
Rationale
Per the EGL spec, EGL_CONTEXT_LOST indicates a power-management event caused the loss of the associated client context, and the app is expected to release its resources and reinitialise. Destroying an already-lost context is a no-op from the driver's perspective. Making this fatal turns a recoverable system event into a crash during transient composable teardown (which happens routinely on navigation, backgrounding, and power-save toggles).
Reference
eglDestroyContext — Khronos EGL reference
Version
11.4.1 (also reproduces in 11.3.2)
Summary
RenderContextGL.dispose()treatseglDestroyContextreturningfalseas fatal and throwsRiveShutdownException. When the underlying EGL error isEGL_CONTEXT_LOST— a documented EGL state that occurs after a power-management / GPU-reset event — the context has already been reclaimed by the system, so there's nothing to destroy. Throwing here turns a normal system event into a user-visible fatal crash whenever arememberRiveFile-owning composable leaves composition after an EGL context-loss event.We've seen this in production on Android 15.
Stack trace
Reproduction
rememberRiveFilein a composable (e.g. feed it toRive(...)).awaitDisposeinrememberRiveFilecallsRiveFile.close(), which chains intoRenderContextGL.dispose()and throwsRiveShutdownException, crashing the process.Offending code (
RenderContext.ktlines 254–272 in 11.4.1)The
eglTerminatebranch has the same issue.RiveEGLSurface.disposeandRiveEGLPBufferSurface.disposethrow oneglDestroySurfacefailure and are likely affected by the same root cause along related teardown paths.Suggested fix
Check
eglGetError()and treatEGL_CONTEXT_LOSTas a non-fatal, already-cleaned-up state — log and continue. Something like:Rationale
Per the EGL spec,
EGL_CONTEXT_LOSTindicates a power-management event caused the loss of the associated client context, and the app is expected to release its resources and reinitialise. Destroying an already-lost context is a no-op from the driver's perspective. Making this fatal turns a recoverable system event into a crash during transient composable teardown (which happens routinely on navigation, backgrounding, and power-save toggles).Reference
eglDestroyContext — Khronos EGL reference