Fix potential dead lock in recipient store

This commit is contained in:
AsamK 2024-02-09 17:51:15 +01:00
parent 4e61f2b2e5
commit be699cbd85

View file

@ -47,7 +47,6 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private final SelfProfileKeyProvider selfProfileKeyProvider; private final SelfProfileKeyProvider selfProfileKeyProvider;
private final Database database; private final Database database;
private final Object recipientsLock = new Object();
private final Map<Long, Long> recipientsMerged = new HashMap<>(); private final Map<Long, Long> recipientsMerged = new HashMap<>();
private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = new HashMap<>(); private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = new HashMap<>();
@ -164,34 +163,30 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
} }
private RecipientId resolveRecipientByNumber(final String number) { private RecipientId resolveRecipientByNumber(final String number) {
synchronized (recipientsLock) { final RecipientId recipientId;
final RecipientId recipientId; try (final var connection = database.getConnection()) {
try (final var connection = database.getConnection()) { connection.setAutoCommit(false);
connection.setAutoCommit(false); recipientId = resolveRecipientLocked(connection, number);
recipientId = resolveRecipientLocked(connection, number); connection.commit();
connection.commit(); } catch (SQLException e) {
} catch (SQLException e) { throw new RuntimeException("Failed read recipient store", e);
throw new RuntimeException("Failed read recipient store", e);
}
return recipientId;
} }
return recipientId;
} }
@Override @Override
public RecipientId resolveRecipient(final ServiceId serviceId) { public RecipientId resolveRecipient(final ServiceId serviceId) {
synchronized (recipientsLock) { try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
final var recipientWithAddress = recipientAddressCache.get(serviceId); final var recipientWithAddress = recipientAddressCache.get(serviceId);
if (recipientWithAddress != null) { if (recipientWithAddress != null) {
return recipientWithAddress.id(); return recipientWithAddress.id();
} }
try (final var connection = database.getConnection()) { final var recipientId = resolveRecipientLocked(connection, serviceId);
connection.setAutoCommit(false); connection.commit();
final var recipientId = resolveRecipientLocked(connection, serviceId); return recipientId;
connection.commit(); } catch (SQLException e) {
return recipientId; throw new RuntimeException("Failed read recipient store", e);
} catch (SQLException e) {
throw new RuntimeException("Failed read recipient store", e);
}
} }
} }
@ -258,17 +253,15 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
} }
public RecipientId resolveRecipient(RecipientAddress address) { public RecipientId resolveRecipient(RecipientAddress address) {
synchronized (recipientsLock) { final RecipientId recipientId;
final RecipientId recipientId; try (final var connection = database.getConnection()) {
try (final var connection = database.getConnection()) { connection.setAutoCommit(false);
connection.setAutoCommit(false); recipientId = resolveRecipientLocked(connection, address);
recipientId = resolveRecipientLocked(connection, address); connection.commit();
connection.commit(); } catch (SQLException e) {
} catch (SQLException e) { throw new RuntimeException("Failed read recipient store", e);
throw new RuntimeException("Failed read recipient store", e);
}
return recipientId;
} }
return recipientId;
} }
public RecipientId resolveRecipient(Connection connection, RecipientAddress address) throws SQLException { public RecipientId resolveRecipient(Connection connection, RecipientAddress address) throws SQLException {
@ -550,19 +543,17 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public void deleteRecipientData(RecipientId recipientId) { public void deleteRecipientData(RecipientId recipientId) {
logger.debug("Deleting recipient data for {}", recipientId); logger.debug("Deleting recipient data for {}", recipientId);
synchronized (recipientsLock) { try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId)); recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId));
try (final var connection = database.getConnection()) { storeContact(connection, recipientId, null);
connection.setAutoCommit(false); storeProfile(connection, recipientId, null);
storeContact(connection, recipientId, null); storeProfileKey(connection, recipientId, null, false);
storeProfile(connection, recipientId, null); storeExpiringProfileKeyCredential(connection, recipientId, null);
storeProfileKey(connection, recipientId, null, false); deleteRecipient(connection, recipientId);
storeExpiringProfileKeyCredential(connection, recipientId, null); connection.commit();
deleteRecipient(connection, recipientId); } catch (SQLException e) {
connection.commit(); throw new RuntimeException("Failed update recipient store", e);
} catch (SQLException e) {
throw new RuntimeException("Failed update recipient store", e);
}
} }
} }
@ -1026,14 +1017,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private RecipientId resolveRecipientTrusted(RecipientAddress address, boolean isSelf) { private RecipientId resolveRecipientTrusted(RecipientAddress address, boolean isSelf) {
final Pair<RecipientId, List<RecipientId>> pair; final Pair<RecipientId, List<RecipientId>> pair;
synchronized (recipientsLock) { try (final var connection = database.getConnection()) {
try (final var connection = database.getConnection()) { connection.setAutoCommit(false);
connection.setAutoCommit(false); pair = resolveRecipientTrustedLocked(connection, address, isSelf);
pair = resolveRecipientTrustedLocked(connection, address, isSelf); connection.commit();
connection.commit(); } catch (SQLException e) {
} catch (SQLException e) { throw new RuntimeException("Failed update recipient store", e);
throw new RuntimeException("Failed update recipient store", e);
}
} }
if (!pair.second().isEmpty()) { if (!pair.second().isEmpty()) {
@ -1073,9 +1062,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
for (final var toBeMergedRecipientId : toBeMergedRecipientIds) { for (final var toBeMergedRecipientId : toBeMergedRecipientIds) {
recipientMergeHandler.mergeRecipients(connection, recipientId, toBeMergedRecipientId); recipientMergeHandler.mergeRecipients(connection, recipientId, toBeMergedRecipientId);
deleteRecipient(connection, toBeMergedRecipientId); deleteRecipient(connection, toBeMergedRecipientId);
synchronized (recipientsLock) { recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(toBeMergedRecipientId));
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(toBeMergedRecipientId));
}
} }
} }
@ -1164,44 +1151,40 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
} }
private void removeRecipientAddress(Connection connection, RecipientId recipientId) throws SQLException { private void removeRecipientAddress(Connection connection, RecipientId recipientId) throws SQLException {
synchronized (recipientsLock) { recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId));
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId)); final var sql = (
final var sql = ( """
""" UPDATE %s
UPDATE %s SET number = NULL, aci = NULL, pni = NULL, username = NULL, storage_id = NULL
SET number = NULL, aci = NULL, pni = NULL, username = NULL, storage_id = NULL WHERE _id = ?
WHERE _id = ? """
""" ).formatted(TABLE_RECIPIENT);
).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) {
try (final var statement = connection.prepareStatement(sql)) { statement.setLong(1, recipientId.id());
statement.setLong(1, recipientId.id()); statement.executeUpdate();
statement.executeUpdate();
}
} }
} }
private void updateRecipientAddress( private void updateRecipientAddress(
Connection connection, RecipientId recipientId, final RecipientAddress address Connection connection, RecipientId recipientId, final RecipientAddress address
) throws SQLException { ) throws SQLException {
synchronized (recipientsLock) { recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId));
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId)); final var sql = (
final var sql = ( """
""" UPDATE %s
UPDATE %s SET number = ?, aci = ?, pni = ?, username = ?
SET number = ?, aci = ?, pni = ?, username = ? WHERE _id = ?
WHERE _id = ? """
""" ).formatted(TABLE_RECIPIENT);
).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) {
try (final var statement = connection.prepareStatement(sql)) { statement.setString(1, address.number().orElse(null));
statement.setString(1, address.number().orElse(null)); statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setString(2, address.aci().map(ACI::toString).orElse(null)); statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(3, address.pni().map(PNI::toString).orElse(null)); statement.setString(4, address.username().orElse(null));
statement.setString(4, address.username().orElse(null)); statement.setLong(5, recipientId.id());
statement.setLong(5, recipientId.id()); statement.executeUpdate();
statement.executeUpdate();
}
rotateStorageId(connection, recipientId);
} }
rotateStorageId(connection, recipientId);
} }
private void deleteRecipient(final Connection connection, final RecipientId recipientId) throws SQLException { private void deleteRecipient(final Connection connection, final RecipientId recipientId) throws SQLException {