Skip to content

[engine] Zero lease assertion when an expired caller draws while another engine keeps shared queues merged #194536

Closed

Description

@bpleekm

Attachments

Summary

SharedThreadMerger::DecrementLease() can abort on lease_term_ref > 0 when an engine resumes drawing after its lease expires while another engine keeps the shared raster/platform queues merged.

This issue isolates the zero-lease assertion from the broader PlatformView problems in #153211. It does not cover the separate deadlock or foreground/background symptoms. Proposed fix: #194200.

Steps to reproduce

Use two RasterThreadMerger instances sharing one SharedThreadMerger and the same distinct platform/raster task queues:

  1. A calls MergeWithLease(1).
  2. A calls DecrementLease(). Its lease reaches zero and the queues unmerge, but its map entry remains.
  3. B calls MergeWithLease(2), merging the shared queues again.
  4. A resumes drawing and calls DecrementLease(). The shared merged-state check succeeds because B has an active lease.
  5. A's stale zero-valued entry triggers the fatal assertion.

The corresponding Android scenario uses engines in the same FlutterEngineGroup, with B's PlatformView keeping the queues merged while A resumes rendering. The affected implementation is shared C++ code; the iOS demo from #153211 has not been validated with the patch.

Android device reproduction

Confirmed on an Android test device running Android 17, using Flutter 3.35.3, Engine ddf47dd3ff, debug mode, and Impeller Vulkan. The attached demo uses two FlutterActivity instances in the same FlutterEngineGroup and a native TextView in hybrid composition; no WebView plugin is required.

  1. On the bottom page, press ADD PlatformView and wait for the 40-frame sequence.
  2. Press REMOVE PlatformView and wait for the next 40-frame sequence.
  3. OPEN the top Activity containing another PlatformView.
  4. Return to the bottom page and repeat ADD, REMOVE, OPEN in the same app process if needed.

Two initial automated cycles did not crash. A subsequent manual cycle triggered SIGABRT with the exact zero-lease assertion. The observed crash occurred while opening the top Activity again, after top onPostResume and before pressing return. Reproduction is timing-dependent; no reproduction rate is claimed. The demo uses an ordinary opaque top Activity, not a translucent variant.

Observed event sequence:

 Bottom ADD completed
 Bottom REMOVE and 40-frame sequence completed
 Top onPostResume
 FATAL shared_thread_merger.cc(59): lease_term_ref > 0, lease_term=0

The attachments above contain the source, build/run instructions, app-specific crash stack, and lifecycle timeline. Patched Android validation results are included in the validation attachment and in #194200; iOS application validation remains pending.

Expected results

Drawing with an expired lease must not abort or consume another caller's lease. Queues should unmerge when the final active lease expires.

Actual results

[FATAL:flutter/fml/shared_thread_merger.cc(59)] Check failed: lease_term_ref > 0. lease_term should always be positive when merged, lease_term=0

Root cause

DecrementLease() retains zero-valued entries in lease_term_by_caller_. RasterThreadMerger::IsMergedUnSafe() checks the shared merge state rather than whether the current caller has an active lease. Thus an expired caller can reach the decrement while another caller remains active.

Production workaround

In our Android return-to-bottom scenario, explicitly destroying the outgoing/top Flutter engine in the Activity's finish() method, instead of waiting for onDestroy(), avoids this crash. We have validated this application-level workaround in production.

This changes teardown timing for the transition where the bottom Activity can resume drawing before the top Activity reaches onDestroy(). It is a workaround for the affected lifecycle sequence; the stale zero-lease record still needs to be fixed in the Engine. Production validation here applies to the workaround. Patched Android device validation is documented in #194200; iOS application validation remains pending.

Reproduction and validation

The deterministic C++ regression is in #194200: RasterThreadMerger.ExpiredLeaseCanDecrementWhileAnotherCallerIsMerged. A standalone reproduction linked against the real FML merger and task queues aborts before the fix and passes afterward.

Unpatched source revision: 87d56361e2246bea8e40441403feda5ceb64a631.

Patched Engine validation on macOS arm64, host_debug_unopt_arm64:

  • New regression: 1 passed.
  • RasterThreadMerger.*: 17 passed.
  • Full fml_unittests: 204 tests across 38 suites passed.

Patched Android: 30/30 cycles completed across three app processes without the target assertion or process restarts, with the early-destroy workaround disabled. Same-framework unpatched/patched device A/B and iOS application validation remain pending.

Activity

  1. suryaKommana2662 commented on Oct 16, 2026

    @suryaKommana2662
    Contributor

    @bpleekm Thanks for the detailed report and reproduction steps. I see that PR #194200 contains the proposed fix. Routing this to the team-engine for further review.

  2. added
    platform-androidAndroid applications specifically
    engineflutter/engine related. See also e: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Oct 16, 2026
  3. added
    P2Important issues not at the top of the work list
    on Oct 19, 2026
  4. added a commit that references this issue on Oct 30, 2026
    5e805bf
  5. added a commit that references this issue on Nov 3, 2026
    f3b1ee7
  6. github-actions commented on Nov 13, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  7. locked as resolved and limited conversation to collaborators on Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listengineflutter/engine related. See also e: labels.platform-androidAndroid applications specificallyteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions