Fix launch crash: recursive dispatch_once between NostrRelayManager and NetworkActivationService (#1343)

* Fix launch crash: recursive dispatch_once between NostrRelayManager and NetworkActivationService

NostrRelayManager.init() runs applyDefaultRelayPolicy(force: true), which
calls dependencies.activationAllowed() when the user has location
permission or a mutual favorite. That closure resolves
NetworkActivationService.shared, whose init captured
NostrRelayManager.shared — re-entering the still-running dispatch_once on
the same thread. libdispatch traps on recursive dispatch_once
(EXC_BREAKPOINT in _dispatch_once_wait), killing the app ~50ms after
launch, before the first frame.

Fresh installs were unaffected (no permission, no favorites, so the
policy path never touched NetworkActivationService during init), which is
why this passed local testing but crashed established TestFlight users on
every launch. Two independent TestFlight crash reports on 1.5.2 (1)
show the identical stack.

Break the cycle by resolving the relay controller lazily: store a
provider closure in init and dereference NostrRelayManager.shared on
first use (start()/reevaluate()), after both singletons have finished
initializing. The injectable test initializer keeps its signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Bump version to 1.5.3

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-12 12:48:53 +02:00
committed by GitHub
co-authored by jack Claude Fable 5
parent 266827ceff
commit fdf28aa5bb
3 changed files with 12 additions and 8 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
MARKETING_VERSION = 1.5.2 MARKETING_VERSION = 1.5.3
CURRENT_PROJECT_VERSION = 1 CURRENT_PROJECT_VERSION = 1
IPHONEOS_DEPLOYMENT_TARGET = 16.0 IPHONEOS_DEPLOYMENT_TARGET = 16.0
+4 -4
View File
@@ -561,7 +561,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -620,7 +620,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -655,7 +655,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -749,7 +749,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -44,7 +44,11 @@ final class NetworkActivationService: ObservableObject {
private let permissionProvider: () -> LocationChannelManager.PermissionState private let permissionProvider: () -> LocationChannelManager.PermissionState
private let mutualFavoritesProvider: () -> Set<Data> private let mutualFavoritesProvider: () -> Set<Data>
private let torController: NetworkActivationTorControlling private let torController: NetworkActivationTorControlling
private let relayController: NetworkActivationRelayControlling // Resolved lazily: NostrRelayManager.init() reads NetworkActivationService.shared
// (via its live dependencies), so capturing NostrRelayManager.shared here would
// re-enter whichever singleton's dispatch_once started first and trap at launch.
private lazy var relayController: NetworkActivationRelayControlling = relayControllerProvider()
private let relayControllerProvider: () -> NetworkActivationRelayControlling
private let proxyController: NetworkActivationProxyControlling private let proxyController: NetworkActivationProxyControlling
private let notificationCenter: NotificationCenter private let notificationCenter: NotificationCenter
@@ -55,7 +59,7 @@ final class NetworkActivationService: ObservableObject {
permissionProvider = { LocationChannelManager.shared.permissionState } permissionProvider = { LocationChannelManager.shared.permissionState }
mutualFavoritesProvider = { FavoritesPersistenceService.shared.mutualFavorites } mutualFavoritesProvider = { FavoritesPersistenceService.shared.mutualFavorites }
torController = TorManager.shared torController = TorManager.shared
relayController = NostrRelayManager.shared relayControllerProvider = { NostrRelayManager.shared }
proxyController = TorURLSession.shared proxyController = TorURLSession.shared
notificationCenter = .default notificationCenter = .default
} }
@@ -77,7 +81,7 @@ final class NetworkActivationService: ObservableObject {
self.permissionProvider = permissionProvider self.permissionProvider = permissionProvider
self.mutualFavoritesProvider = mutualFavoritesProvider self.mutualFavoritesProvider = mutualFavoritesProvider
self.torController = torController self.torController = torController
self.relayController = relayController self.relayControllerProvider = { relayController }
self.proxyController = proxyController self.proxyController = proxyController
self.notificationCenter = notificationCenter self.notificationCenter = notificationCenter
} }