Skip to content

fix(ios): dispatch Dictionary access to main thread in offline controllers#1119

Open
mateusztylman wants to merge 1 commit intomapbox:mainfrom
mateusztylman:fix/tile-store-thread-safety
Open

fix(ios): dispatch Dictionary access to main thread in offline controllers#1119
mateusztylman wants to merge 1 commit intomapbox:mainfrom
mateusztylman:fix/tile-store-thread-safety

Conversation

@mateusztylman
Copy link
Copy Markdown

@mateusztylman mateusztylman commented Apr 1, 2026

Summary

Fix thread-unsafe Dictionary access in TileStoreController.swift and OfflineController.swift that causes EXC_BAD_ACCESS (SIGSEGV) crashes when downloading tile regions or style packs.

Fixes #1116

Problem

loadTileRegion, estimateTileRegion, and loadStylePack use progress and completion closures that are called on MapboxCommon background threads. Both closures access shared handler dictionaries without synchronization:

  • Progress callbacks: read from the dictionary on a background thread
  • Completion callbacks: removeValue(forKey:) runs on a background thread (executeOnMainThread only wrapped the completion call, not the removeValue that followed)

Swift's Dictionary is not thread-safe for concurrent read+write. When multiple regions/packs have overlapping lifetimes, this causes crashes.

Fix

Wrap both progress and completion closure bodies in DispatchQueue.main.async so all dictionary access is serialized on the main thread. This also ensures Flutter event sink calls happen on the main thread, consistent with Flutter's platform channel contract.

Files changed:

  • TileStoreController.swift - loadTileRegion and estimateTileRegion
  • OfflineController.swift - loadStylePack

Crash signatures (from production)

// Crash 1 - concurrent read during mutation
Thread 29: specialized __RawDictionaryStorage.find<A>(_:)
           specialized Dictionary.removeValue(forKey:) (TileStoreController.swift:33)

// Crash 2 - CoW uniqueness check on freed backing storage
Thread 23: swift_isUniquelyReferenced_nonNull_native
           specialized Dictionary.removeValue(forKey:) (TileStoreController.swift:33)

…llers

Wrap progress and completion closures in DispatchQueue.main.async to
prevent concurrent read/write access to handler dictionaries from
MapboxCommon background threads, which caused EXC_BAD_ACCESS (SIGSEGV).

Affected files:
- TileStoreController.swift: tileRegionLoadProgressHandlers,
  tileRegionEstimateProgressHandlers
- OfflineController.swift: progressHandlers

Fixes mapbox#1116
@mateusztylman mateusztylman requested a review from a team as a code owner April 1, 2026 14:59
@mateusztylman mateusztylman requested a review from evil159 April 1, 2026 14:59
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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.

iOS: Thread-unsafe Dictionary access in TileStoreController causes SIGSEGV crash

2 participants