Skip to content

Memory Leak: iOS Layer Modifications Off Main Thread #244

@TrebuhD

Description

@TrebuhD

Memory Leak: iOS Layer Modifications Off Main Thread

Summary

react-native-wgpu causes continuous memory leaks on iOS due to WebGPU operations (layer modifications) happening off the main thread. Memory usage climbs indefinitely at ~6-10MB per minute during rendering.

Environment

  • react-native-wgpu: 0.2.1
  • Platform: iOS (Physical device - iPhone 15 Pro)
  • React Native: 0.74.6 (Expo SDK 53)
  • iOS Version: 18.x

Issue Description

WebGPU rendering operations trigger iOS warnings about modifying view layers off the main thread, causing memory to accumulate without being garbage collected. The leak occurs even with minimal rendering (empty scene with no compute shaders).

Error Message

Modifying properties of a view's layer off the main thread is not allowed: 
view <MetalView: 0x11c558000> with nearest ancestor view controller <RNSScreen: 0x1073ab200>

Stack trace points to:
dawn::native::metal::SwapChain::Initialize
dawn::native::metal::Surface::Configure

Reproduction Steps

  1. Create a basic WebGPU render loop:
const ref = useCanvasEffect(async () => {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();
  const context = ref.current!.getContext('webgpu')!;
  
  context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
    alphaMode: 'premultiplied',
  });

  const frame = () => {
    const texture = context.getCurrentTexture();
    const commandEncoder = device.createCommandEncoder();
    
    const renderPass = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: texture.createView(),
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
        loadOp: 'clear',
        storeOp: 'store',
      }],
    });
    
    renderPass.end();
    device.queue.submit([commandEncoder.finish()]);
    
    requestAnimationFrame(frame);
  };
  
  requestAnimationFrame(frame);
});
  1. Run the app on a physical iOS device
  2. Monitor memory usage in Xcode
  3. Observe continuous memory growth (~6-10MB/minute)

Root Cause

The WebGPU operations are executing on the React Native JavaScript thread instead of the main UI thread. iOS requires all layer/view modifications to happen on the main thread. Key problematic operations:

  1. context.configure() - Configures the Metal swap chain
  2. context.getCurrentTexture() - Gets texture from swap chain
  3. device.queue.submit() - Submits commands to GPU
  4. Any buffer mapping operations (buffer.mapAsync())

Impact

  • Memory Growth: 6-10MB per minute during active rendering
  • Performance Degradation: GPU frame time doubles after prolonged use
  • Battery Drain: Device heats up due to memory pressure
  • App Crashes: Eventually crashes due to memory exhaustion

Attempted Workarounds (Unsuccessful)

  1. ✅ Wrapping context.configure() in requestAnimationFrame() - Partial fix, doesn't solve render loop
  2. ❌ Periodic buffer recreation/destruction - Doesn't address core issue
  3. ❌ Reducing frame rate - Slows leak but doesn't eliminate it
  4. ❌ Manual garbage collection hints - Native memory not accessible from JS

Expected Behavior

WebGPU operations should be dispatched to the main thread on iOS to comply with UIKit requirements and prevent memory leaks.

Suggested Fix

The library needs to dispatch WebGPU operations to the main thread on iOS:

// In native iOS code
dispatch_async(dispatch_get_main_queue(), ^{
    // WebGPU/Metal operations here
});

Specifically for:

  • SwapChain configuration
  • Texture acquisition
  • Command submission
  • Buffer mapping operations

Additional Context

  • The issue does not occur in simulators (different threading model)
  • The issue is specific to iOS (UIKit's main thread requirements)
  • Memory leak occurs even without any compute shaders or complex rendering
  • The leak accumulates in native memory (not visible in JS heap snapshots)

Profiler Evidence

Instruments trace showing the issue originates in Dawn's Metal implementation when configuring the swap chain from a background thread. The memory accumulates in Core Animation layers that aren't properly released.

Related Issues

This may be related to iOS's strict main thread requirements for UI operations, similar to issues seen in other graphics libraries when not properly dispatching to the main thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions