You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[0.15.0-dev.1] iOS: unbounded Metal object growth on the deferred replay thread → OOM (suspected missing @autoreleasepool) #655
I have confirmed the issue is present in the latest version of the rive Flutter package
I have searched the documentation and forums and could not find an answer
I have searched existing issues and this is not a duplicate
Description
Symptom
After upgrading to rive 0.15.0-dev.1 / rive_native 0.2.0-dev.1, memory climbs steadily
on iOS until the app is jetsammed (~1.5 GB). Pinning back to rive 0.14.10 /
rive_native 0.1.10, no other change: flat.
Device: iPhone 13 Pro Max, iOS 26.6. Instruments 26.6, Leaks template, 134 s trace.
Measurement
At trace end, one thread holds 490 MiB in 318,340 live objects — all Metal
render-pass/encoder objects, zero textures:
4,865 MTLDebugRenderCommandEncoder (478 MB — 96 KiB each; Metal API Validation was
on, so the byte figures are inflated, but the object counts should hold without it)
14,596 MTLRenderPassDescriptorInternal + 121,626 of its attachment children
Leaks instrument: zero leaks. The objects are still reachable — retention, not
an orphaned allocation.
That thread has 181 live autorelease-pool pages. Every other thread has 1.
All 318,340 objects were allocated between t=3.2 s and t=61.5 s. At t=134 s none
had been freed — 73 s after the thread stopped allocating. ~12 MiB/s while rendering.
Suspected cause
The accumulating classes are exactly the objects ContextMetal autoreleases per frame
— [m_mtlQueue commandBuffer], [MTLRenderPassDescriptor renderPassDescriptor], renderCommandEncoderWithDescriptor: — plus their owned attachment descriptors. With
181 undrained pool pages on that one thread, this looks like the deferred replay thread
new in 0.2.0-dev.1 running its Metal work with no @autoreleasepool, so everything
sits in the thread's top-level pool.
The only @autoreleasepool calls in the shipped sources are in path_fiddle, headless_rendering.mm, and a completion handler at rive_native_plugin.mm:280 —
none covering recording or replay. The replay loop is in deferred_binding.cpp, a .cpp, so it can't hold one.
Possible fix (untested): wrap each replayed frame in @autoreleasepool; ContextMetal::beginFrame/endFrame in ore_context_metal.mm look like the seam.
Analysis was AI-assisted. Happy to dig deeper or share the trace if that helps.
It looks like this issue was probably already fixed in dev-2. I’m currently on dev-3 and haven’t experienced any memory issues so far, so I think we can close this for now.
Activity
lawinski commentedon Oct 6, 2026
It looks like this issue was probably already fixed in
dev-2. I’m currently ondev-3and haven’t experienced any memory issues so far, so I think we can close this for now.