Skip to content

[Bug] iOS New Arch: ShapeAnimator TurboModules call updateGeoJSONSource off-main → MapboxMaps StyleSourceManager Dictionary corruption (FATAL) #4248

Description

@Cruncho420

Environment

  • @rnmapbox/maps: 10.2.10 (also verified present in 10.3.2 sources)
  • MapboxMaps iOS: 11.16.6
  • React Native: 0.81 (Expo 54), New Architecture (Fabric) enabled
  • iOS 26.5, iPhone14,3, real device, debug + confirmed race also fatal in release-class conditions

Summary

MovePointShapeAnimator (and ChangeLineOffsetsShapeAnimator) mutate the MapboxMaps style from a background thread under the New Architecture, corrupting MapboxMaps' internal StyleSourceManager.WorkItemPerGeoJSONSourceTracker Swift Dictionary and crashing the app (FATAL). MapboxMaps' style/source API is main-thread-only (mapbox/mapbox-maps-ios#1322); every other RNMBX module already hops to main (RNMBXViewResolver / addUIBlock), but the shape-animator TurboModules do not: methodQueue { return RCTGetUIManagerQueue(); } is ignored for TurboModule invocations, so moveTo runs on a background dispatch queue and synchronously calls ShapeAnimatorCommon.refresh()shapeUpdatedStyle.updateGeoJSONSource off-main — racing (a) the animator's own main-thread CADisplayLink refresh and (b) main-thread Fabric shape.didset updates.

Evidence — two fully-symbolicated on-device crash reports, same dictionary, opposite threads

Crash 1 — MAIN thread (Fabric prop update side of the race):

5  libswiftCore   Dictionary._Variant.removeValue(forKey:)
6  MapboxMaps     StyleSourceManager.WorkItemPerGeoJSONSourceTracker.cancelAll(for:)
7  MapboxMaps     StyleSourceManager.updateGeoJSONSource(withId:data:dataId:)
...
21 RNMBX          RNMBXShapeSource.shape.didset
24 RNMBX          -[RNMBXShapeSourceComponentView updateProps:oldProps:]
25 React          RCTPerformMountInstructions   (main thread)

NSInvalidArgumentException: -[NSIndexPath objectForKey:]: unrecognized selector sent to instance 0x8000000000000000 (poisoned pointer out of the corrupted Dictionary).

Crash 2 — BACKGROUND TurboModule queue (animator side of the race):

5  libswiftCore   Dictionary._Variant.setValue(_:forKey:)
6  libswiftCore   Dictionary.subscript.setter
7  MapboxMaps     StyleSourceManager.WorkItemPerGeoJSONSourceTracker.add(_:for:)
8  MapboxMaps     StyleSourceManager.updateGeoJSONSource(withId:data:dataId:)
...
16 RNMBX          RNMBXShapeSource.doUpdate(_:)
19 RNMBX          ShapeAnimatorCommon.refresh()
23 RNMBX          MovePointShapeAnimator.moveTo(coordinate:durationSec:)
26 RNMBX          -[RNMBXMovePointShapeAnimatorModule moveTo:coordinate:duration:resolve:reject:]
31 React          facebook::react::ObjCTurboModule::performMethodInvocation
...on _dispatch_workloop_worker_thread (background)

EXC_BAD_ACCESS KERN_INVALID_ADDRESS (possible pointer authentication failure).

Reproduction context: navigation app animating a point ShapeSource via __experimental.MovePointShapeAnimator.moveTo(...) per GPS tick (~6-10Hz) while other declarative ShapeSources update. Intermittent (seconds→minutes to crash); frequency scales with update volume.

Fix that works for us (patch-package, happy to PR)

  1. ShapeAnimatorCommon.refresh() — main-thread guard (deepest chokepoint, covers all animators):
guard Thread.isMainThread else {
  DispatchQueue.main.async { [weak self] in self?.refresh() }
  return
}
  1. RNMBXMovePointShapeAnimatorModule.swift static moveTo — wrap body in DispatchQueue.main.async (with resolve: @escaping RCTPromiseResolveBlock), so the animator's AnimatableElement state writes can't race the 60fps main-thread displayLink reads (Swift exclusivity traps this in debug).
  2. ShapeAnimatorManager.register — same main-thread guard (generate registers from the background queue while get(shape:) reads on main).

Ordering stays correct (main queue FIFO), latency cost ≤1 runloop turn per moveTo, no ABI change. Related upstream reports of the same crash class: mapbox/mapbox-maps-ios#2282, mapbox/mapbox-maps-ios#2195.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions