EXC_BAD_ACCESS on com.mapbox.threadpool: unsynchronized device-location-provider lookup in MapboxCommon when location authorization changes (24.32.0, 24.33.0)
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 35/100
Direzione di ricerca
Start with the authorization-change flow described in the issue: ConfigurationServiceWorker calls the device-location-provider lookup in MapboxCommon, which iterates the LocationService provider list. The issue provides no source files or tests to run, so first trace that call path in the SDK source if available. Done means concurrent authorization callbacks no longer race on the provider list; validate the fix with the supplied forced-interleaving procedure.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Environment
- Xcode version: 27.1 (27A9275)
- iOS version: iOS 27.0 Simulator (arm64), on macOS 26.6.2 and macOS 27.0.1 hosts
- Devices affected: iPhone 17 Pro Max Simulator. Not yet tried on a physical device (the app is pre-release), but nothing in the code path below is Simulator-specific.
- Maps SDK Version: 11.32.0 (MapboxCommon 24.32.0, MapboxCoreMaps 11.32.0) and 11.33.0 (MapboxCommon 24.33.0, MapboxCoreMaps 11.33.0), through Swift Package Manager
Observed behavior and steps to reproduce
The app crashes on a com.mapbox.threadpool thread right after location authorization changes, for example when the user taps "Allow While Using App" in the system prompt, while a map exists. The signal varies from run to run: EXC_BAD_ACCESS with SIGBUS or SIGSEGV, a garbage PC, __shared_weak_count::lock on freed memory, or a malloc abort ("pointer being freed was not allocated"). The crashed thread is always in the same MapboxCommon function (see below).
Setup:
- Create an
OfflineManager(). We keep one alive for style packs. - Show a
MapView(SwiftUIMap) with aPuck2Dfed throughmapView.location.dataModel. Positions come from the app's ownCLLocationManager; we use none of the SDK's other location features. - Call
requestWhenInUseAuthorization()on the app's ownCLLocationManager, and grant.
Without lldb it happens on about 2% of grants on the Simulator: 3 in ~133 grants on 24.32.0 and 1 in 20 on 24.33.0. The rate goes up when the host machine is loaded. Without an authorization change it never happens: 0 crashes in 10 launches without the prompt. It reproduces both on the first-launch prompt and on a prompt raised later from a "locate me" button.
Under lldb it reproduces every time with a forced thread interleaving, described below.
Expected behavior
No crash. Concurrent authorization callbacks should not race on the LocationService's provider list.
Notes / preliminary analysis
The crashed thread, as reported without a debugger. On 24.32.0 (MapboxCommon simulator slice UUID 65BB91DB-D516-35B8-9955-DE94EF3B52B8):
EXC_BAD_ACCESS SIGBUS KERN_PROTECTION_FAILURE queue: com.mapbox.threadpool
0 MapboxCommon 0x2b01c4
1 MapboxCommon 0x2b53c0
2 MapboxCommon 0x1859d4
3 MapboxCommon 0x1855ec
4 MapboxCommon 0x1858a0
5 MapboxCommon 0x21f284
6 MapboxCommon 0x1bacdc
7 libdispatch.dylib _dispatch_call_block_and_release
8 libdispatch.dylib _dispatch_client_callout
9 libdispatch.dylib _dispatch_continuation_pop
10 libdispatch.dylib _dispatch_async_redirect_invoke
11 libdispatch.dylib _dispatch_root_queue_drain
12 libdispatch.dylib _dispatch_worker_thread2
13 libsystem_pthread.dylib _pthread_wqthread
14 libsystem_pthread.dylib start_wqthread
Frame 0 also shows up at +0x2b0100, +0x2b0148, +0x2b01d4 and +0x2b0680 in other reports. Frames 1 to 6 are always the same.
On 24.33.0 (UUID 1E3BAE8C-9719-3BF2-A1CE-407DC1FD9862) it is the same path at new addresses: 0x2b1d68 (or 0x2b1ca4, 0x2b1cec, 0x2b1d78), then 0x2b6f64, 0x18559c, 0x1851b4, 0x185468, 0x21f32c, 0x1ba8d8.
What those functions are. No dSYM ships, so we identified them by the log strings they reference. Offsets are given as 24.32.0 / 24.33.0:
0x185468/0x185030logsonPermissionStatusChanged to {}. This is theConfigurationServiceWorkerpermission handler.0x18593c/0x185504logsFailed to obtain device location provider.0x2b5364/0x2b6f08logsLocation service is already destroyed, then calls:0x2b008c/0x2b1c30, the device-location-provider lookup. It iterates and compacts a vector of{variant, weak_ptr}elements held atthis+0x48(begin, end and capacity at+0x48,+0x50and+0x58). No lock is taken anywhere in the function.
The lookup and its callers are instruction-for-instruction identical in 24.32.0 and 24.33.0.
Who calls it concurrently. On every authorization change, onPermissionStatusChanged runs once per ConfigurationServiceWorker, each on its own com.mapbox.threadpool thread, at the same time. Each handler reaches the lookup on the same LocationService. Our app has up to three workers; lldb shows these exported names:
- one from
ConfigurationService::getOrCreate, called from MapboxCoreMaps'OfflineManager::OfflineManager(); - two from
getOrCreateIfNotCleanedUp, called from theMapView'sEventsManager.
Launching with -MGLMapboxMetricsEnabled NO still leaves three workers. Between one and three handlers run on a given grant. With only one there is nothing to race, which fits the low natural rate.
Forced interleaving. We set breakpoints on the permission handler, on the lookup's loop body and on the lookup's return into its caller. When two or more handlers are running, the first one to enter the loop is suspended, and it is resumed once the others have returned. While it is held, the vector moves to a new buffer, and the held thread then crashes. Here is a 24.33.0 run with three handlers:
permission t15 (1)
permission t36 (2)
permission t4 (3)
held t15 at element 0x11e9ee300; vector [0x11e9ee300,0x11e9ee380) cap 0x11e9ee380
t4 returned (1 of 2)
t36 returned (2 of 2)
vector now [0x124306c00,0x124306d80) cap 0x124306e00
resumed t15
* thread #15, queue = 'com.mapbox.threadpool', stop reason = EXC_BAD_ACCESS (code=1, address=0x9343fd0954001780)
* frame #0: 0x00007d0954001780
frame #1: MapboxCommon`___lldb_unnamed_symbol_2b1c30 + 328
frame #2: MapboxCommon`___lldb_unnamed_symbol_2b6f08 + 92
frame #3: MapboxCommon`___lldb_unnamed_symbol_185504 + 152
frame #4: MapboxCommon`___lldb_unnamed_symbol_185030 + 388
frame #5: MapboxCommon`___lldb_unnamed_symbol_185314 + 340
frame #6: MapboxCommon`___lldb_unnamed_symbol_21eddc + 1360
frame #7: MapboxCommon`___lldb_unnamed_symbol_1ba66c + 620
frame #8: libdispatch.dylib`_dispatch_call_block_and_release + 24
Results:
| 24.32.0 | 24.33.0 | |
|---|---|---|
| Forced interleaving: crashed launches, of those where 2 or more handlers ran | 7 of 7 | 9 of 9 |
| Control: same breakpoints, but each handler waits for the previous one to return | 0 of 2 | 0 of 4 |
The lldb script (24.33.0 simulator offsets; attach with process attach --pid, then command script import force_race33.py and continue)
"""Only the threads that ran onPermissionStatusChanged (fn 0x185030) take
part. The first of them inside the device-provider lookup loop (fn 0x2b1c30,
loop body 0x2b1cb4) is suspended; once every other permission thread has
returned to fn 0x2b6f08 (0x2b6f64), the vector's bounds are read again and
the held thread resumes. x19 is `this` and x26 the current element at the
loop body, in this build."""
import time
import lldb
STATE = {"perm": set(), "held": None, "this": None, "returned": set(),
"resumed": False}
def _say(msg):
print("RACE %.3f %s" % (time.time(), msg), flush=True)
def _bounds(process, this):
err = lldb.SBError()
return tuple(process.ReadPointerFromMemory(this + off, err)
for off in (0x48, 0x50, 0x58))
def on_permission(frame, bp_loc, extra, internal_dict):
thread = frame.GetThread()
if STATE["held"] is None:
STATE["perm"].add(thread.GetThreadID())
_say("permission t%d (%d)" % (thread.GetIndexID(), len(STATE["perm"])))
return False
def in_loop(frame, bp_loc, extra, internal_dict):
thread = frame.GetThread()
tid = thread.GetThreadID()
if STATE["held"] is None and tid in STATE["perm"] and len(STATE["perm"]) >= 2:
this = frame.FindRegister("x19").GetValueAsUnsigned()
x26 = frame.FindRegister("x26").GetValueAsUnsigned()
STATE["held"], STATE["this"] = tid, this
thread.Suspend()
_say("held t%d at element 0x%x; vector [0x%x,0x%x) cap 0x%x" % (
(thread.GetIndexID(), x26) + _bounds(thread.GetProcess(), this)))
return False
def returned(frame, bp_loc, extra, internal_dict):
thread = frame.GetThread()
tid = thread.GetThreadID()
held = STATE["held"]
if held is None or STATE["resumed"] or tid == held or tid not in STATE["perm"]:
return False
STATE["returned"].add(tid)
_say("t%d returned (%d of %d)" % (thread.GetIndexID(), len(STATE["returned"]),
len(STATE["perm"]) - 1))
if len(STATE["returned"]) >= len(STATE["perm"]) - 1:
process = thread.GetProcess()
_say("vector now [0x%x,0x%x) cap 0x%x" % _bounds(process, STATE["this"]))
for t in process:
if t.GetThreadID() == held:
t.Resume()
STATE["resumed"] = True
_say("resumed t%d" % t.GetIndexID())
return False
def __lldb_init_module(debugger, internal_dict):
for addr, fn in ((0x185030, "on_permission"), (0x2b1cb4, "in_loop"),
(0x2b6f64, "returned")):
debugger.HandleCommand(
"breakpoint set -s MapboxCommon -a 0x%x -N race33_%s" % (addr, fn))
debugger.HandleCommand(
"breakpoint command add -F force_race33.%s race33_%s" % (fn, fn))
Question. Is there a supported way to avoid this until a fix ships? Creating the OfflineManager later would only remove one of the three workers. The MapView's two would remain.
We're happy to run a fix candidate under the same forced interleaving.
Additional links and references
These look different but are also crashes on authorization changes: #2236 (closed as stale: onAccuracyAuthorizationChanged on com.mapbox.common.location.sharedRunloop) and #2260 (EXC_BAD_ACCESS on a Mapbox worker thread with "Always" permission).
- Lingua principale
- Swift
- Stelle
- 601
- Fork
- 198
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di mapbox/mapbox-maps-ios
-
auto-triaged bug :beetle: p3 SwiftUI
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
mapbox/mapbox-maps-ios#2452 · 6 reazioni ·
-
auto-triaged bug :beetle: p2
Difficoltà 4/5 3-5 giorni Idoneità per principianti 42/100
mapbox/mapbox-maps-ios#2451 ·
-
auto-triaged bug :beetle: needs info p3 performance :zap:
Difficoltà 5/5 Più di una settimana Idoneità per principianti 22/100
mapbox/mapbox-maps-ios#2450 · 1 commento ·
-
auto-triaged bug :beetle: p2
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
mapbox/mapbox-maps-ios#2448 ·
-
auto-triaged bug :beetle: p2 performance :zap:
Difficoltà 3/5 1-2 giorni Idoneità per principianti 69/100
mapbox/mapbox-maps-ios#2447 ·
Tutte le issue di mapbox/mapbox-maps-ios
Issue simili
-
Seed a JRNYAx price before the last close so asset-detail S1.3 doesn't fail from 08:00Z to the openApertabug tests
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
lognorman20/monaco#7231 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 5 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
Provenance-Emu/SkinKit#42 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
lwouis/alt-tab-macos#6154 ·
-
bug mac アプリ: コード アプリ: チャット 優先: 中 要トリアージ
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
yksr-melt/Meltype#1346 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno