Repository navigation
Description
Steps to reproduce
- Run a Flutter app on Android using the Impeller Vulkan backend.
- Trigger frequent
Picture.toImage/Scene.toImagecalls — for example, an app rendering many thumbnails, recents-style task previews, or any screen that snapshotsDisplayLists on the raster thread at a steady rate. - Apply GPU pressure or contention (any of the following reproduces): heavy graphics workload from another process, low memory + memory-fragmented state, or a vendor GPU driver hiccup (we have observed this on both Adreno and Mali devices in production).
- Trigger an Android Activity lifecycle change while the raster thread is busy: home key, recents gesture, screen rotation, or app-switching.
- ANR within 5 seconds for an Input Dispatching ANR, or up to 5 minutes for a
drawState=DRAW_PENDING(Application not responding) ANR.
Expected results
HostBuffer::~HostBuffer should not block the raster thread indefinitely. When the GPU is stuck or under heavy contention, the raster thread should bound the wait so that subsequent raster tasks (especially Surface lifecycle callbacks issued from the platform thread) can be processed within a frame budget. The engine should never let an unbounded vkDeviceWaitIdle hold the raster thread forever.
Actual results
The raster thread blocks indefinitely in vkDeviceWaitIdle() invoked from HostBuffer::~HostBuffer() via IdleWaiterVK::WaitIdle(). Once the raster thread is stuck, all subsequent platform-thread-issued raster tasks — those posted from Shell::OnPlatformViewCreated, Shell::OnPlatformViewDestroyed, and the PlatformViewAndroid::Notify* family — never run, so the matching platform-thread latch.Wait() never signals. The platform thread is stuck, inputs are not processed, the framework's finishDrawing callback is never called, and Android raises an ANR.
Code sample
Raster thread stack (the root)
#00 syscall
#01 __futex_wait_ex
#02 pthread_cond_timedwait
#03 osup_sync_object_timedwait (vendor GPU driver)
#04~#07 vendor GPU driver internals (5 frames)
#08 impeller::IdleWaiterVK::WaitIdle() (context_vk.h:51)
#09 std::shared_ptr<HostBuffer>::~__on_zero_shared
#10 impeller::ContentContext::~ContentContext()
#11 std::shared_ptr<AiksContext>::~__on_zero_shared
#12 flutter::DoMakeRasterSnapshot(...)
#13 fml::SyncSwitch::Execute(...)
#14 flutter::SnapshotControllerImpeller::MakeRasterSnapshot(...)
#15 flutter::Rasterizer::MakeRasterSnapshot(...)
Platform thread stack (the cascade)
#00 syscall
#01 __futex_wait_ex
#02 pthread_cond_wait
#03 fml::AutoResetWaitableEvent::Wait()
#04 flutter::Shell::OnPlatformViewDestroyed() (shell.cc:1198)
#05 flutter::PlatformViewAndroid::NotifyDestroyed()
#06~ Android Activity onStop transaction
Problematic code
-
impeller/renderer/backend/vulkan/context_vk.h:51—IdleWaiterVK::WaitIdle()callsvk::Device::waitIdle()with no timeout:void WaitIdle() const override { auto strong = device_holder_.lock(); if (strong && strong->GetDevice()) { strong->GetDevice().waitIdle(); // No timeout, waits forever } }
-
impeller/core/host_buffer.cc:45—HostBuffer::~HostBuffer()calls the unboundedWaitIdle()from a synchronous destructor on the raster thread:HostBuffer::~HostBuffer() { if (idle_waiter_) { // Since we hold on to DeviceBuffers we should make sure they aren't // being used while we are deleting the HostBuffer. idle_waiter_->WaitIdle(); } };
-
impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc:330—KHRSwapchainImplVK::WaitIdle()follows the same unbounded pattern, called fromDestroySwapchain(). -
shell/common/shell.cc:1119,1198,shell/common/platform_view.cc:60, andshell/platform/android/platform_view_android.cc(4Notify*functions) — all usefml::AutoResetWaitableEvent::Wait()without timeout, waiting for raster-thread tasks to complete. Same root cause as Impeller Vulkan: Infinite timeout in vkWaitForFences / acquireNextImageKHR causes ANR on Android when GPU is under pressure #184871.
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
"1.raster" sysTid=2917
#00 pc 00000000000a431c /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28) (BuildId: fb68f3df733a20047bcd0820cf727489)
#01 pc 000000000007ce04 /apex/com.android.runtime/lib64/bionic/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+148) (BuildId: fb68f3df733a20047bcd0820cf727489)
#02 pc 000000000008c0d0 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_timedwait+136) (BuildId: fb68f3df733a20047bcd0820cf727489)
#03 pc 000000000099cf24 /vendor/lib64/egl/libGLES_mali.so (osup_sync_object_timedwait+172) (BuildId: 9f5c4c3746186e9b)
#04 pc 000000000099cbe8 /vendor/lib64/egl/libGLES_mali.so (BuildId: 9f5c4c3746186e9b)
#05 pc 000000000099c604 /vendor/lib64/egl/libGLES_mali.so (BuildId: 9f5c4c3746186e9b)
#06 pc 0000000000f4d60c /vendor/lib64/egl/libGLES_mali.so (BuildId: 9f5c4c3746186e9b)
#07 pc 0000000000f31eb4 /vendor/lib64/egl/libGLES_mali.so (BuildId: 9f5c4c3746186e9b)
#08 pc 0000000000c4b960 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#09 pc 0000000000a29110 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#10 pc 0000000000b76e4c /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#11 pc 0000000000c8f6d0 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#12 pc 0000000000cc239c /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#13 pc 0000000000cc21f8 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#14 pc 000000000061c028 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#15 pc 0000000000cc2084 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#16 pc 0000000000c8e870 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#17 pc 0000000000d04f60 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#18 pc 0000000000619490 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#19 pc 000000000061f90c /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#20 pc 0000000000015dc8 /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+944) (BuildId: b47d214080628ccf355c7e15478a121f)
#21 pc 000000000002a64c /system/lib64/libandroid.so (ALooper_pollOnce+100) (BuildId: aacdc7f657d3b74223ce28ff1587fd4f)
#22 pc 000000000061fa54 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#23 pc 000000000061d970 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#24 pc 000000000061d038 /system_ext/lib64/libhyper_os_flutter.so (BuildId: c37a781dff4d5f8f1e98a6a875bb0fe7e91b7422)
#25 pc 000000000008cd74 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*) (.__uniq.67847048707805468364044055584648682506)+184) (BuildId: fb68f3df733a20047bcd0820cf727489)
#26 pc 000000000007e39c /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68) (BuildId: fb68f3df733a20047bcd0820cf727489)
Address: 0x0000000000c4b960, Function: impeller::vk::Result impeller::vk::Device::waitIdle<impeller::vk::detail::DispatchLoaderDynamic>(impeller::vk::detail::DispatchLoaderDynamic const&) const
out/hyperos_release_arm64/../../flutter/third_party/vulkan-deps/vulkan-headers/src/include/vulkan/vulkan_funcs.hpp:974
Address: 0x0000000000a29110, Function: impeller::HostBuffer::~HostBuffer()
out/hyperos_release_arm64/../../flutter/impeller/core/host_buffer.cc:49
Address: 0x0000000000b76e4c, Function: std::_fl::__shared_count::__release_shared[abi:nn210000]()
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__memory/shared_count.h:92
Address: 0x0000000000c8f6d0, Function: std::_fl::default_delete<impeller::ContentContext>::operator()[abi:nn210000](impeller::ContentContext*) const
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__memory/unique_ptr.h:76
Address: 0x0000000000cc239c, Function: std::_fl::__shared_count::__release_shared[abi:nn210000]()
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__memory/shared_count.h:92
Address: 0x0000000000cc21f8, Function: flutter::SnapshotControllerImpeller::MakeRasterSnapshot(sk_sp<flutter::DisplayList>, impeller::TSize<int>, bool, std::_fl::function<void (sk_sp<flutter::DlImage> const&)>)::$_1::operator()() const
out/hyperos_release_arm64/../../flutter/shell/common/snapshot_controller_impeller.cc:170
Address: 0x000000000061c028, Function: std::_fl::__function::__value_func<void ()>::operator()[abi:nn210000]() const
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__functional/function.h:436
Address: 0x0000000000cc2084, Function: flutter::SnapshotControllerImpeller::MakeRasterSnapshot(sk_sp<flutter::DisplayList>, impeller::TSize<int>, bool, std::_fl::function<void (sk_sp<flutter::DlImage> const&)>)
out/hyperos_release_arm64/../../flutter/shell/common/snapshot_controller_impeller.cc:127
Address: 0x0000000000c8e870, Function: flutter::Rasterizer::MakeRasterSnapshot(sk_sp<flutter::DisplayList>, impeller::TSize<int>, bool, std::_fl::function<void (sk_sp<flutter::DlImage>)>)
out/hyperos_release_arm64/../../flutter/shell/common/rasterizer.cc:599
Address: 0x0000000000d04f60, Function: flutter::Picture::DoRasterizeToImage(sk_sp<flutter::DisplayList> const&, std::_fl::unique_ptr<flutter::LayerTree, std::_fl::default_delete<flutter::LayerTree>>, unsigned int, unsigned int, bool, _Dart_Handle*)::$_1::operator()()
out/hyperos_release_arm64/../../flutter/lib/ui/painting/picture.cc:249
Address: 0x0000000000619490, Function: std::_fl::__function::__value_func<void ()>::operator()[abi:nn210000]() const
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__functional/function.h:436
Address: 0x000000000061f90c, Function: fml::MessageLoopImpl::RunExpiredTasksNow()
out/hyperos_release_arm64/../../flutter/fml/message_loop_impl.cc:139
Address: 0x0000000000015dc8, Function: _binary_icudtl_dat_start
??:0
Address: 0x000000000002a64c, Function: _binary_icudtl_dat_start
??:0
Address: 0x000000000061fa54, Function: fml::MessageLoopAndroid::Run()
out/hyperos_release_arm64/../../flutter/fml/platform/android/message_loop_android.cc:67
Address: 0x000000000061d970, Function: fml::MessageLoopImpl::DoRun()
out/hyperos_release_arm64/../../flutter/fml/message_loop_impl.cc:94
Address: 0x000000000061d038, Function: std::_fl::__function::__value_func<void ()>::operator()[abi:nn210000]() const
out/hyperos_release_arm64/../../flutter/third_party/libcxx/include/__functional/function.h:436
Address: 0x000000000008cd74, Function: _binary_icudtl_dat_start
??:0
Address: 0x000000000007e39c, Function: _binary_icudtl_dat_start
??:0
Flutter Doctor output
Doctor output
Flutter 3.38
Android devices: Qualcomm Adreno GPU MTK Mali GPU
Affected devices observed: REDMI K PAD REDMI K90
Activity
mbcorona commentedon May 18, 2026
Hi @XyTechWorks , thanks for the detailed report.
Related to #184871 — same unbounded-wait ANR pattern, but a distinct call path.
Routing team-engine for further investigation.
gaaclarke commentedon May 18, 2026
p1 to investigate, this is an outlier in terms of behavior, maybe there is an alternative cleanup api we could add to flush this work so it isn't all jammed up at the end.
XyTechWorks commentedon May 29, 2026
Hello, may I ask if there has been any progress on this issue?
gaaclarke commentedon May 29, 2026
If I remember correctly, preliminary investigation showed that this usage was atypical and that we aren't experiencing a deadlock but rather a large amount of time cleaning up garbage. I didn't have a chance to investigate myself. You may be able to throttle your generation of garbage to clean up by spreading it across frames. This is a common technique that has to be done in gamedev with coroutines.
XyTechWorks commentedon Jun 25, 2026
Hello, We found a latent multithreading defect in Impeller's Vulkan backend: submissions to a VkQueue (vkQueueSubmit / vkQueuePresentKHR) are not mutually exclusive with device-wide waits (vkDeviceWaitIdle), which violates Vulkan's
external-synchronization requirements for queues.
Specifically:
As a result, when one thread runs vkDeviceWaitIdle while another thread issues vkQueueSubmit on the same graphics queue, both concurrently access the same queue — which is undefined behavior per the spec.
Trigger (platform-independent, reproducible with a single engine)
A typical concurrent pair:
These two run on different threads by design and can overlap without requiring any special feature such as multi-window.
Observed impact (on ARM Mali drivers)
Most GPU drivers tolerate this (technically illegal) concurrency without complaint, but the ARM Mali driver exposes it strictly, in two forms:
overwritten with unrelated data), crashing.
We have a tombstone from a Mali debug build whose built-in queue race detector directly printed the two thread IDs concurrently writing the same queue (one being the raster thread's device-wait-idle, the other the image-upload thread's submit),
independently confirming the race from the GPU side.
Conclusion
We believe this is a genuine external-synchronization defect in Impeller's Vulkan backend: vkDeviceWaitIdle must be mutually exclusive with submission/presentation on all queues of the device. The current implementation relies on the implicit
assumption that all GPU queue operations happen on a single raster thread, but image texture upload (ImageDecoderImpeller) runs on concurrent worker threads, which already breaks that assumption.
Proposed fix
Introduce a device-level reader/writer lock:
Alternatively, more surgically, avoid the full-device vkDeviceWaitIdle in DestroySwapchain and instead wait only on the swapchain's own fences (vkWaitForFences does not require queue external synchronization).
XyTechWorks commentedon Jun 25, 2026
1.raster (sysTid=14396) —— vkDeviceWaitIdle
#3-07 libGLES_mali.so osup_sync_object_timedwait ← 卡在 Mali 队列同步对象
#8 c7cc50 impeller::vk::Device::waitIdle() vulkan_funcs.hpp:974 (= vkDeviceWaitIdle)
#9 c7c9a4 impeller::KHRSwapchainImplVK::~KHRSwapchainImplVK() khr_swapchain_impl_vk.cc:289
#10 c813d8 shared_ptr 释放 → ~KHRSwapchainVK
#11 5c600c shared_ptr 释放 → SurfaceContextVK::releaseSwapChain / TeardownOnScreenContext
#12 5e5b84 flutter::PlatformViewAndroid::NotifyDestroyed()::$_0 platform_view_android.cc:326 ← raster 上执行的 teardown lambda
#13 621968 fml::MessageLoopImpl::FlushTasks
#14 627de4 fml::MessageLoopImpl::RunExpiredTasksNow message_loop_impl.cc:139
#15-16 libutils/libandroid Looper::pollOnce
#17 627f2c fml::MessageLoopAndroid::Run message_loop_android.cc:67
#18 625e48 fml::MessageLoopImpl::DoRun / MessageLoop::Run
#19 625510 fml::Thread 线程入口
io.worker.1 (sysTid=14402) —— 在做 vkQueueSubmit(图片上传)
#3-07 libGLES_mali.so osup_sync_object_wait ← 卡在 Mali 队列同步对象
#8 libvulkan.so vulkan::driver::QueueSubmit ← vkQueueSubmit
#9 c735ac impeller::vk::Queue::submit() vulkan_funcs.hpp:924
#10 c508f0 impeller::CommandQueueVK::Submit() command_queue_vk.cc:90
#11 d3ced0 flutter::ImageDecoderImpeller::UnsafeUploadTextureToPrivate() image_decoder_impeller.cc:1704
#12 d3dd68 flutter::ImageDecoderImpeller::UploadTextureToPrivate()::$_0 image_decoder_impeller.cc:1880
#13 624500 std::function 调用
#14 d3ad48 flutter::ImageDecoderImpeller::UploadTextureToPrivate() image_decoder_impeller.cc:1871
#15 d3a69c flutter::ImageDecoderImpeller::Decode()::$_1 lambda1 image_decoder_impeller.cc:2142
#16 d4a618 std::function 调用
#17 61e164 fml::ConcurrentMessageLoop::WorkerMain() concurrent_message_loop.cc:109 ← 并发 worker 线程
gaaclarke commentedon Jun 25, 2026
cc @jason-simmons #186573 (comment) has some good research that sounds plausible. I haven't dove into it much though, just an fyi since you have been looking at this too in other issues.
flutter-triage-bot commentedon Oct 29, 2026
This issue is marked P1 but has had no recent status updates.
The P1 label indicates high-priority issues that are at the top of the work list. This is the highest priority level a bug can have if it isn't affecting a top-tier customer or breaking the build. Bugs marked P1 are generally actively being worked on unless the assignee is dealing with a P0 bug (or another P1 bug). Issues at this level should be resolved in a matter of months and should have monthly updates on GitHub.
Please consider where this bug really falls in our current priorities, and label it or assign it accordingly. This allows people to have a clearer picture of what work is actually planned. Thanks!