rive-app / rive-app/rive-android
RiveShutdownException on RiveFile dispose after EGL_CONTEXT_LOST (rememberRiveFile)
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 538
- Forks
- 66
- PR merge metrics
- No merged PRs in 30d
Description
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
rememberRiveFilein a composable (e.g. feed it toRive(...)). - 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.).
awaitDisposeinrememberRiveFilecallsRiveFile.close(), which chains intoRenderContextGL.dispose()and throwsRiveShutdownException, 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in RenderContext.kt at RenderContextGL.dispose(), then inspect RiveEGLSurface.dispose and RiveEGLPBufferSurface.dispose for the related EGL teardown paths. Verify how eglGetError() is handled after failed destroy or terminate calls; done means EGL_CONTEXT_LOST no longer crashes disposal while other teardown errors remain fatal.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- computer-graphics, mobile-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100