Mobile Development 17 min read

Analyzing and Resolving a Main Thread ↔ JavaScriptCore Heap Collector Thread Deadlock on iOS 13

This article investigates a deadlock between the Main Thread and the JavaScriptCore Heap Collector Thread on iOS 13, explains the underlying mutex and RunLoop interactions, reproduces the issue with CFTimer‑based timers, and proposes an AutoReleasePool‑wrapped solution along with best‑practice recommendations for JavaScriptCore usage.

Kuaishou Tech
Kuaishou Tech
Kuaishou Tech
Analyzing and Resolving a Main Thread ↔ JavaScriptCore Heap Collector Thread Deadlock on iOS 13

The problem originates from a high‑frequency timer implemented with CoreFoundation's CFTimer that creates JSValue objects on the main thread; when the timer fires, the main thread’s RunLoop executes __CFRunLoopDoTimers , triggers an autorelease pool, and attempts to release JSValue objects while the JavaScriptCore Heap Collector Thread is simultaneously performing garbage collection.

Analysis of stack traces shows that both the Main Thread and the JSC Heap Collector Thread contend for the same mutex inside the main RunLoop, satisfying the four classic deadlock conditions (mutual exclusion, hold‑and‑wait, no‑preemption, circular wait). The JSC thread holds the lock while waiting for the main thread to finish its autorelease work, and the main thread waits for the JSC thread’s GC to release the lock, creating a circular wait.

Key observations include:

The issue appears only on iOS 13 because its __CFRunLoopDoTimers contains an extra autorelease‑pool step not present in other versions.

Using NSTimer does not reproduce the deadlock because its callback is already wrapped in an autorelease pool.

The deadlock can be reliably reproduced by a demo that schedules a high‑frequency CFTimer which repeatedly creates JSValue objects.

Solution: wrap the timer callback code in an explicit @autoreleasepool block so that JSValue objects are released before the RunLoop reaches the critical section, breaking the circular wait. The article provides pseudo‑code illustrating this fix.

Additional recommendations for safe JavaScriptCore usage on mobile include:

Encapsulate short‑lived JSContext ‑related objects in autorelease pools.

Use JSManagedValue for long‑lived references.

Access the current context via [JSContext currentContext] .

Avoid cross‑thread VM interactions; keep a single VM per thread.

Be aware that other RunLoop‑related APIs (e.g., performSelector , CFRunLoopPerformBlock ) can also trigger similar lock contention.

iOSMemory ManagementdeadlockRunLoopthreadingJavaScriptCoreTimer
Kuaishou Tech
Written by

Kuaishou Tech

Official Kuaishou tech account, providing real-time updates on the latest Kuaishou technology practices.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.