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