Skip to content

RiveShutdownException on RiveFile dispose after EGL_CONTEXT_LOST (rememberRiveFile) #453

@ericb-gingerlabs

Description

@ericb-gingerlabs

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

  1. Use rememberRiveFile in a composable (e.g. feed it to Rive(...)).
  2. 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.).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions