-
Notifications
You must be signed in to change notification settings - Fork 230
RiveNativePlugin crash during app termination — Metal command queue dealloc use-after-free #623
Description
Submission checklist
- I have confirmed the issue is present in the latest version of the
riveFlutter 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
The RiveNativePlugin crashes during iOS app termination with a use-after-free in MTLResourceListPool.purge, triggered by the auto-generated .cxx_destruct releasing Metal ivars after the GPU context is partially torn down.
The crash occurs even when no Rive animation has been loaded or displayed — the plugin eagerly creates a
MTLDevice and MTLCommandQueue during registerWithRegistrar: → initWithTextures: (lines 211-212 of
rive_native_plugin.mm), so the Metal resources exist from app startup.
The issue: In dealloc (line 226), the plugin destroys the renderer context and clears GPU pointers. But
the Objective-C++ auto-generated .cxx_destruct runs after dealloc returns, releasing _metalDevice and
_metalCommandQueue ivars. By that point during system termination, the Metal device may already be
partially torn down, and the command queue's dealloc → MTLResourceListPool.purge accesses freed memory.
Expected behavior: App terminates cleanly without crash.
Reproduction steps / code
No special code needed — this crashes during normal app termination on iOS. The crash is intermittent and depends on system teardown ordering.
No Rive animation needs to be loaded.
The crash happens because RiveNativePlugin eagerly creates Metal resources on plugin registration (app startup), and the teardown order during app termination is unsafe.
rive_native_plugin.mm lines 211-212 (in initWithTextures:):
_metalDevice = MTLCreateSystemDefaultDevice();
_metalCommandQueue = [_metalDevice newCommandQueue];
.cxx_destruct releases these AFTER dealloc, when GPU may be torn down
Nil out _metalCommandQueue and _metalDevice explicitly in dealloc before the method
returns, so .cxx_destruct has nothing to release:
Upload your reproduction files / stack trace
Source .riv / .rev file
No response
Screenshots / video
No response
Rive Flutter package version
0.14.4
Flutter version
- rive: 0.14.4
- rive_native: 0.1.4 (transitive)
- iOS 18.x, iPhone (AGXMetalG14 = A15/A16 GPU)
- Flutter 3.35.7
Device
iPhone 13
OS version
iOS 18.6.2 and iOS 26.3.1
Additional context
Potential fix: Nil out _metalCommandQueue and _metalDevice explicitly in dealloc before the method
returns, so .cxx_destruct has nothing to release:
-
(void)dealloc
{
riveLock();
if (_riveRendererContext != nullptr)
{
destroyRiveRendererContext(_riveRendererContext);
_riveRendererContext = nullptr;
}
setGPU(nullptr, nullptr);
riveUnlock();// Release Metal resources explicitly before .cxx_destruct runs,
// to avoid use-after-free during system termination teardown.
_metalCommandQueue = nil;
_metalDevice = nil;
}