Skip to content

Fix #45: prevent and contain multi-process WebView data-dir crash#60

Open
jim-daf wants to merge 2 commits into
flamyoad:masterfrom
jim-daf:fix/webview-multi-process-data-dir
Open

Fix #45: prevent and contain multi-process WebView data-dir crash#60
jim-daf wants to merge 2 commits into
flamyoad:masterfrom
jim-daf:fix/webview-multi-process-data-dir

Conversation

@jim-daf
Copy link
Copy Markdown

@jim-daf jim-daf commented May 2, 2026

Closes #45.

What

Two-layer mitigation for:

Fatal Exception: java.lang.RuntimeException: Using WebView from more than one
process at once with the same data directory is not supported.
https://crbug.com/558377
   Current process com.flamyoad.honnoki (pid 11738),
   lock owner   com.flamyoad.honnoki (pid 32594)

Both layers live in MyApplication.onCreate() so they are wired up before any code that could lazily touch a WebView.

Layer 1 — Per-process data-dir suffix (preventive)

private fun applyWebViewDataDirectorySuffix() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
    val processName = getProcessName() ?: return
    if (packageName != processName) {
        try {
            WebView.setDataDirectorySuffix(processName)
        } catch (t: Throwable) {
            Timber.w(t, "Failed to set WebView data directory suffix for %s", processName)
        }
    }
}

This is the Google-documented workaround (WebView.setDataDirectorySuffix). It fixes the common case where the collision is between the main process and a secondary :something process started by a transitive dependency (Firebase, WorkManager, a webkit-backed third-party SDK, etc.).

The suffix is applied only in non-main processes, so the main UI process keeps using the default data directory and existing WebView storage / cookies are left untouched. Pre-API-28 devices skip the call.

Layer 2 — Narrow UncaughtExceptionHandler (defensive)

The Crashlytics report attached to #45 actually shows two processes with the same name (com.flamyoad.honnoki / com.flamyoad.honnoki). That points at one of two things the suffix can't fix:

  • a stale data-dir file lock left by a previously-killed instance of this same process (the lock owner PID is gone but the lock file remains);
  • the system swapping the WebView provider implementation while we're running, racing two AwBrowserProcess startups.

In those cases, letting the exception propagate kills the app with a user-visible "App keeps stopping" dialog and another Crashlytics record. Layer 2 installs a narrow handler that recognises this specific exception by walking the cause chain for the message Using WebView from more than one process, logs it via Timber, and silently kills the current process via Process.killProcess + exitProcess. Android's ActivityManager will then restart any foreground component cleanly, by which time the OS has released the file lock.

private fun installWebViewMultiProcessGuard() {
    val previous = Thread.getDefaultUncaughtExceptionHandler()
    Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
        if (isWebViewMultiProcessCrash(throwable)) {
            Timber.w(throwable, "Swallowing WebView multi-process data-dir crash; killing pid %d", Process.myPid())
            Process.killProcess(Process.myPid())
            exitProcess(10)
        } else {
            previous?.uncaughtException(thread, throwable)
        }
    }
}

Every other exception is forwarded to the previous default handler (Crashlytics, etc.) untouched, so unrelated crashes still get reported normally.

Why both layers?

The result: in every code path that historically produced this exception, the user no longer sees a crash dialog and you no longer get a hard Crashlytics record for this signature.

Risk

Very low.

  • Layer 1 is a no-op for the main process and on pre-P devices.
  • Layer 2 only intercepts a single, very specific RuntimeException message string. Anything else flows through to the previous default handler.
  • No new runtime dependencies (Application.getProcessName() is framework-provided since API 28).

Pattern source

Layer 1 matches a recurring fix pattern mined from Justson/AgentWeb PR #655 (issues #542 / #646), adapted to use the framework Application.getProcessName() API instead of AgentWeb's ProcessUtils helper so it drops in without any extra utility class. Layer 2 is the standard hardening that complements that pattern when the colliding processes share a name.

jim-daf added 2 commits May 2, 2026 15:23
Sets a per-process WebView data directory suffix in MyApplication.onCreate so that secondary processes (typically isolated services started by Firebase / WorkManager / third party SDKs) don't trip https://crbug.com/558377:

java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported.

Uses Application.getProcessName() (API 28+) and only applies the suffix when the current process is NOT the main app process, so the main UI process keeps using the default data dir and existing WebView storage is preserved.
The setDataDirectorySuffix workaround only helps when the colliding processes have different names. The Crashlytics report in flamyoad#45 actually shows two processes with the same name (com.flamyoad.honnoki / com.flamyoad.honnoki), which means the lock collision is most likely a stale data-dir lock left by a previously-killed instance, or a WebView provider swap mid-run. Neither is fixable by a suffix.

Install a narrow Thread.setDefaultUncaughtExceptionHandler that recognises this exact RuntimeException (matched by message walk through the cause chain), logs it via Timber, and silently kills the current process so the OS releases the file lock and ActivityManager can restart any foreground component cleanly. All other exceptions are forwarded to the previous default handler (Crashlytics) untouched.
@jim-daf jim-daf changed the title Fix #45: avoid multi-process WebView data-dir crash (crbug/558377) Fix #45: prevent and contain multi-process WebView data-dir crash May 2, 2026
@jim-daf jim-daf marked this pull request as ready for review May 2, 2026 16:50
Copilot AI review requested due to automatic review settings May 2, 2026 16:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Mitigates the Android WebView multi-process “same data directory” crash (crbug.com/558377 / Issue #45) by configuring per-process WebView data dirs and adding a narrowly-scoped uncaught-exception backstop early in Application.onCreate().

Changes:

  • Install a default UncaughtExceptionHandler that detects the specific WebView data-dir collision crash and terminates the current process to allow a clean restart.
  • On API 28+, apply WebView.setDataDirectorySuffix(...) for non-main processes to prevent cross-process WebView data directory collisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*/
private fun applyWebViewDataDirectorySuffix() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
val processName = getProcessName() ?: return
@flamyoad
Copy link
Copy Markdown
Owner

flamyoad commented May 3, 2026

?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants