Delay processing FCM uninstalled feedback

Check to make sure client is not still active before unregistering,
since FCM feedback seems to be often erroneous
This commit is contained in:
Moxie Marlinspike
2019-05-06 21:06:18 -07:00
parent 92ca8862e1
commit 4d9c9206cf
13 changed files with 259 additions and 63 deletions

View File

@@ -210,7 +210,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ActiveUserCounter activeUserCounter = new ActiveUserCounter(config.getMetricsFactory(), cacheClient);
DirectoryReconciler directoryReconciler = new DirectoryReconciler(directoryReconciliationClient, directory);
AccountCleaner accountCleaner = new AccountCleaner(accountsManager, directoryQueue);
List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = Arrays.asList(activeUserCounter, directoryReconciler, accountCleaner);
PushFeedbackProcessor pushFeedbackProcessor = new PushFeedbackProcessor(accountsManager, directoryQueue);
List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = List.of(pushFeedbackProcessor, activeUserCounter, directoryReconciler, accountCleaner);
AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheClient);
AccountDatabaseCrawler accountDatabaseCrawler = new AccountDatabaseCrawler(accounts, accountDatabaseCrawlerCache, accountDatabaseCrawlerListeners, config.getAccountDatabaseCrawlerConfiguration().getChunkSize(), config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs());

View File

@@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Util;
import java.io.IOException;
import java.util.HashMap;
@@ -111,19 +112,20 @@ public class GCMSender implements Managed {
GcmMessage message = (GcmMessage)result.getContext();
logger.warn("Got GCM unregistered notice! " + message.getGcmId());
// Optional<Account> account = getAccountForEvent(message);
//
// if (account.isPresent()) {
// Device device = account.get().getDevice(message.getDeviceId()).get();
Optional<Account> account = getAccountForEvent(message);
if (account.isPresent()) {
Device device = account.get().getDevice(message.getDeviceId()).get();
device.setUninstalledFeedbackTimestamp(Util.todayInMillis());
// device.setGcmId(null);
// device.setFetchesMessages(false);
//
// accountsManager.update(account.get());
//
accountsManager.update(account.get());
// if (!account.get().isActive()) {
// directoryQueue.deleteRegisteredUser(account.get().getNumber());
// }
// }
}
unregistered.mark();
}

View File

@@ -93,7 +93,7 @@ public class Account implements Principal {
}
public void removeDevice(long deviceId) {
this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, "NA", false));
this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, "NA", false, 0));
}
public Set<Device> getDevices() {

View File

@@ -55,6 +55,9 @@ public class Device {
@JsonProperty
private long pushTimestamp;
@JsonProperty
private long uninstalledFeedback;
@JsonProperty
private boolean fetchesMessages;
@@ -83,7 +86,7 @@ public class Device {
String voipApnId, boolean fetchesMessages,
int registrationId, SignedPreKey signedPreKey,
long lastSeen, long created, String userAgent,
boolean unauthenticatedDelivery)
boolean unauthenticatedDelivery, long uninstalledFeedback)
{
this.id = id;
this.name = name;
@@ -100,6 +103,7 @@ public class Device {
this.created = created;
this.userAgent = userAgent;
this.unauthenticatedDelivery = unauthenticatedDelivery;
this.uninstalledFeedback = uninstalledFeedback;
}
public String getApnId() {
@@ -122,6 +126,14 @@ public class Device {
this.voipApnId = voipApnId;
}
public void setUninstalledFeedbackTimestamp(long uninstalledFeedback) {
this.uninstalledFeedback = uninstalledFeedback;
}
public long getUninstalledFeedbackTimestamp() {
return uninstalledFeedback;
}
public void setLastSeen(long lastSeen) {
this.lastSeen = lastSeen;
}

View File

@@ -0,0 +1,69 @@
package org.whispersystems.textsecuregcm.storage;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Util;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static com.codahale.metrics.MetricRegistry.name;
public class PushFeedbackProcessor implements AccountDatabaseCrawlerListener {
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private final Meter expired = metricRegistry.meter(name(getClass(), "unregistered", "expired"));
private final Meter recovered = metricRegistry.meter(name(getClass(), "unregistered", "recovered"));
private final AccountsManager accountsManager;
private final DirectoryQueue directoryQueue;
public PushFeedbackProcessor(AccountsManager accountsManager, DirectoryQueue directoryQueue) {
this.accountsManager = accountsManager;
this.directoryQueue = directoryQueue;
}
@Override
public void onCrawlStart() {}
@Override
public void onCrawlChunk(Optional<String> fromNumber, List<Account> chunkAccounts) {
for (Account account : chunkAccounts) {
boolean update = false;
for (Device device : account.getDevices()) {
if (device.getUninstalledFeedbackTimestamp() != 0 &&
device.getUninstalledFeedbackTimestamp() + TimeUnit.DAYS.toMillis(2) <= Util.todayInMillis())
{
if (device.getLastSeen() + TimeUnit.DAYS.toMillis(2) <= Util.todayInMillis()) {
device.setGcmId(null);
device.setApnId(null);
device.setVoipApnId(null);
device.setFetchesMessages(false);
expired.mark();
} else {
device.setUninstalledFeedbackTimestamp(0);
recovered.mark();
}
update = true;
}
}
if (update) {
accountsManager.update(account);
if (!account.isEnabled()) {
directoryQueue.deleteRegisteredUser(account.getNumber());
}
}
}
}
@Override
public void onCrawlEnd(Optional<String> fromNumber) {}
}