fix deadlock issue

This commit is contained in:
otsmr 2026-05-04 23:33:42 +02:00
parent cdababa3c8
commit 0a972d023f
6 changed files with 122 additions and 82 deletions

View file

@ -145,6 +145,7 @@ class ApiService {
}
Future<void> onClosed() async {
Log.info('websocket connection closed');
_channel = null;
isAuthenticated = false;
_connectionStateController.add(false);
@ -249,7 +250,7 @@ class ApiService {
completer.complete(msg);
}
} else {
await handleServerMessage(msg);
unawaited(handleServerMessage(msg));
}
} catch (e) {
Log.error('Error parsing the servers message: $e');

View file

@ -67,6 +67,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
Receipt? receipt,
bool onlyReturnEncryptedData = false,
bool blocking = true,
bool useLock = true,
}) async {
try {
if (receiptId == null && receipt == null) return null;
@ -132,6 +133,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
final cipherText = await signalEncryptMessage(
receipt.contactId,
Uint8List.fromList(message.encryptedContent),
useLock: useLock,
);
if (cipherText == null) {
Log.error('Could not encrypt the message. Aborting and trying again.');
@ -336,6 +338,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
bool blocking = true,
String? messageId,
bool onlySendIfNoReceiptsAreOpen = false,
bool useLock = true,
}) async {
if (onlySendIfNoReceiptsAreOpen) {
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
@ -397,6 +400,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
receipt: receipt,
onlyReturnEncryptedData: onlyReturnEncryptedData,
blocking: blocking,
useLock: useLock,
);
if (!blocking) {
return null;

View file

@ -40,6 +40,8 @@ final lockHandleServerMessage = Mutex();
Future<void> handleServerMessage(server.ServerToClient msg) async {
return lockHandleServerMessage.protect(() async {
Log.info('Processing a message from the server.');
/// Returns means, that the server can delete the message from the server.
final ok = client.Response_Ok()..none = true;
var response = client.Response()..ok = ok;
@ -48,8 +50,12 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
if (msg.v0.hasRequestNewPreKeys()) {
response = await handleRequestNewPreKey();
} else if (msg.v0.hasNewMessage()) {
Log.info('Got 1 message from the server.');
await handleClient2ClientMessage(msg.v0.newMessage);
} else if (msg.v0.hasNewMessages()) {
Log.info(
'Got ${msg.v0.newMessages.newMessages.length} messages from the server.',
);
for (final newMessage in msg.v0.newMessages.newMessages) {
try {
await handleClient2ClientMessage(newMessage);
@ -70,13 +76,12 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
await apiService.sendResponse(ClientToServer()..v0 = v0);
AppState.gotMessageFromServer = true;
Log.info('Message from server proccessed.');
});
}
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
Mutex protectReceiptCheck = Mutex();
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
final body = Uint8List.fromList(newMessage.body);
final fromUserId = newMessage.fromUserId.toInt();
@ -84,15 +89,15 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
final message = Message.fromBuffer(body);
final receiptId = message.receiptId;
final isDuplicated = await protectReceiptCheck.protect(() async {
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
return true;
return;
}
await twonlyDB.receiptsDao.gotReceipt(receiptId);
return false;
});
if (isDuplicated) {
try {
await twonlyDB.receiptsDao.gotReceipt(receiptId);
Log.info('Got a message with receiptId $receiptId');
} catch (e) {
Log.error(e);
return;
}

View file

@ -11,10 +11,22 @@ import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';
Future<CiphertextMessage?> signalEncryptMessage(
int target,
Uint8List plaintextContent, {
bool useLock = true,
}) async {
if (useLock) {
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
return _signalEncryptMessage(target, plaintextContent);
});
}
return _signalEncryptMessage(target, plaintextContent);
}
Future<CiphertextMessage?> _signalEncryptMessage(
int target,
Uint8List plaintextContent,
) async {
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
try {
final signalStore = (await getSignalStore())!;
final address = getSignalAddress(target);
@ -24,7 +36,6 @@ Future<CiphertextMessage?> signalEncryptMessage(
Log.error(e.toString());
return null;
}
});
}
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
@ -67,8 +78,9 @@ signalDecryptMessage(
Log.info(e.toString());
return (null, null);
} on InvalidMessageException catch (e) {
Log.warn(e);
if (!resyncedUsers.contains(fromUserId)) {
if (await handleSessionResync(fromUserId)) {
if (await handleSessionResync(fromUserId, useLock: false)) {
// This flag prevents from resyncing the session the client received multiple new
// messages from the server he could not decrypt
resyncedUsers.add(fromUserId);
@ -81,10 +93,10 @@ signalDecryptMessage(
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
),
),
useLock: false,
);
}
}
Log.warn(e);
return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
} catch (e) {
Log.error(e);

View file

@ -91,9 +91,14 @@ Future<SignalIdentity?> getSignalIdentity() async {
}
Future<Uint8List> getUserPublicKey() async {
Log.info('getUserPublicKey: getting identity');
final signalIdentity = (await getSignalIdentity())!;
Log.info('getUserPublicKey: getting signal store');
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
return (await signalStore.getIdentityKeyPair()).getPublicKey().serialize();
Log.info('getUserPublicKey: getting key pair');
final keyPair = await signalStore.getIdentityKeyPair();
Log.info('getUserPublicKey: serializing public key');
return keyPair.getPublicKey().serialize();
}
Future<void> createIfNotExistsSignalIdentity() async {

View file

@ -8,8 +8,19 @@ import 'package:twonly/src/services/signal/protocol_state.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';
Future<bool> processSignalUserData(Response_UserData userData) async {
Future<bool> processSignalUserData(
Response_UserData userData, {
bool useLock = true,
}) async {
if (useLock) {
return lockingSignalProtocol.protect(() async {
return _processSignalUserData(userData);
});
}
return _processSignalUserData(userData);
}
Future<bool> _processSignalUserData(Response_UserData userData) async {
final SignalProtocolStore? signalStore = await getSignalStore();
if (signalStore == null) {
@ -74,7 +85,6 @@ Future<bool> processSignalUserData(Response_UserData userData) async {
Log.error('could not process pre key bundle: $e');
return false;
}
});
}
Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
@ -96,11 +106,14 @@ Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
}
}
Future<bool> handleSessionResync(int fromUserId) async {
Future<bool> handleSessionResync(
int fromUserId, {
bool useLock = true,
}) async {
final userData = await apiService.getUserById(fromUserId);
if (userData != null) {
Log.info('Got new session data from the server to re-sync the session');
return processSignalUserData(userData);
return processSignalUserData(userData, useLock: useLock);
}
Log.info('Could not download userdata from the server.');
return false;