From 32a95f96fffd25ea87cb84c6cca7fa28822bfe9e Mon Sep 17 00:00:00 2001
From: Jon Chambers <63609320+jon-signal@users.noreply.github.com>
Date: Wed, 14 Jul 2021 16:58:51 -0400
Subject: [PATCH] Add a pessimistic locking system for operations on
recently-deleted account records
---
service/pom.xml | 11 ++
.../WhisperServerConfiguration.java | 9 ++
.../textsecuregcm/WhisperServerService.java | 17 ++-
.../controllers/AccountController.java | 2 +-
.../storage/AccountsManager.java | 12 +-
.../storage/DeletedAccounts.java | 55 ++++++-
.../storage/DeletedAccountsManager.java | 120 +++++++++++++++
.../storage/DeletedAccountsTableCrawler.java | 34 ++---
.../workers/DeleteUserCommand.java | 15 +-
...ConcurrentModificationIntegrationTest.java | 2 +-
.../storage/DeletedAccountsManagerTest.java | 142 ++++++++++++++++++
.../storage/DeletedAccountsTest.java | 52 ++++++-
.../storage/DynamoDbExtension.java | 17 +++
.../controllers/AccountControllerTest.java | 17 ++-
.../tests/storage/AccountCleanerTest.java | 4 +-
.../tests/storage/AccountsManagerTest.java | 31 ++--
16 files changed, 487 insertions(+), 53 deletions(-)
create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManager.java
create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManagerTest.java
diff --git a/service/pom.xml b/service/pom.xml
index 936c91dc1..b584a1394 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -283,6 +283,17 @@
com.amazonaws
aws-java-sdk-s3
+
+ com.amazonaws
+ dynamodb-lock-client
+ 1.1.0
+
+
+ commons-logging
+ commons-logging
+
+
+
redis.clients
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
index de63cbb94..4f5a671c7 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java
@@ -163,6 +163,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private DeletedAccountsDynamoDbConfiguration deletedAccountsDynamoDb;
+ @Valid
+ @NotNull
+ @JsonProperty
+ private DynamoDbConfiguration deletedAccountsLockDynamoDb;
+
@Valid
@NotNull
@JsonProperty
@@ -391,6 +396,10 @@ public class WhisperServerConfiguration extends Configuration {
return deletedAccountsDynamoDb;
}
+ public DynamoDbConfiguration getDeletedAccountsLockDynamoDbConfiguration() {
+ return deletedAccountsLockDynamoDb;
+ }
+
public DatabaseConfiguration getAbuseDatabaseConfiguration() {
return abuseDatabase;
}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
index 93292ce03..5a59c2bf0 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
@@ -6,6 +6,10 @@ package org.whispersystems.textsecuregcm;
import static com.codahale.metrics.MetricRegistry.name;
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.auth.InstanceProfileCredentialsProvider;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.jdbi3.strategies.DefaultNameStrategy;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -153,6 +157,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ActiveUserCounter;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsDirectoryReconciler;
+import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsTableCrawler;
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
@@ -347,6 +352,13 @@ public class WhisperServerService extends Application deleteStorageServiceDataFuture = secureStorageClient.deleteStoredData(account.getUuid());
final CompletableFuture deleteBackupServiceDataFuture = secureBackupClient.deleteBackups(account.getUuid());
@@ -340,9 +340,9 @@ public class AccountsManager {
}
}
- deletedAccounts.put(account.getUuid(), account.getNumber());
+ deletedAccountsManager.put(account.getUuid(), account.getNumber());
- } catch (final Exception e) {
+ } catch (final RuntimeException | InterruptedException e) {
logger.warn("Failed to delete account", e);
Metrics.counter(DELETE_ERROR_COUNTER_NAME,
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeletedAccounts.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeletedAccounts.java
index 8980e25f7..a0a9cfc69 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeletedAccounts.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeletedAccounts.java
@@ -6,13 +6,24 @@ package org.whispersystems.textsecuregcm.storage;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.whispersystems.textsecuregcm.util.AttributeValues;
import org.whispersystems.textsecuregcm.util.Pair;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.BatchGetItemResponse;
+import software.amazon.awssdk.services.dynamodb.model.KeysAndAttributes;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
@@ -27,6 +38,9 @@ public class DeletedAccounts extends AbstractDynamoDbStore {
static final Duration TIME_TO_LIVE = Duration.ofDays(30);
+ // Note that this limit is imposed by DynamoDB itself; going above 100 will result in errors
+ static final int GET_BATCH_SIZE = 100;
+
private final String tableName;
private final String needsReconciliationIndexName;
@@ -37,7 +51,7 @@ public class DeletedAccounts extends AbstractDynamoDbStore {
this.needsReconciliationIndexName = needsReconciliationIndexName;
}
- public void put(UUID uuid, String e164) {
+ void put(UUID uuid, String e164) {
db().putItem(PutItemRequest.builder()
.tableName(tableName)
.item(Map.of(
@@ -48,7 +62,7 @@ public class DeletedAccounts extends AbstractDynamoDbStore {
.build());
}
- public List> listAccountsToReconcile(final int max) {
+ List> listAccountsToReconcile(final int max) {
final ScanRequest scanRequest = ScanRequest.builder()
.tableName(tableName)
@@ -64,7 +78,42 @@ public class DeletedAccounts extends AbstractDynamoDbStore {
.collect(Collectors.toList());
}
- public void markReconciled(final List phoneNumbersReconciled) {
+ Set getAccountsNeedingReconciliation(final Collection e164s) {
+ final Queue