From af833d310b86235f2d47b3d2df29e1ea9f7287ff Mon Sep 17 00:00:00 2001 From: Dave Craig Date: Tue, 15 Sep 2026 09:32:31 +0100 Subject: [PATCH 1/2] Disable Xcode's Metal instrumentation in the iosApp scheme Metal API Validation and GPU frame capture deadlock the app. MetalTools and GPUToolsCapture inject objc retain traffic into Compose's render thread, which then blocks on the objc sidetable lock while the main thread holds the objc weak-table lock inside MapLibre's draw (objc_loadWeakRetained on a Kotlin ObjCBackRef) and waits for a Kotlin/Native GC that can never suspend the render thread. Three-way lock inversion with no timeout, so the UI never recovers. Set in project.yml rather than Xcode's scheme editor: xcodegen omits both attributes by default, so regenerating the project silently restores them. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011r7YyswsqGBpg3mYrEyiTV --- iosApp/project.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/iosApp/project.yml b/iosApp/project.yml index bc81047de..ce88e4e82 100644 --- a/iosApp/project.yml +++ b/iosApp/project.yml @@ -270,6 +270,15 @@ schemes: iosAppTests: [ test ] run: config: Debug + # Xcode's Metal instrumentation deadlocks the app. MetalTools/GPUToolsCapture + # inject objc retain traffic into Compose's render thread, which then blocks on + # the objc sidetable lock while the main thread holds the objc weak-table lock + # inside MapLibre's draw (objc_loadWeakRetained on a Kotlin ObjCBackRef) and + # waits for a Kotlin/Native GC that can never suspend the render thread. Set + # here rather than in Xcode's scheme editor because xcodegen regenerates the + # scheme and omits both attributes by default, silently restoring them. + enableGPUFrameCaptureMode: disabled + enableGPUValidationMode: disabled test: config: Debug targets: From c886e4fab7c60ed94167f0c8ce29f59b438aa0e7 Mon Sep 17 00:00:00 2001 From: Dave Craig Date: Tue, 15 Sep 2026 09:54:04 +0100 Subject: [PATCH 2/2] Link with -ObjC and pin Firebase to 12.19.0 Fixes "NSInvalidArgumentException -[APMMeasurement fetchSBT]: unrecognized selector" crashes in release builds. GoogleAppMeasurement ships its Objective-C categories in object files that each export a dummy _APMIncludeCategory symbol, referenced from APMMeasurement.o, so the linker keeps them under normal static archive semantics without needing -ObjC. 12.19.0 added APMMeasurement+SBT.o and left out the anchor: it defines nothing any other member references (only block descriptors and ARC helpers), so the linker drops it, while APMMeasurement.o still sends fetchSBT from startMeasurementOnWorkerQueue, updateSchedule, uploadData and networkRemoteConfigFetchCompletionHandler:data:error:. Confirmed by partial-linking the 12.19.0 archive: 0 APMMeasurement(SBT) methods without -ObjC, all 5 with it. The flag went into OTHER_LDFLAGS rather than the OTHER_LINKER_FLAGS block that was already there, because OTHER_LINKER_FLAGS is not a build setting Xcode recognises and was being silently dropped. Verified with a throwaway project: -lnosuchlib under OTHER_LINKER_FLAGS builds fine, under OTHER_LDFLAGS fails to link. The "-framework Shared" it contained was therefore inert too, which went unnoticed because `import Shared` makes Swift autolink the framework anyway. Firebase is now pinned exactly. `from: 12.18.0` is a >=12.18.0 <13.0.0 range and Package.resolved isn't committed (it lives inside the generated, gitignored .xcodeproj), so CI resolved whatever was newest at build time. That is how a release shipped on GoogleAppMeasurement 12.19.0 while local builds were still on 12.18.0 - where nothing sends fetchSBT, so the crash could not be reproduced here at all. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011r7YyswsqGBpg3mYrEyiTV --- iosApp/project.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/iosApp/project.yml b/iosApp/project.yml index ce88e4e82..fa968dafb 100644 --- a/iosApp/project.yml +++ b/iosApp/project.yml @@ -28,8 +28,13 @@ packages: url: https://github.com/realm/realm-swift from: 20.0.4 Firebase: + # Pinned exactly, like MapLibre above. `from:` is a >=12.18.0 <13.0.0 range, and + # Package.resolved isn't committed (it lives inside the generated, gitignored + # .xcodeproj), so CI resolved whatever was newest at build time. A release shipped + # on GoogleAppMeasurement 12.19.0 while local builds were still on 12.18.0, which + # is how the -ObjC crash below reached users without reproducing for anyone here. url: https://github.com/firebase/firebase-ios-sdk - from: 12.18.0 + version: 12.19.0 targets: iosApp: type: application @@ -141,8 +146,21 @@ targets: FRAMEWORK_SEARCH_PATHS: - "$(inherited)" - "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)" - OTHER_LINKER_FLAGS: + # OTHER_LDFLAGS, not OTHER_LINKER_FLAGS: the latter isn't a build setting Xcode + # recognises, so everything in it was silently dropped. `-framework Shared` was + # inert but harmless, because `import Shared` makes Swift autolink the framework + # anyway. -ObjC was not harmless - see below. + OTHER_LDFLAGS: - "$(inherited)" + # GoogleAppMeasurement ships its Objective-C categories in object files that + # export a dummy _APMIncludeCategory symbol, referenced from + # APMMeasurement.o, so the linker keeps them without -ObjC. 12.19.0 added + # APMMeasurement+SBT.o and forgot the anchor: it defines nothing any other + # member references, so the linker drops it, and the -[self fetchSBT] calls in + # APMMeasurement.o (startMeasurementOnWorkerQueue, updateSchedule, uploadData, + # networkRemoteConfigFetchCompletionHandler:data:error:) become + # "NSInvalidArgumentException -[APMMeasurement fetchSBT]: unrecognized selector". + - "-ObjC" - "-framework" - "Shared" CODE_SIGN_ENTITLEMENTS: iosApp/iosApp.entitlements