From 10017714e5e8a7c5faa85b5d3bfb0bdbe77d426e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 22 Jun 2026 08:29:33 +0300 Subject: [PATCH] Fix libdispatch main-thread trap when logging Sentry crash events Sentry's beforeSend runs on a background queue, but Event.crashLog read UIDevice.current there, which carries a main-thread dispatch assertion and trapped libdispatch. Read the device values on the main thread at setup and pass them into the now-nonisolated crashLog. --- ElementX/Sources/Application/AppCoordinator.swift | 7 +++++-- ElementX/Sources/Other/Extensions/SentryEvent.swift | 9 +++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 237fcee5a..48111b030 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -1020,8 +1020,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg } // Mirror every crash into our own logs (which ship with rageshakes) before it's sent to Sentry. - options.beforeSend = { event in - if let crashLog = event.crashLog { + // Device values are read here on the main thread; beforeSend runs on a background queue. + let deviceModel = UIDevice.current.model + let systemVersion = UIDevice.current.systemVersion + options.beforeSend = { @Sendable event in + if let crashLog = event.crashLog(deviceModel: deviceModel, systemVersion: systemVersion) { MXLog.error("Sentry crash event \(event.eventId.sentryIdString):\n\(crashLog)") } return event diff --git a/ElementX/Sources/Other/Extensions/SentryEvent.swift b/ElementX/Sources/Other/Extensions/SentryEvent.swift index b15d10cfe..093c85530 100644 --- a/ElementX/Sources/Other/Extensions/SentryEvent.swift +++ b/ElementX/Sources/Other/Extensions/SentryEvent.swift @@ -8,12 +8,14 @@ import MatrixRustSDK import Sentry -import UIKit extension Event { /// A human readable crash log matching the format used by the legacy app. /// `nil` for non-crash events (handled errors, transactions, etc.). - var crashLog: String? { + /// + /// Device values must be read on the main thread and passed in. `beforeSend` + /// runs on a background queue and touching `UIDevice.current` there traps libdispatch. + nonisolated func crashLog(deviceModel: String, systemVersion: String) -> String? { guard let exceptions, exceptions.contains(where: { $0.mechanism?.handled?.boolValue == false }) else { return nil @@ -21,14 +23,13 @@ extension Event { let reason = exceptions.map { "\($0.type ?? "Unknown"): \($0.value ?? "")" }.joined(separator: "\n") let infoPlist = InfoPlistReader.main - let device = UIDevice.current return """ \(reason) Application: \(infoPlist.bundleExecutable) (\(infoPlist.bundleIdentifier)) Application version: \(infoPlist.bundleShortVersionString) (\(infoPlist.bundleVersion)) Matrix SDK version: \(sdkGitSha()) - \(device.model) \(device.systemVersion) + \(deviceModel) \(systemVersion) """ } }