Skip to content

fix: Purge cache on memory warning to prevent OOM crashes#3711

Open
mrousavy wants to merge 1 commit intoShopify:mainfrom
mrousavy:fix/purge-cache-on-memory-warning
Open

fix: Purge cache on memory warning to prevent OOM crashes#3711
mrousavy wants to merge 1 commit intoShopify:mainfrom
mrousavy:fix/purge-cache-on-memory-warning

Conversation

@mrousavy
Copy link
Contributor

@mrousavy mrousavy commented Feb 13, 2026

Purges the Skia cache (including unused GPU textures), as well as the Metal Texture Pool (used for VisionCamera/NativeBuffer APIs) when we receive a memory warning from iOS.

Before

The app crashes after ~5-10 seconds when stress testing Skia VisionCamera with 4k YUV 4:2:2 10-bit buffers, about the 3GB RAM mark:

image

After

The app runs fine, and purges the Skia/Metal GPU cache when it hits ~3GB RAM:

Screenshot 2026-02-13 at 17 36 42

Individual purges

You can see the individual cache purges here:
image

@mrousavy mrousavy marked this pull request as ready for review February 13, 2026 16:46
@mrousavy
Copy link
Contributor Author

mrousavy commented Feb 13, 2026

The reason why memory grows so rapidly is because of the same problem I highlighted in #3704 - static thread_local will not work for DispatchQueues/GCD. It literally creates a new MetalContext per OS-Thread, which can happen up to ~12 times per DispatchQueue - even though we only need 1. So 12x the memory worst case.
So when you use Skia from a DispatchQueue (= in VisionCamera), you'll end up with ~12 MetalContexts even though you only need 1.

And DispatchQueues are effectively the default on iOS, it only never occurred before because both the JS Thread and the UI Thread are exceptions to this; they use a single OS-Thread.

But on any DispatchQueue - which is the default with Cameras, Video, Multimedia, etc in AVFoundation, it won't work.

Long story short; as a follow-up PR we should eventually go away from caching MetalContext as static thread_local, and instead possibly pass it around all the time, and the owner could be each JS Runtime.

This is the original PR I had up 2 years ago to fix this same thread_local issue, but we need a proper fix: #2368

@terrysahaidak
Copy link
Contributor

@wcandillon
Copy link
Contributor

I understand the issue. Because you use a thread pool, the current way the Dispatcher work, it will never work as expected in this particular context.
Ganesh is very much not multithreaded but I could see how we could try to get away here.
Will ping you offline on potential solutions.

@mrousavy
Copy link
Contributor Author

Yep! Pretty much.

I don't explicitly use a Thread Pool though, this is just how GCD / DispatchQueue works on iOS. Also it is never "multithreaded" in a sense that it runs parallel - it is guaranteed to run serially. Still, the OS reuses Threads for each new call so static thread_local won't work.

My suggestion is to maybe cache everything per JS Runtime instead (jsi::Runtime::setData(...)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments