kopia lustrzana https://github.com/ryukoposting/Signal-Android
Prevent possible deadlock in identity cache.
rodzic
a92638e897
commit
417070e957
|
@ -5,6 +5,8 @@ import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.libsignal.protocol.IdentityKey;
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||||
|
@ -256,7 +258,8 @@ public class SignalBaseIdentityKeyStore {
|
||||||
this.cache = new LRUCache<>(200);
|
this.cache = new LRUCache<>(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized @Nullable IdentityStoreRecord get(@NonNull String addressName) {
|
public @Nullable IdentityStoreRecord get(@NonNull String addressName) {
|
||||||
|
synchronized (this) {
|
||||||
if (cache.containsKey(addressName)) {
|
if (cache.containsKey(addressName)) {
|
||||||
return cache.get(addressName);
|
return cache.get(addressName);
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,17 +268,21 @@ public class SignalBaseIdentityKeyStore {
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void save(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) {
|
|
||||||
identityDatabase.saveIdentity(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
|
||||||
cache.put(addressName, new IdentityStoreRecord(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonblockingApproval) {
|
public void save(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) {
|
||||||
|
withWriteLock(() -> {
|
||||||
|
identityDatabase.saveIdentity(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||||
|
cache.put(addressName, new IdentityStoreRecord(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonblockingApproval) {
|
||||||
setApproval(addressName, recipientId, cache.get(addressName), nonblockingApproval);
|
setApproval(addressName, recipientId, cache.get(addressName), nonblockingApproval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, @Nullable IdentityStoreRecord record, boolean nonblockingApproval) {
|
public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, @Nullable IdentityStoreRecord record, boolean nonblockingApproval) {
|
||||||
|
withWriteLock(() -> {
|
||||||
identityDatabase.setApproval(addressName, recipientId, nonblockingApproval);
|
identityDatabase.setApproval(addressName, recipientId, nonblockingApproval);
|
||||||
|
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
|
@ -287,9 +294,11 @@ public class SignalBaseIdentityKeyStore {
|
||||||
record.getTimestamp(),
|
record.getTimestamp(),
|
||||||
nonblockingApproval));
|
nonblockingApproval));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus) {
|
public void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus) {
|
||||||
|
withWriteLock(() -> {
|
||||||
identityDatabase.setVerified(addressName, recipientId, identityKey, verifiedStatus);
|
identityDatabase.setVerified(addressName, recipientId, identityKey, verifiedStatus);
|
||||||
|
|
||||||
IdentityStoreRecord record = cache.get(addressName);
|
IdentityStoreRecord record = cache.get(addressName);
|
||||||
|
@ -302,15 +311,49 @@ public class SignalBaseIdentityKeyStore {
|
||||||
record.getTimestamp(),
|
record.getTimestamp(),
|
||||||
record.getNonblockingApproval()));
|
record.getNonblockingApproval()));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void delete(@NonNull String addressName) {
|
public void delete(@NonNull String addressName) {
|
||||||
|
withWriteLock(() -> {
|
||||||
identityDatabase.delete(addressName);
|
identityDatabase.delete(addressName);
|
||||||
cache.remove(addressName);
|
cache.remove(addressName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void invalidate(@NonNull String addressName) {
|
public synchronized void invalidate(@NonNull String addressName) {
|
||||||
|
synchronized (this) {
|
||||||
cache.remove(addressName);
|
cache.remove(addressName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are situations when this class is accessed in a transaction, meaning that if we *just* synchronize the method, we can end up with:
|
||||||
|
*
|
||||||
|
* Thread A:
|
||||||
|
* 1. Start transaction
|
||||||
|
* 2. Acquire cache lock
|
||||||
|
* 3. Do DB write
|
||||||
|
*
|
||||||
|
* Thread B:
|
||||||
|
* 1. Acquire cache lock
|
||||||
|
* 2. Do DB write
|
||||||
|
*
|
||||||
|
* If the order is B.1 -> A.1 -> B.2 -> A.2, you have yourself a deadlock.
|
||||||
|
*
|
||||||
|
* To prevent this, writes should first acquire the DB lock before getting the cache lock to ensure we always acquire locks in the same order.
|
||||||
|
*/
|
||||||
|
private void withWriteLock(Runnable runnable) {
|
||||||
|
SQLiteDatabase db = SignalDatabase.getRawDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue