mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 06:25:20 +00:00
Fix deadlock in DeliveryTracker causing macOS app freeze
- Fixed nested lock acquisition in trackMessage/scheduleTimeout - Properly handle lock release before calling methods that need locks - Ensure retryDelivery and handleTimeout don't cause deadlocks - Release locks before calling external methods
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 63;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = "<group>"; };
|
763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = "<group>"; };
|
||||||
7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = "<group>"; };
|
8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = "<group>"; };
|
||||||
997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
|
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
|
||||||
AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = "<group>"; };
|
AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = "<group>"; };
|
||||||
@@ -302,7 +302,6 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 18198ED912AAF495D8AF7763;
|
mainGroup = 18198ED912AAF495D8AF7763;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
preferredProjectObjectVersion = 54;
|
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -486,6 +485,7 @@
|
|||||||
CODE_SIGNING_REQUIRED = YES;
|
CODE_SIGNING_REQUIRED = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -531,6 +531,7 @@
|
|||||||
CODE_SIGNING_REQUIRED = YES;
|
CODE_SIGNING_REQUIRED = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
|||||||
+14
-2
@@ -26,7 +26,19 @@
|
|||||||
<string>bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users.</string>
|
<string>bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users.</string>
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
<string>bitchat uses Bluetooth to discover and connect with other bitchat users nearby.</string>
|
<string>bitchat uses Bluetooth to discover and connect with other bitchat users nearby.</string>
|
||||||
<key>NSUserNotificationAlertStyle</key>
|
<key>UIBackgroundModes</key>
|
||||||
<string>alert</string>
|
<array>
|
||||||
|
<string>bluetooth-central</string>
|
||||||
|
<string>bluetooth-peripheral</string>
|
||||||
|
</array>
|
||||||
|
<key>UILaunchScreen</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIColorName</key>
|
||||||
|
<string>Black</string>
|
||||||
|
</dict>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ class DeliveryTracker {
|
|||||||
// MARK: - Public Methods
|
// MARK: - Public Methods
|
||||||
|
|
||||||
func trackMessage(_ message: BitchatMessage, recipientID: String, recipientNickname: String, isFavorite: Bool = false, expectedRecipients: Int = 1) {
|
func trackMessage(_ message: BitchatMessage, recipientID: String, recipientNickname: String, isFavorite: Bool = false, expectedRecipients: Int = 1) {
|
||||||
pendingLock.lock()
|
|
||||||
defer { pendingLock.unlock() }
|
|
||||||
|
|
||||||
// Don't track broadcasts or certain message types
|
// Don't track broadcasts or certain message types
|
||||||
guard message.isPrivate || message.room != nil else { return }
|
guard message.isPrivate || message.room != nil else { return }
|
||||||
|
|
||||||
@@ -86,14 +83,17 @@ class DeliveryTracker {
|
|||||||
timeoutTimer: nil
|
timeoutTimer: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Store the delivery with lock
|
||||||
|
pendingLock.lock()
|
||||||
pendingDeliveries[message.id] = delivery
|
pendingDeliveries[message.id] = delivery
|
||||||
|
pendingLock.unlock()
|
||||||
|
|
||||||
// Update status to sent
|
// Update status to sent
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||||
self?.updateDeliveryStatus(message.id, status: .sent)
|
self?.updateDeliveryStatus(message.id, status: .sent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule timeout
|
// Schedule timeout (outside of lock)
|
||||||
scheduleTimeout(for: message.id)
|
scheduleTimeout(for: message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,10 +175,18 @@ class DeliveryTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func scheduleTimeout(for messageID: String) {
|
private func scheduleTimeout(for messageID: String) {
|
||||||
guard let delivery = pendingDeliveries[messageID] else { return }
|
// Get delivery info with lock
|
||||||
|
pendingLock.lock()
|
||||||
|
guard let delivery = pendingDeliveries[messageID] else {
|
||||||
|
pendingLock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let isFavorite = delivery.isFavorite
|
||||||
|
let isRoomMessage = delivery.isRoomMessage
|
||||||
|
pendingLock.unlock()
|
||||||
|
|
||||||
let timeout = delivery.isFavorite ? favoriteTimeout :
|
let timeout = isFavorite ? favoriteTimeout :
|
||||||
(delivery.isRoomMessage ? roomMessageTimeout : privateMessageTimeout)
|
(isRoomMessage ? roomMessageTimeout : privateMessageTimeout)
|
||||||
|
|
||||||
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
|
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
|
||||||
self?.handleTimeout(messageID: messageID)
|
self?.handleTimeout(messageID: messageID)
|
||||||
@@ -194,23 +202,33 @@ class DeliveryTracker {
|
|||||||
|
|
||||||
private func handleTimeout(messageID: String) {
|
private func handleTimeout(messageID: String) {
|
||||||
pendingLock.lock()
|
pendingLock.lock()
|
||||||
defer { pendingLock.unlock() }
|
guard let delivery = pendingDeliveries[messageID] else {
|
||||||
|
pendingLock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let delivery = pendingDeliveries[messageID] else { return }
|
let shouldRetry = delivery.shouldRetry
|
||||||
|
let isRoomMessage = delivery.isRoomMessage
|
||||||
|
|
||||||
if delivery.shouldRetry {
|
if shouldRetry {
|
||||||
// Retry for favorites
|
pendingLock.unlock()
|
||||||
|
// Retry for favorites (outside of lock)
|
||||||
retryDelivery(messageID: messageID)
|
retryDelivery(messageID: messageID)
|
||||||
} else {
|
} else {
|
||||||
// Mark as failed
|
// Mark as failed
|
||||||
let reason = delivery.isRoomMessage ? "No response from room members" : "Message not delivered"
|
let reason = isRoomMessage ? "No response from room members" : "Message not delivered"
|
||||||
updateDeliveryStatus(messageID, status: .failed(reason: reason))
|
|
||||||
pendingDeliveries.removeValue(forKey: messageID)
|
pendingDeliveries.removeValue(forKey: messageID)
|
||||||
|
pendingLock.unlock()
|
||||||
|
updateDeliveryStatus(messageID, status: .failed(reason: reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func retryDelivery(messageID: String) {
|
private func retryDelivery(messageID: String) {
|
||||||
guard let delivery = pendingDeliveries[messageID] else { return }
|
pendingLock.lock()
|
||||||
|
guard let delivery = pendingDeliveries[messageID] else {
|
||||||
|
pendingLock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Increment retry count
|
// Increment retry count
|
||||||
let newDelivery = PendingDelivery(
|
let newDelivery = PendingDelivery(
|
||||||
@@ -227,9 +245,11 @@ class DeliveryTracker {
|
|||||||
)
|
)
|
||||||
|
|
||||||
pendingDeliveries[messageID] = newDelivery
|
pendingDeliveries[messageID] = newDelivery
|
||||||
|
let retryCount = delivery.retryCount
|
||||||
|
pendingLock.unlock()
|
||||||
|
|
||||||
// Exponential backoff for retry
|
// Exponential backoff for retry
|
||||||
let delay = retryDelay * pow(2, Double(delivery.retryCount))
|
let delay = retryDelay * pow(2, Double(retryCount))
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
// Trigger resend through delegate or notification
|
// Trigger resend through delegate or notification
|
||||||
|
|||||||
Reference in New Issue
Block a user