Skip to content

[Impeller] SIGABRT in vkDestroyFramebuffer after long background dwell (cross-GPU: Mali + Adreno) #191168

Open

Description

@vedamayank

UPDATE 2026-08-17: deeper forensics across what are now 9 occurrences changed the reproduction model and corrected the toolchain info. The crashes fire while the app is backgrounded, during background FlutterEngine create/destroy churn (Workmanager tasks), not on resume. The crashing binaries were built on Flutter 3.44.8 / engine 0cd610717bde, not 3.47.0 as this body originally said. Full data, corrections, and an MRE harness: comment. The body below has been corrected in place.

Steps to reproduce

  1. Flutter app using Impeller (Vulkan backend) on Android, with workmanager background tasks (each Android task execution spawns a fresh FlutterEngine with its own Impeller Vulkan context and destroys it on completion).
  2. Use the app normally, then background it (HOME, not swipe-away).
  3. Leave it backgrounded for hours (8h to 19h measured across our occurrences) while the background tasks keep running on their schedule.
  4. A background task execution eventually crashes the process: the main (platform) thread runs vkDestroyFramebuffer from inside Looper::pollOnce against driver state that is already destroyed.

The crash is silent for the user (the app is backgrounded; it is simply gone from recents afterwards). One measured case crashed 5 minutes after the app's self-scheduled 00:10-local background task fired, 8h11m after backgrounding. Crashed processes show engine churn in their thread lists: e.g. only 3.raster alive (engines 1 and 2, including the original UI engine, already destroyed), or 1.raster/2.raster/6.raster alive (engines 3 to 5 came and went).

An MRE harness that compresses this cadence from hours to seconds (self-rescheduling workmanager one-off every ~20s, plus forced job runs and am send-trim-memory RUNNING_CRITICAL): https://github.com/vedamayank/vk_engine_churn_repro

Crash signature

FORTIFY: pthread_mutex_lock called on a destroyed mutex (0x...)
Fatal signal SIGABRT (6), SI_QUEUE (-1)

Mechanism: Tombstone (native, unhandled). On two devices the same shape manifests as SIGSEGV (SEGV_MAPERR) instead (FORTIFY catches the freed mutex before the wild dereference on some builds, not on others).

Stack trace (Qualcomm/Adreno device)

abort
__fortify_fatal
HandleUsingDestroyedMutex
pthread_mutex_lock
<driver-internal frame>   (module hash tracks the device model, e.g. f6b5df5188 on CPH2707)
<driver-internal frame>
<driver-internal frame>
qglinternal::vkDestroyFramebuffer
<libflutter.so frame>     (~12 unsymbolicated Impeller/engine frames)
...
android::Looper::pollOnce
android::android_os_MessageQueue_nativePollOnce
android.os.Looper.loopOnce
android.os.Looper.loop
android.app.ActivityThread.main

Correction from the original report: the three hashed frames between pthread_mutex_lock and vkDestroyFramebuffer are driver-internal (their module hash differs per device model), not libflutter. The libflutter.so (Impeller) frames are the unsymbolicated ones between Looper::pollOnce and vkDestroyFramebuffer. Mali devices show the same shape with the driver frames unsymbolicated (the Mali driver exports no symbols).

Affected devices (9 events, 9 users, Aug 11-16 2026)

Date (UTC) Device GPU Signal
Aug 11 18:05 realme RMX5003 Mali SIGABRT
Aug 14 12:04 OnePlus CPH2707 Adreno SIGABRT
Aug 14 18:59 OnePlus CPH2487 Adreno SIGSEGV (SEGV_MAPERR)
Aug 14 23:02 realme RMX3999 Mali SIGABRT
Aug 15 04:48 realme RMX5033 Mali SIGABRT
Aug 15 05:15 OnePlus CPH2707 Adreno SIGABRT
Aug 15 12:24 Samsung SM-S906U (S22+) Adreno SIGSEGV (SEGV_MAPERR)
Aug 15 14:22 Samsung SM-A146P (A14 5G) Mali SIGABRT
Aug 16 13:58 realme RMX5003 Mali SIGABRT

Android 15 and 16, all arm64, all Play installs. Crosses GPU vendors, so the bug is in Impeller's Vulkan backend teardown ordering, not one driver.

Relationship to #187237 / PR #187477

PR #187477 added WaitIdle() in AHBSwapchainImplVK::~AHBSwapchainImplVK before destroying swapchain-owned Vulkan objects. That fix IS present in our build (Flutter 3.44.8 stable, via cherry-pick #188531, commit 97f70d2e9ef), and the crash still occurs.

This crash is in vkDestroyFramebuffer, not in swapchain teardown. The framebuffer destruction path does not appear to go through AHBSwapchainImplVK, so the WaitIdle guard from #187477 does not protect it. Same class of bug (destroying a Vulkan object whose device-side state is already gone), different site.

Additional context

  • Every engine in the crashed processes, headless background ones included, has its own IplrVkFenceWait/IplrVkResMgr thread pair, i.e. a full Impeller Vulkan context per engine.
  • The background tasks also rasterize widgets offscreen through Flutter (HomeWidget.renderFlutterWidget), so background engines create real framebuffers.
  • No Dart-level breadcrumbs survive because the crash fires below the framework layer while the app is backgrounded.
  • Our crash capture changed in the same app release (tombstone-based native capture is new in it), so absence of this signature on earlier releases is not evidence of a regression range.

Flutter version

Crashing production binaries (correction; the original report mistakenly quoted the dev machine's 3.47.0 / 59d54a2b2896):

Flutter 3.44.8, channel stable
Engine revision 0cd610717bde95fd88343c64f81c11ba4e5c0010
android-arm64-release libflutter.so GNU BuildID 889a1df37c58955a78bb76391e165822b866bf48

Our next release is built on Flutter 3.47.0 (engine 5f77625673); we will report whether the crash reproduces there.

Activity

  1. puneetkukreja98 commented on Aug 17, 2026

    @puneetkukreja98
    Contributor

    @vedamayank Could you share an easier or alternative way to reproduce this with an MRE.? Also, could you provide the o/p of your flutter doctor -v?

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    and removed
    in triagePresently being triaged by the triage team
    on Aug 17, 2026
  3. vedamayank commented on Aug 17, 2026

    @vedamayank
    Author

    @puneetkukreja98 Thanks for picking this up. Three parts: (1) the flutter doctor -v you asked for, (2) corrections to the original report after deeper forensics, (3) a much more specific reproduction model plus an MRE harness, based on cross-referencing what are now 9 occurrences with our product analytics and full thread dumps.

    1. flutter doctor -v

    [✓] Flutter (Channel stable, 3.47.0, on macOS 26.6.1 25G76 darwin-arm64, locale en-IN)
        • Flutter version 3.47.0 on channel stable at /Users/mg/development/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 4cf2416426 (6 days ago), 2026-08-11 11:53:49 -0700
        • Engine revision 5f77625673
        • Dart version 3.13.0
        • DevTools version 2.60.0
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 37.0.0)
        • Android SDK at /Users/mg/Library/Android/sdk
        • Platform android-37.1, build-tools 37.0.0
        ! Multiple adb binaries found (platform-tools 37.0.x via SDK and Homebrew)
        • Java version OpenJDK Runtime Environment Temurin-17.0.20+8 (build 17.0.20+8)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 26.6)
    [✓] Chrome - develop for the web
    [✓] Connected device (3 available)   [device names redacted]
    [✓] Network resources
    
    • No issues found!
    

    2. Corrections to the original report

    Version correction. The doctor output above is today's dev machine (3.47.0). The production binaries behind these crashes were built on Flutter 3.44.8 stable, engine 0cd610717bde95fd88343c64f81c11ba4e5c0010. The original report incorrectly quoted 3.47.0 / 59d54a2b2896; that was the dev machine at filing time, sorry for the confusion. For symbol matching: the android-arm64-release libflutter.so for that engine has GNU BuildID 889a1df37c58955a78bb76391e165822b866bf48.

    The AHBSwapchainImplVK::WaitIdle() fix is present at that tag (cherry-picked via #188531, commit 97f70d2e9ef), so the original point stands: this crash is in a path that fix does not cover.

    Stack annotation correction. The three hashed frames between pthread_mutex_lock and qglinternal::vkDestroyFramebuffer are driver-internal, not libflutter. Their module hash tracks the device model, not our binary: f6b5df5188 on both OnePlus CPH2707 events, 2e251f7ab9 on the Galaxy S22+, 0c393b63cf on the CPH2487. The libflutter.so (Impeller) frames are the ~12 unsymbolicated frames between Looper::pollOnce and vkDestroyFramebuffer. We are also wiring the matching engine symbols into our Sentry org so future occurrences arrive with those frames named, and we can share the raw tombstone event JSON for any of the 9 events if that helps.

    3. Updated scale and a revised reproduction model

    9 events, 9 users, 7 device models in 6 days (Aug 11-16 UTC). 8 of the 9 carry release metadata, and all 8 are the same app release; the Aug 11 event reported no version metadata:

    Date (UTC) Device GPU Signal
    Aug 11 18:05 realme RMX5003 Mali SIGABRT
    Aug 14 12:04 OnePlus CPH2707 Adreno SIGABRT
    Aug 14 18:59 OnePlus CPH2487 Adreno SIGSEGV (SEGV_MAPERR)
    Aug 14 23:02 realme RMX3999 Mali SIGABRT
    Aug 15 04:48 realme RMX5033 Mali SIGABRT
    Aug 15 05:15 OnePlus CPH2707 Adreno SIGABRT
    Aug 15 12:24 Samsung SM-S906U (S22+) Adreno SIGSEGV (SEGV_MAPERR)
    Aug 15 14:22 Samsung SM-A146P (A14 5G) Mali SIGABRT
    Aug 16 13:58 realme RMX5003 Mali SIGABRT

    Android 15 and 16, all arm64, all installed from Play. Adreno is confirmed by the qglinternal:: frames; Mali by mali-event-hand/mali-mem-purge threads in the dumps (the Mali driver exports no symbols, so those stacks show the same shape with the driver frames unsymbolicated). We read SIGABRT and SIGSEGV as one bug: identical crashed-thread shape (main thread, inside Looper::pollOnce, destroying a framebuffer); FORTIFY just catches the freed mutex before the wild dereference on some builds.

    One caveat we owe you: this app release also changed our crash capture (tombstone-based native capture is new in it), so "zero occurrences on earlier releases" is not evidence the bug is new. Please do not treat it as a regression range.

    What analytics cross-referencing shows. We matched each crash against our product analytics (lifecycle events per user). This changes the reproduction model from the original report:

    • Every crash fired while the app was backgrounded, not on resume. Where we have analytics coverage, the last event before each crash is Application Backgrounded 8h to 19h earlier, and nothing foreground follows until a later cold start. Users never see the crash; the app is simply gone from recents.
    • The app runs Workmanager background tasks: a 6h periodic home-widget refresh plus a one-off that reschedules itself for 00:10 local time daily. On Android each task execution spawns a fresh FlutterEngine and destroys it on completion, and the task rasterizes widget images offscreen through Flutter (HomeWidget.renderFlutterWidget).
    • The cleanest data point: the OnePlus CPH2707 crash at 05:15 UTC is 00:15 local for that user, 5 minutes after our self-scheduled 00:10-local background task, 8h11m after backgrounding. The next sign of life from that install is a background widget-sync in a fresh process, with no app open in between.
    • The thread dumps show engine churn at crash time. The CPH2707 process has only 3.raster/3.io alive: engines 1 and 2, including the original UI engine, were already destroyed. The S22+ process has 1.raster, 2.raster, and 6.raster alive, so engines 3 to 5 had come and gone. Every engine, headless ones included, has its own IplrVkFenceWait/IplrVkResMgr pair, i.e. a full Impeller Vulkan context.

    So our best current model: hours after backgrounding (with the OS trimming/reclaiming in between), background FlutterEngine create/destroy churn eventually runs a deferred vkDestroyFramebuffer on the platform thread against a VkDevice that engine teardown already destroyed. The long dwell seems to matter as accumulation plus OS reclamation, not because resume is involved.

    MRE

    Minimal harness: vk_engine_churn_repro. It compresses the production cadence from hours to seconds:

    • Foreground: continuous animation so the UI engine holds a live swapchain.
    • A self-rescheduling workmanager one-off spawns and destroys a background engine every ~20s while the app is backgrounded (same plugin and pattern as production, 0.10.x).
    • churn.sh forces the queued job to run each cycle and issues am send-trim-memory RUNNING_CRITICAL, replacing the overnight dwell.
    • Escalations in the README: Developer options "Don't keep activities" (forces the UI engine's surface teardown on every HOME press), GPU-heavy foreground apps for real reclaim, and Khronos validation layers to surface the invalid destroy deterministically instead of waiting for the driver to trip.

    Honest status: we have not yet caught the crash on demand ourselves; production needed hours of dwell at a 6h task cadence, and we are now running the compressed harness on the affected models. Sharing it already because the conditions above are narrow enough to point at the multi-engine teardown path, and your team can likely get there faster with engine internals in view.

    Happy to run instrumented or validation builds on affected models, share raw tombstones, or test a patch. This is a recurring fatal for us (9 users in 6 days on the current release) and we believe it contributes to crash complaints in our store reviews. Our next release is built on 3.47.0 (engine 5f77625673), and we will report whether it reproduces there.

  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Aug 17, 2026
  5. puneetkukreja98 commented on Aug 18, 2026

    @puneetkukreja98
    Contributor

    @vedamayank Thanks for the response, I noticed that you are using the workmanager package in your MRE.
    Could you provide a MRE without third-party packages?

  6. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    and removed
    in triagePresently being triaged by the triage team
    on Aug 18, 2026
  7. vedamayank commented on Aug 19, 2026

    @vedamayank
    Author

    @puneetkukreja98 Done, the MRE is now plugin-free: vk_engine_churn_repro (same link) has zero pub dependencies and zero added Gradle dependencies. Framework JobScheduler plus ~100 lines of Kotlin do exactly what the plugin did: new FlutterEngine(context) -> executeDartEntrypoint (offscreen toImage renders, pure dart:ui) -> jobFinished -> engine.destroy() posted to a later main-looper turn. The workmanager version stays at tag v1-workmanager. One note from the README: it must be plain FlutterEngine, not FlutterEngineGroup; grouped engines share one ContextVK/VkDevice, which masks the bug.

    Since the last comment we also received a fully symbolicated production occurrence (10th event, 8th device model; in-process signal-handler capture instead of a tombstone; same release on Flutter 3.44.8, engine 0cd610717bde, Adreno, Android 16). Innermost frames first, libc++ internals elided:

    qglinternal::vkDestroyFramebuffer              <- SIGSEGV inside the driver
    std::__fl::__shared_weak_count::__release_shared
    std::__fl::array<T>::~array (array:175)
    impeller::TextureSourceVK::~TextureSourceVK (texture_source_vk.cc:11)
    impeller::TextureVK::~TextureVK (texture_vk.cc:25)
    impeller::DlImageImpeller::~DlImageImpeller (dl_image_impeller.cc:60)
    (anonymous)::lambda::~lambda (picture.cc:244)
    std::__fl::__function::__func<T>::destroy_deallocate
    fml::MessageLoopImpl::FlushTasks (message_loop_impl.cc:122)
    fml::MessageLoopAndroid::OnEventFired (message_loop_android.cc:91)
    android::Looper::pollOnce
    

    That names the mechanism end to end: a message-loop task from Picture::DoRasterizeToImage's task chain (picture.cc:244) is destroyed during FlushTasks, releasing the last reference to a DlImageImpeller; ~TextureSourceVK then destroys its cached frame_data_ (FramebufferAndRenderPass), whose SharedHandleVK<vk::Framebuffer> destroys through its raw VkDevice, which the already-destroyed engine no longer backs. ~TextureSourceVK is defaulted, with no device-liveness guard, on 3.44 and on master; #189666 added exactly that guard to the AHB subclass members and its comment notes the destructor can run after ContextVK/VkDevice destruction, but the base-class frame_data_ teardown was not covered. Same bug class as #187703 (FenceWaiter) and #186458 (CommandPoolVK), both already fixed one object at a time.

    The harness reproduces this on demand. OnePlus NE2211 (Adreno, Android 16), Flutter 3.47.0 stable, debug build with the stock validation layer (EnableVulkanValidation): crashed within 5 churn cycles, on both process generations tried. The timestamped sequence from one occurrence:

    11:16:42.881  onStopJob mid-task -> posted engine.destroy() executes
    11:16:43.108  VALIDATION: vkDestroyDevice(): For VkDevice [ImpellerDevice],
                  VkRenderPass 0xaa00000000aa[LinearGradient] has not been destroyed.
                  [VUID-vkDestroyDevice-device-05137]
    11:16:43.109  VALIDATION: ... VkFramebuffer 0xab00000000ab has not been destroyed.
    11:16:43.138  SIGSEGV SEGV_MAPERR, main thread, in Looper::pollOnce; pc = wild jump
                  through the destroyed device's dispatch
    

    The leaked pair the layer names (framebuffer + render pass) is exactly what frame_data_ caches. Symbolized against the engine's own debug symbols (BuildID-verified), the crash chain on 3.47.0 is frame-for-frame the production stack above: FlushTasks -> destruction of an undelivered Picture::DoRasterizeToImage result lambda (picture.cc:266; captures shared_ptr<impeller::Texture>) -> ~TextureVK -> frame_data_ teardown (std::vector<CachedFrameDataEntry> there, std::array<FramebufferAndRenderPass,2> at 3.44.8) -> SharedHandleVK release through the dead device. Full tombstones, validation output, and symbolized stacks are in the repo under evidence/, with the run protocol in RESULTS.md. The release build survived 60 freeze-stress cycles before this, so the validation layer is what makes it deterministic; production waits for the driver to trip instead.

    Suggested labels: engine, e: impeller, platform-android, c: fatal crash, a: production, found in release: 3.44, customer: crowd.

  8. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Aug 19, 2026
  9. puneetkukreja98 commented on Aug 19, 2026

    @puneetkukreja98
    Contributor

    @vedamayank Thanks for the updated MRE, routing this to team-engine for further investigation.

  10. added
    platform-androidAndroid applications specifically
    engineflutter/engine related. See also e: labels.
    e: impellerImpeller rendering backend issues and features requests
    c: crashStack traces logged to the console
    c: fatal crashCrashes that terminate the process
    and removed
    in triagePresently being triaged by the triage team
    c: crashStack traces logged to the console
    on Aug 19, 2026
  11. added
    P2Important issues not at the top of the work list
    on Aug 24, 2026
  12. phpirate commented on Sep 22, 2026

    @phpirate

    Second production app hitting this, moving our data over from #193182 which @mbcorona closed as a duplicate. The analysis above is well ahead of anything I could add to, so only what might be useful.

    Same shape as @vedamayank's app: Impeller default, offscreen widget rasterisation via HomeWidget.renderFlutterWidget() from short-lived background isolates. Flutter 3.44.6 stable, engine d3a3293399, so just behind the 3.44.8 above and without the #187477 cherry-pick.

    Prevalence: 8 crashes from 8 distinct users in 6 days, against a Play install base of about 26 devices. Two vendors (Adreno and Mali), 5 device models, Android 15 and 16, two of our releases. Seven are teardown aborts nobody sees; one hit with the app in the foreground.

    One symbolicated Mali stack, in case it is useful that the Mali side is not just shape-matched. Caught in-process by a signal handler rather than read from a tombstone, on a Xiaomi 25028RN03Y, Android 15 (A15.0.2.0.VGWMIXM):

    impeller::DlImageImpeller::~DlImageImpeller
    impeller::TextureVK::~TextureVK
    impeller::TextureSourceVK::~TextureSourceVK
    std::__fl::array<T>::~array
    impeller::Attachment::~Attachment
    /vendor/lib64/egl/libGLES_mali.so
    /vendor/lib64/egl/libGLES_mali.so
    libc.so  pthread_mutex_lock
    libc.so  HandleUsingDestroyedMutex
    libc.so  abort
    

    That is the ~TextureSourceVK → frame_data_ teardown you named, reached through the Mali blob instead of Adreno, which fits the unguarded destructor being the common factor rather than anything vendor-specific.

    Happy to run the harness on the affected Mali models or test a patched engine build. Raw tombstones and breadcrumbs for any of the 8 are available if they would help.

  13. phpirate commented on Oct 2, 2026

    @phpirate

    Prevalence update from the second app, 10 days on.

    When I posted on 22 Sep it was 8 crashes from 8 distinct users. The largest of our four Sentry groups for this signature is now at 116 events from 79 distinct users, still climbing — 10 new users in the last 26 hours alone. Spread across 5 of our releases, both vendors, Android 13 through 16, and 20+ device models. No release of ours has ever been free of it.

    One mechanism detail I left out last time, which probably explains why we see it so heavily: our workmanager task is registered unconditionally at every launch on a 15-minute period, and each execution calls HomeWidget.renderFlutterWidget(). So every install churns a background FlutterEngine four times an hour regardless of whether the user has actually placed a widget — the cadence in the MRE, running continuously in production.

    Field data above is still Flutter 3.44.6 (engine d3a3293399); our shipped build has not moved yet. I did check 3.47.5 source: TextureSourceVK::~TextureSourceVK is still = default, and frame_data_ is now a std::vector<CachedFrameDataEntry> rather than a single FramebufferAndRenderPass, so teardown destroys N framebuffers through the dead device instead of one.

    Offer stands to test a patched engine build on affected hardware, Adreno or Mali.

  14. wazeemlabs commented on Oct 12, 2026

    @wazeemlabs

    A third production app with this abort, and a second way to get the extra lifetime: no second FlutterEngine and no workmanager, only ordinary background isolates (Isolate.spawn and Isolate.run) in the root isolate's group.

    Build: Flutter 3.47.2 stable, engine a804b26, Dart 3.13.2. Impeller default (no EnableImpeller meta-data). One FlutterFragmentActivity, a new engine per activity, no FlutterEngineGroup, no cached engine, no home-screen widget rendering.

    Prevalence, 28 days to 2026-10-12 (Play Console reporting API): 15 reports from 14 devices on three of our releases. Mali 11 (Honor, Samsung Galaxy A16 5G, Redmi 13C, Redmi 14C, Redmi Note 14, Xiaomi 14T, moto g67, ZTE 9030 twice, Oppo A5 Pro 5G, one unnamed), Adreno Vulkan 4 (Galaxy S24 Ultra, OnePlus 13R, motorola edge 50 fusion, motorola signature). Android 11 to 16.

    Two backtrace shapes, both on a Dart thread-pool thread, never on the UI or raster thread:

    1. At isolate shutdown (12 of 15):
    abort < __fortify_fatal < HandleUsingDestroyedMutex < pthread_mutex_lock
      < [libGLES_mali.so or vulkan.adreno.so: vkDestroyFramebuffer]
      < impeller::Attachment::~Attachment < impeller::TextureSourceVK::~TextureSourceVK
      < impeller::TextureVK::~TextureVK
      < default_delete<flutter::DlDeferredImageGPUImpeller::ImageWrapper>
      < flutter::DlRuntimeEffectImpeller::~DlRuntimeEffectImpeller
      < flutter::DisplayList::DisposeOps < flutter::DisplayList::~DisplayList
      < flutter::DisplayListRasterCacheItem::~DisplayListRasterCacheItem
      < flutter::DisplayListLayer::~DisplayListLayer < flutter::ContainerLayer::~ContainerLayer (x4)
      < flutter::EngineLayer::~EngineLayer
      < dart::FinalizeWeakPersistentHandlesVisitor::VisitHandle
      < dart::Isolate::LowLevelCleanup < dart::Dart::ShutdownIsolate < Dart_ShutdownIsolate
      < dart::ThreadPool::Worker::Main < dart::ThreadStart
    
    1. During an old-space GC inside a running background isolate (3 of 15), the same chain from EngineLayer::~EngineLayer up, reached from:
      < dart::MarkingWeakVisitor::VisitHandle < dart::ParallelMarkTask::RunEnteredIsolateGroup
      < dart::SafepointHandler::RunTasks < dart::PageSpace::CollectGarbage
      < dart::Heap::CollectOldSpaceGarbage < dart::Thread::HandleInterrupts
      < dart::IsolateMessageHandler::HandleMessage < ... < dart::MessageHandlerTask::Run
      < dart::ThreadPool::Worker::Main
    

    The Dart frames below the GC are our background isolate's own work (image decoding through FFI, a TFLite model running over image tiles), never UI code.

    What the frames show, and what is inferred: the root isolate runs on the UI thread, so a Dart_ShutdownIsolate on a thread-pool worker is a spawned isolate, here running group-wide finalizers of EngineLayers the framework never disposed. From that we infer the sequence: the activity is destroyed while a background isolate is still busy (on Android 12+ that is the task swiped from recents), the engine's Vulkan device goes with it, and the background isolate then finalizes the UI's layers against the dead device, in a GC or when it exits as the group's last isolate. We have not reproduced it on a device; the inference is from the backtraces alone. If it holds, it is the same extra lifetime this issue's second engine provides, from inside one engine.

    The 15 backtraces, frames only, can be attached as a text file if that helps.

  15. TonyDowney commented on Oct 23, 2026

    @TonyDowney

    One more trigger for this abort, on Mali with Vulkan: an ordinary FlutterActivity recreate after a configuration change. No background engine, no workmanager, no home-widget rendering. The app was in the foreground.

    Build: Flutter 3.47.3, engine 0e228ec8c8d2abc9fcf1d053e8a40665bb859ec7, Dart 3.13.3. Impeller default. Profile build. One FlutterActivity that owns its engine (no cached engine, no FlutterEngineGroup).

    Device: Pixel 8 (Tensor G3, Mali), Android 16.

    Steps:

    1. Open the app and leave it in the foreground.
    2. Change the system navigation mode, for example adb shell cmd overlay enable-exclusive --category com.android.internal.systemui.navbar.threebutton. Android swaps a resource overlay, so the activity is destroyed and recreated.
    3. The default FlutterActivity destroys its engine with the old activity and creates a new engine for the new one, in the same process.

    About 100 ms after the new engine attached, logcat showed F libc: FORTIFY: pthread_mutex_lock called on a destroyed mutex. The aborting thread was in a Vulkan destroy call from libflutter.so. The frames below it have no symbols, so "the old engine's teardown" is my reading of the timing, not something the trace proves:

    #04 libc.so            __fortify_fatal+224
    #05 libc.so            HandleUsingDestroyedMutex+64
    #06 libc.so            pthread_mutex_lock+140
    #07 libGLES_mali.so    vulkan::device::remove_slot_data+32
    #08 libGLES_mali.so    vkDestroyRenderPass+72
    #09.. libflutter.so    (no symbols)
    

    It was vkDestroyRenderPass here, not vkDestroyFramebuffer. The shape is the same as the teardown cases above: a destroy call into the driver after the driver's state is gone.

    Rate: 1 abort in 2 tries on the first day. It did not come back on demand after that: 0 in 28 more recreates on the same build. On every recreate the old engine detached and a new Impeller context was created (6 of 6 that were still in the log buffer).

    What made it worse for us: the app also runs Sentry with the native (NDK) integration. Sentry's signal handler caught the abort on that thread and blocked. At the same moment, the new engine's Dart main() re-initialised Sentry on the platform thread, which closes the previous native SDK and waits for it. The two waited on each other: no tombstone, the UI froze on its last frame, and an ANR followed 80 s later. I am reporting that half to Sentry separately; it only matters here because it turns this abort into a silent hang.

    Workaround that removed the teardown on this path: keep the engine across a configuration-change recreate only. In the activity: shouldDestroyEngineWithHost() returns !isChangingConfigurations; onDestroy() keeps flutterEngine (and backCallbackState) in a static slot when isChangingConfigurations is true; provideFlutterEngine() hands it to the next instance and calls setFrameworkHandlesBack(...) with the saved value. After that: 30 recreates, 0 engine teardowns, 0 aborts. Two things a retained engine loses with the old activity, in case anyone copies this: the back-callback registration (Back leaves the app on Android 16 without the setFrameworkHandlesBack call) and the SystemChrome UI mode and preferred orientations, which need to be sent again. Do not use one process-wide engine for this: a FLAG_ACTIVITY_CLEAR_TASK launch then throws AssertionError ... has been attached to by another activity.

    This only avoids the teardown on a recreate. Finishing the activity still destroys an engine, so the race is still there.

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 listc: fatal crashCrashes that terminate the processe: impellerImpeller rendering backend issues and features requestsengineflutter/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