Retire the legacy client presence system

This commit is contained in:
Jon Chambers 2024-11-05 12:34:27 -05:00 committed by Jon Chambers
parent 9898e18ae2
commit 1c167ec150
27 changed files with 40 additions and 1034 deletions

View File

@ -191,7 +191,6 @@ import org.whispersystems.textsecuregcm.metrics.TrafficSource;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck; import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.ProvisioningManager;
@ -543,11 +542,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
.minThreads(8) .minThreads(8)
.maxThreads(8) .maxThreads(8)
.build(); .build();
ExecutorService clientPresenceExecutor = environment.lifecycle()
.executorService(name(getClass(), "clientPresence-%d"))
.minThreads(8)
.maxThreads(8)
.build();
// unbounded executor (same as cachedThreadPool) // unbounded executor (same as cachedThreadPool)
ExecutorService remoteStorageHttpExecutor = environment.lifecycle() ExecutorService remoteStorageHttpExecutor = environment.lifecycle()
.executorService(name(getClass(), "remoteStorage-%d")) .executorService(name(getClass(), "remoteStorage-%d"))
@ -617,8 +611,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration()); secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator,
storageServiceExecutor, storageServiceRetryExecutor, config.getSecureStorageServiceConfiguration()); storageServiceExecutor, storageServiceRetryExecutor, config.getSecureStorageServiceConfiguration());
ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, recurringJobExecutor,
keyspaceNotificationDispatchExecutor);
PubSubClientEventManager pubSubClientEventManager = new PubSubClientEventManager(messagesCluster, clientEventExecutor); PubSubClientEventManager pubSubClientEventManager = new PubSubClientEventManager(messagesCluster, clientEventExecutor);
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
MessagesCache messagesCache = new MessagesCache(messagesCluster, keyspaceNotificationDispatchExecutor, MessagesCache messagesCache = new MessagesCache(messagesCluster, keyspaceNotificationDispatchExecutor,
@ -637,9 +629,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor); new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor);
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
pubsubClient, accountLockManager, keysManager, messagesManager, profilesManager, pubsubClient, accountLockManager, keysManager, messagesManager, profilesManager,
secureStorageClient, secureValueRecovery2Client, secureStorageClient, secureValueRecovery2Client, pubSubClientEventManager,
clientPresenceManager, pubSubClientEventManager, registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor,
registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, clientPresenceExecutor,
clock, config.getLinkDeviceSecretConfiguration().secret().value(), dynamicConfigurationManager); clock, config.getLinkDeviceSecretConfiguration().secret().value(), dynamicConfigurationManager);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs); RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration()); APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
@ -668,7 +659,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new MessageDeliveryLoopMonitor(rateLimitersCluster); new MessageDeliveryLoopMonitor(rateLimitersCluster);
final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager( final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
accountsManager, clientPresenceManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator, accountsManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator,
registrationRecoveryPasswordsManager, pushNotificationManager, rateLimiters); registrationRecoveryPasswordsManager, pushNotificationManager, rateLimiters);
final ReportedMessageMetricsListener reportedMessageMetricsListener = new ReportedMessageMetricsListener( final ReportedMessageMetricsListener reportedMessageMetricsListener = new ReportedMessageMetricsListener(
@ -745,7 +736,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.lifecycle().manage(pushNotificationScheduler); environment.lifecycle().manage(pushNotificationScheduler);
environment.lifecycle().manage(provisioningManager); environment.lifecycle().manage(provisioningManager);
environment.lifecycle().manage(messagesCache); environment.lifecycle().manage(messagesCache);
environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(pubSubClientEventManager); environment.lifecycle().manage(pubSubClientEventManager);
environment.lifecycle().manage(currencyManager); environment.lifecycle().manage(currencyManager);
environment.lifecycle().manage(registrationServiceClient); environment.lifecycle().manage(registrationServiceClient);
@ -998,8 +988,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.jersey().register(MultiRecipientMessageProvider.class); environment.jersey().register(MultiRecipientMessageProvider.class);
environment.jersey().register(new AuthDynamicFeature(accountAuthFilter)); environment.jersey().register(new AuthDynamicFeature(accountAuthFilter));
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class)); environment.jersey().register(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class));
environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager));
pubSubClientEventManager));
environment.jersey().register(new TimestampResponseFilter()); environment.jersey().register(new TimestampResponseFilter());
/// ///
@ -1009,11 +998,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator, new AccountPrincipalSupplier(accountsManager))); webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator, new AccountPrincipalSupplier(accountsManager)));
webSocketEnvironment.setConnectListener( webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, messageMetrics, pushNotificationManager, new AuthenticatedConnectListener(receiptSender, messagesManager, messageMetrics, pushNotificationManager,
pushNotificationScheduler, clientPresenceManager, pubSubClientEventManager, websocketScheduledExecutor, pushNotificationScheduler, pubSubClientEventManager, websocketScheduledExecutor,
messageDeliveryScheduler, clientReleaseManager, messageDeliveryLoopMonitor)); messageDeliveryScheduler, clientReleaseManager, messageDeliveryLoopMonitor));
webSocketEnvironment.jersey() webSocketEnvironment.jersey()
.register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, .register(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager));
pubSubClientEventManager));
webSocketEnvironment.jersey().register(new RateLimitByIpFilter(rateLimiters)); webSocketEnvironment.jersey().register(new RateLimitByIpFilter(rateLimiters));
webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET)); webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class); webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class);
@ -1155,8 +1143,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
WebSocketEnvironment<AuthenticatedDevice> provisioningEnvironment = new WebSocketEnvironment<>(environment, WebSocketEnvironment<AuthenticatedDevice> provisioningEnvironment = new WebSocketEnvironment<>(environment,
webSocketEnvironment.getRequestLog(), Duration.ofMillis(60000)); webSocketEnvironment.getRequestLog(), Duration.ofMillis(60000));
provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager));
pubSubClientEventManager));
provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(provisioningManager)); provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(provisioningManager));
provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET, clientReleaseManager)); provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET, clientReleaseManager));
provisioningEnvironment.jersey().register(new KeepAliveController(pubSubClientEventManager)); provisioningEnvironment.jersey().register(new KeepAliveController(pubSubClientEventManager));

View File

@ -25,7 +25,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.entities.Svr3Credentials; import org.whispersystems.textsecuregcm.entities.Svr3Credentials;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
@ -55,7 +54,6 @@ public class RegistrationLockVerificationManager {
private static final String PHONE_VERIFICATION_TYPE_TAG_NAME = "phoneVerificationType"; private static final String PHONE_VERIFICATION_TYPE_TAG_NAME = "phoneVerificationType";
private final AccountsManager accounts; private final AccountsManager accounts;
private final ClientPresenceManager clientPresenceManager;
private final PubSubClientEventManager pubSubClientEventManager; private final PubSubClientEventManager pubSubClientEventManager;
private final ExternalServiceCredentialsGenerator svr2CredentialGenerator; private final ExternalServiceCredentialsGenerator svr2CredentialGenerator;
private final ExternalServiceCredentialsGenerator svr3CredentialGenerator; private final ExternalServiceCredentialsGenerator svr3CredentialGenerator;
@ -65,7 +63,6 @@ public class RegistrationLockVerificationManager {
public RegistrationLockVerificationManager( public RegistrationLockVerificationManager(
final AccountsManager accounts, final AccountsManager accounts,
final ClientPresenceManager clientPresenceManager,
final PubSubClientEventManager pubSubClientEventManager, final PubSubClientEventManager pubSubClientEventManager,
final ExternalServiceCredentialsGenerator svr2CredentialGenerator, final ExternalServiceCredentialsGenerator svr2CredentialGenerator,
final ExternalServiceCredentialsGenerator svr3CredentialGenerator, final ExternalServiceCredentialsGenerator svr3CredentialGenerator,
@ -73,7 +70,6 @@ public class RegistrationLockVerificationManager {
final PushNotificationManager pushNotificationManager, final PushNotificationManager pushNotificationManager,
final RateLimiters rateLimiters) { final RateLimiters rateLimiters) {
this.accounts = accounts; this.accounts = accounts;
this.clientPresenceManager = clientPresenceManager;
this.pubSubClientEventManager = pubSubClientEventManager; this.pubSubClientEventManager = pubSubClientEventManager;
this.svr2CredentialGenerator = svr2CredentialGenerator; this.svr2CredentialGenerator = svr2CredentialGenerator;
this.svr3CredentialGenerator = svr3CredentialGenerator; this.svr3CredentialGenerator = svr3CredentialGenerator;
@ -165,7 +161,6 @@ public class RegistrationLockVerificationManager {
} }
final List<Byte> deviceIds = updatedAccount.getDevices().stream().map(Device::getId).toList(); final List<Byte> deviceIds = updatedAccount.getDevices().stream().map(Device::getId).toList();
clientPresenceManager.disconnectAllPresences(updatedAccount.getUuid(), deviceIds);
pubSubClientEventManager.requestDisconnection(updatedAccount.getUuid(), deviceIds); pubSubClientEventManager.requestDisconnection(updatedAccount.getUuid(), deviceIds);
try { try {

View File

@ -9,7 +9,6 @@ import org.glassfish.jersey.server.monitoring.ApplicationEvent;
import org.glassfish.jersey.server.monitoring.ApplicationEventListener; import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent; import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener; import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -21,11 +20,9 @@ public class WebsocketRefreshApplicationEventListener implements ApplicationEven
private final WebsocketRefreshRequestEventListener websocketRefreshRequestEventListener; private final WebsocketRefreshRequestEventListener websocketRefreshRequestEventListener;
public WebsocketRefreshApplicationEventListener(final AccountsManager accountsManager, public WebsocketRefreshApplicationEventListener(final AccountsManager accountsManager,
final ClientPresenceManager clientPresenceManager,
final PubSubClientEventManager pubSubClientEventManager) { final PubSubClientEventManager pubSubClientEventManager) {
this.websocketRefreshRequestEventListener = new WebsocketRefreshRequestEventListener(clientPresenceManager, this.websocketRefreshRequestEventListener = new WebsocketRefreshRequestEventListener(pubSubClientEventManager,
pubSubClientEventManager,
new LinkedDeviceRefreshRequirementProvider(accountsManager), new LinkedDeviceRefreshRequirementProvider(accountsManager),
new PhoneNumberChangeRefreshRequirementProvider(accountsManager)); new PhoneNumberChangeRefreshRequirementProvider(accountsManager));
} }

View File

@ -19,12 +19,10 @@ import org.glassfish.jersey.server.monitoring.RequestEvent.Type;
import org.glassfish.jersey.server.monitoring.RequestEventListener; import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
public class WebsocketRefreshRequestEventListener implements RequestEventListener { public class WebsocketRefreshRequestEventListener implements RequestEventListener {
private final ClientPresenceManager clientPresenceManager;
private final PubSubClientEventManager pubSubClientEventManager; private final PubSubClientEventManager pubSubClientEventManager;
private final WebsocketRefreshRequirementProvider[] providers; private final WebsocketRefreshRequirementProvider[] providers;
@ -37,11 +35,9 @@ public class WebsocketRefreshRequestEventListener implements RequestEventListene
private static final Logger logger = LoggerFactory.getLogger(WebsocketRefreshRequestEventListener.class); private static final Logger logger = LoggerFactory.getLogger(WebsocketRefreshRequestEventListener.class);
public WebsocketRefreshRequestEventListener( public WebsocketRefreshRequestEventListener(
final ClientPresenceManager clientPresenceManager,
final PubSubClientEventManager pubSubClientEventManager, final PubSubClientEventManager pubSubClientEventManager,
final WebsocketRefreshRequirementProvider... providers) { final WebsocketRefreshRequirementProvider... providers) {
this.clientPresenceManager = clientPresenceManager;
this.pubSubClientEventManager = pubSubClientEventManager; this.pubSubClientEventManager = pubSubClientEventManager;
this.providers = providers; this.providers = providers;
} }
@ -64,7 +60,6 @@ public class WebsocketRefreshRequestEventListener implements RequestEventListene
.forEach(pair -> { .forEach(pair -> {
try { try {
displacedDevices.incrementAndGet(); displacedDevices.incrementAndGet();
clientPresenceManager.disconnectPresence(pair.first(), pair.second());
pubSubClientEventManager.requestDisconnection(pair.first(), List.of(pair.second())); pubSubClientEventManager.requestDisconnection(pair.first(), List.of(pair.second()));
} catch (final Exception e) { } catch (final Exception e) {
logger.error("Could not displace device presence", e); logger.error("Could not displace device presence", e);

View File

@ -22,7 +22,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.websocket.auth.ReadOnly; import org.whispersystems.websocket.auth.ReadOnly;
import org.whispersystems.websocket.session.WebSocketSession; import org.whispersystems.websocket.session.WebSocketSession;

View File

@ -1,390 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.push;
import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.lifecycle.Managed;
import io.lettuce.core.LettuceFutures;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.cluster.SlotHash;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import io.lettuce.core.cluster.event.ClusterTopologyChangedEvent;
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
import io.lettuce.core.cluster.pubsub.RedisClusterPubSubAdapter;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubClusterConnection;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.storage.Device;
/**
* The client presence manager keeps track of which clients are actively connected and "present" to receive messages.
* Only one client per account/device may be present at a time; if a second client for the same account/device declares
* its presence, the previous client is displaced.
* <p/>
* The client presence manager depends on Redis keyspace notifications and requires that the Redis instance support at
* least the following notification types: {@code K$z}.
*/
public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, String> implements Managed {
private final String managerId = UUID.randomUUID().toString();
private final String connectedClientSetKey = getConnectedClientSetKey(managerId);
private final FaultTolerantRedisClusterClient presenceCluster;
private final FaultTolerantPubSubClusterConnection<String, String> pubSubConnection;
private final ClusterLuaScript clearPresenceScript;
private final ClusterLuaScript renewPresenceScript;
private final ExecutorService keyspaceNotificationExecutorService;
private final ScheduledExecutorService scheduledExecutorService;
private ScheduledFuture<?> pruneMissingPeersFuture;
private final Map<String, DisplacedPresenceListener> displacementListenersByPresenceKey = new ConcurrentHashMap<>();
private final Map<String, CompletionStage<?>> pendingPresenceSetsByPresenceKey = new ConcurrentHashMap<>();
private final Timer checkPresenceTimer;
private final Timer setPresenceTimer;
private final Timer clearPresenceTimer;
private final Timer prunePeersTimer;
private final Counter pruneClientMeter;
private final Counter remoteDisplacementMeter;
private final Counter pubSubMessageMeter;
private final Counter displacementListenerAlreadyRemovedCounter;
private static final int PRUNE_PEERS_INTERVAL_SECONDS = (int) Duration.ofSeconds(30).toSeconds();
private static final int PRESENCE_EXPIRATION_SECONDS = (int) Duration.ofMinutes(11).toSeconds();
static final String MANAGER_SET_KEY = "presence::managers";
private static final Logger log = LoggerFactory.getLogger(ClientPresenceManager.class);
public ClientPresenceManager(final FaultTolerantRedisClusterClient presenceCluster,
final ScheduledExecutorService scheduledExecutorService,
final ExecutorService keyspaceNotificationExecutorService) throws IOException {
this.presenceCluster = presenceCluster;
this.pubSubConnection = this.presenceCluster.createPubSubConnection();
this.clearPresenceScript = ClusterLuaScript.fromResource(presenceCluster, "lua/clear_presence.lua",
ScriptOutputType.INTEGER);
this.renewPresenceScript = ClusterLuaScript.fromResource(presenceCluster, "lua/renew_presence.lua",
ScriptOutputType.VALUE);
this.scheduledExecutorService = scheduledExecutorService;
this.keyspaceNotificationExecutorService = keyspaceNotificationExecutorService;
Metrics.gauge(name(getClass(), "localClientCount"), this, ignored -> displacementListenersByPresenceKey.size());
this.checkPresenceTimer = Metrics.timer(name(getClass(), "checkPresence"));
this.setPresenceTimer = Metrics.timer(name(getClass(), "setPresence"));
this.clearPresenceTimer = Metrics.timer(name(getClass(), "clearPresence"));
this.prunePeersTimer = Metrics.timer(name(getClass(), "prunePeers"));
this.pruneClientMeter = Metrics.counter(name(getClass(), "pruneClient"));
this.remoteDisplacementMeter = Metrics.counter(name(getClass(), "remoteDisplacement"));
this.pubSubMessageMeter = Metrics.counter(name(getClass(), "pubSubMessage"));
this.displacementListenerAlreadyRemovedCounter = Metrics.counter(
name(getClass(), "displacementListenerAlreadyRemoved"));
}
@VisibleForTesting
FaultTolerantPubSubClusterConnection<String, String> getPubSubConnection() {
return pubSubConnection;
}
@Override
public void start() {
pubSubConnection.usePubSubConnection(connection -> {
connection.addListener(this);
final String presenceChannel = getManagerPresenceChannel(managerId);
final int slot = SlotHash.getSlot(presenceChannel);
connection.sync().nodes(node -> node.is(RedisClusterNode.NodeFlag.UPSTREAM) && node.hasSlot(slot))
.commands()
.subscribe(presenceChannel);
});
pubSubConnection.subscribeToClusterTopologyChangedEvents(this::resubscribeAll);
presenceCluster.useCluster(connection -> connection.sync().sadd(MANAGER_SET_KEY, managerId));
pruneMissingPeersFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
try {
pruneMissingPeers();
} catch (final Throwable t) {
log.warn("Failed to prune missing peers", t);
}
}, new Random().nextInt(PRUNE_PEERS_INTERVAL_SECONDS), PRUNE_PEERS_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
@Override
public void stop() {
pubSubConnection.usePubSubConnection(connection -> connection.removeListener(this));
if (pruneMissingPeersFuture != null) {
pruneMissingPeersFuture.cancel(false);
}
for (final String presenceKey : displacementListenersByPresenceKey.keySet()) {
clearPresence(presenceKey);
}
presenceCluster.useCluster(connection -> {
connection.sync().srem(MANAGER_SET_KEY, managerId);
connection.sync().del(getConnectedClientSetKey(managerId));
});
pubSubConnection.usePubSubConnection(
connection -> connection.sync().upstream().commands().unsubscribe(getManagerPresenceChannel(managerId)));
}
public void setPresent(final UUID accountUuid, final byte deviceId,
final DisplacedPresenceListener displacementListener) {
setPresenceTimer.record(() -> {
final String presenceKey = getPresenceKey(accountUuid, deviceId);
displacePresence(presenceKey, true);
displacementListenersByPresenceKey.put(presenceKey, displacementListener);
final CompletableFuture<Void> presenceFuture = new CompletableFuture<>();
final CompletionStage<?> previousFuture = pendingPresenceSetsByPresenceKey.put(presenceKey, presenceFuture);
if (previousFuture != null) {
log.debug("Another presence is already pending for {}:{}", accountUuid, deviceId);
}
subscribeForRemotePresenceChanges(presenceKey);
presenceCluster.withCluster(connection -> {
final RedisAdvancedClusterAsyncCommands<String, String> commands = connection.async();
commands.sadd(connectedClientSetKey, presenceKey);
return commands.setex(presenceKey, PRESENCE_EXPIRATION_SECONDS, managerId);
}).whenComplete((result, throwable) -> {
if (throwable != null) {
presenceFuture.completeExceptionally(throwable);
} else {
presenceFuture.complete(null);
}
});
presenceFuture.whenComplete(
(ignored, throwable) -> pendingPresenceSetsByPresenceKey.remove(presenceKey, presenceFuture));
});
}
public void renewPresence(final UUID accountUuid, final byte deviceId) {
renewPresenceScript.execute(List.of(getPresenceKey(accountUuid, deviceId)),
List.of(managerId, String.valueOf(PRESENCE_EXPIRATION_SECONDS)));
}
public void disconnectAllPresences(final UUID accountUuid, final List<Byte> deviceIds) {
List<String> presenceKeys = new ArrayList<>();
deviceIds.forEach(deviceId -> {
String presenceKey = getPresenceKey(accountUuid, deviceId);
if (isLocallyPresent(accountUuid, deviceId)) {
displacePresence(presenceKey, false);
}
presenceKeys.add(presenceKey);
});
presenceCluster.useCluster(connection -> {
List<RedisFuture<Long>> futures = presenceKeys.stream().map(key -> connection.async().del(key)).toList();
LettuceFutures.awaitAll(connection.getTimeout(), futures.toArray(new RedisFuture[0]));
});
}
public void disconnectAllPresencesForUuid(final UUID accountUuid) {
disconnectAllPresences(accountUuid, Device.ALL_POSSIBLE_DEVICE_IDS);
}
public void disconnectPresence(final UUID accountUuid, final byte deviceId) {
disconnectAllPresences(accountUuid, List.of(deviceId));
}
private void displacePresence(final String presenceKey, final boolean connectedElsewhere) {
final DisplacedPresenceListener displacementListener = displacementListenersByPresenceKey.get(presenceKey);
if (displacementListener != null) {
displacementListener.handleDisplacement(connectedElsewhere);
}
clearPresence(presenceKey);
}
public boolean isPresent(final UUID accountUuid, final byte deviceId) {
return checkPresenceTimer.record(() ->
presenceCluster.withCluster(connection ->
connection.sync().exists(getPresenceKey(accountUuid, deviceId))) == 1);
}
public boolean isLocallyPresent(final UUID accountUuid, final byte deviceId) {
return displacementListenersByPresenceKey.containsKey(getPresenceKey(accountUuid, deviceId));
}
public boolean clearPresence(final UUID accountUuid, final byte deviceId, final DisplacedPresenceListener listener) {
final String presenceKey = getPresenceKey(accountUuid, deviceId);
if (displacementListenersByPresenceKey.remove(presenceKey, listener)) {
return clearPresence(presenceKey);
} else {
displacementListenerAlreadyRemovedCounter.increment();
return false;
}
}
private boolean clearPresence(final String presenceKey) {
return clearPresenceTimer.record(() -> {
displacementListenersByPresenceKey.remove(presenceKey);
unsubscribeFromRemotePresenceChanges(presenceKey);
final boolean removed = clearPresenceScript.execute(List.of(presenceKey), List.of(managerId)) != null;
presenceCluster.useCluster(connection -> connection.sync().srem(connectedClientSetKey, presenceKey));
return removed;
});
}
private void subscribeForRemotePresenceChanges(final String presenceKey) {
final int slot = SlotHash.getSlot(presenceKey);
pubSubConnection.usePubSubConnection(
connection -> connection.sync().nodes(node -> node.is(RedisClusterNode.NodeFlag.UPSTREAM) && node.hasSlot(slot))
.commands()
.subscribe(getKeyspaceNotificationChannel(presenceKey)));
}
private void resubscribeAll(final ClusterTopologyChangedEvent event) {
for (final String presenceKey : displacementListenersByPresenceKey.keySet()) {
subscribeForRemotePresenceChanges(presenceKey);
}
}
private void unsubscribeFromRemotePresenceChanges(final String presenceKey) {
pubSubConnection.usePubSubConnection(
connection -> connection.sync().upstream().commands().unsubscribe(getKeyspaceNotificationChannel(presenceKey)));
}
void pruneMissingPeers() {
prunePeersTimer.record(() -> {
final Set<String> peerIds = presenceCluster.withCluster(
connection -> connection.sync().smembers(MANAGER_SET_KEY));
peerIds.remove(managerId);
for (final String peerId : peerIds) {
final boolean peerMissing = presenceCluster.withCluster(
connection -> connection.sync().publish(getManagerPresenceChannel(peerId), "ping") == 0);
if (peerMissing) {
log.debug("Presence manager {} did not respond to ping", peerId);
final String connectedClientsKey = getConnectedClientSetKey(peerId);
String presenceKey;
while ((presenceKey = presenceCluster.withCluster(connection -> connection.sync().spop(connectedClientsKey)))
!= null) {
clearPresenceScript.execute(List.of(presenceKey), List.of(peerId));
pruneClientMeter.increment();
}
presenceCluster.useCluster(connection -> {
connection.sync().del(connectedClientsKey);
connection.sync().srem(MANAGER_SET_KEY, peerId);
});
}
}
});
}
@Override
public void message(final RedisClusterNode node, final String channel, final String message) {
pubSubMessageMeter.increment();
if (channel.startsWith("__keyspace@0__:presence::{")) {
if ("set".equals(message) || "del".equals(message)) {
// "set" might mean the client has connected to another host, although it might just be our own `set`,
// because we subscribe for changes before setting the key.
// for "del", another process has indicated the client should be disconnected
final boolean maybeConnectedElsewhere = "set".equals(message);
// At this point, we're on a Lettuce IO thread and need to dispatch to a separate thread before making
// synchronous Lettuce calls to avoid deadlocking.
keyspaceNotificationExecutorService.execute(() -> {
final String clientPresenceKey = channel.substring("__keyspace@0__:".length());
final CompletionStage<?> pendingConnection = pendingPresenceSetsByPresenceKey.getOrDefault(clientPresenceKey,
CompletableFuture.completedFuture(null));
pendingConnection.thenCompose(ignored -> {
if (maybeConnectedElsewhere) {
return presenceCluster.withCluster(connection -> connection.async().get(clientPresenceKey))
.thenApply(currentManagerId -> !managerId.equals(currentManagerId));
}
return CompletableFuture.completedFuture(true);
})
.exceptionally(ignored -> true)
.thenAcceptAsync(shouldDisplace -> {
if (shouldDisplace) {
try {
displacePresence(clientPresenceKey, maybeConnectedElsewhere);
remoteDisplacementMeter.increment();
} catch (final Exception e) {
log.warn("Error displacing presence", e);
}
}
}, keyspaceNotificationExecutorService);
});
}
}
}
@VisibleForTesting
String getManagerId() {
return managerId;
}
@VisibleForTesting
static String getPresenceKey(final UUID accountUuid, final byte deviceId) {
return "presence::{" + accountUuid.toString() + "::" + deviceId + "}";
}
private static String getKeyspaceNotificationChannel(final String presenceKey) {
return "__keyspace@0__:" + presenceKey;
}
@VisibleForTesting
static String getConnectedClientSetKey(final String managerId) {
return "presence::clients::" + managerId;
}
@VisibleForTesting
static String getManagerPresenceChannel(final String managerId) {
return "presence::manager::" + managerId;
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.push;
/**
* A displaced presence listener is notified when a specific client's presence has been displaced because the same
* client opened a newer connection to the Signal service.
*/
@FunctionalInterface
public interface DisplacedPresenceListener {
void handleDisplacement(boolean connectedElsewhere);
}

View File

@ -68,19 +68,17 @@ import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfigurati
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException; import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.DeviceInfo; import org.whispersystems.textsecuregcm.entities.DeviceInfo;
import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey; import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey; import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.entities.RemoteAttachment; import org.whispersystems.textsecuregcm.entities.RemoteAttachment;
import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubConnection; import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubConnection;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.redis.RedisOperation; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryException; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryException;
@ -126,12 +124,10 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
private final ProfilesManager profilesManager; private final ProfilesManager profilesManager;
private final SecureStorageClient secureStorageClient; private final SecureStorageClient secureStorageClient;
private final SecureValueRecovery2Client secureValueRecovery2Client; private final SecureValueRecovery2Client secureValueRecovery2Client;
private final ClientPresenceManager clientPresenceManager;
private final PubSubClientEventManager pubSubClientEventManager; private final PubSubClientEventManager pubSubClientEventManager;
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager; private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
private final ClientPublicKeysManager clientPublicKeysManager; private final ClientPublicKeysManager clientPublicKeysManager;
private final Executor accountLockExecutor; private final Executor accountLockExecutor;
private final Executor clientPresenceExecutor;
private final Clock clock; private final Clock clock;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
@ -206,12 +202,10 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
final ProfilesManager profilesManager, final ProfilesManager profilesManager,
final SecureStorageClient secureStorageClient, final SecureStorageClient secureStorageClient,
final SecureValueRecovery2Client secureValueRecovery2Client, final SecureValueRecovery2Client secureValueRecovery2Client,
final ClientPresenceManager clientPresenceManager,
final PubSubClientEventManager pubSubClientEventManager, final PubSubClientEventManager pubSubClientEventManager,
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager, final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
final ClientPublicKeysManager clientPublicKeysManager, final ClientPublicKeysManager clientPublicKeysManager,
final Executor accountLockExecutor, final Executor accountLockExecutor,
final Executor clientPresenceExecutor,
final Clock clock, final Clock clock,
final byte[] linkDeviceSecret, final byte[] linkDeviceSecret,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
@ -225,12 +219,10 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
this.profilesManager = profilesManager; this.profilesManager = profilesManager;
this.secureStorageClient = secureStorageClient; this.secureStorageClient = secureStorageClient;
this.secureValueRecovery2Client = secureValueRecovery2Client; this.secureValueRecovery2Client = secureValueRecovery2Client;
this.clientPresenceManager = clientPresenceManager;
this.pubSubClientEventManager = pubSubClientEventManager; this.pubSubClientEventManager = pubSubClientEventManager;
this.registrationRecoveryPasswordsManager = requireNonNull(registrationRecoveryPasswordsManager); this.registrationRecoveryPasswordsManager = requireNonNull(registrationRecoveryPasswordsManager);
this.clientPublicKeysManager = clientPublicKeysManager; this.clientPublicKeysManager = clientPublicKeysManager;
this.accountLockExecutor = accountLockExecutor; this.accountLockExecutor = accountLockExecutor;
this.clientPresenceExecutor = clientPresenceExecutor;
this.clock = requireNonNull(clock); this.clock = requireNonNull(clock);
this.dynamicConfigurationManager = dynamicConfigurationManager; this.dynamicConfigurationManager = dynamicConfigurationManager;
@ -333,10 +325,7 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
keysManager.deleteSingleUsePreKeys(pni), keysManager.deleteSingleUsePreKeys(pni),
messagesManager.clear(aci), messagesManager.clear(aci),
profilesManager.deleteAll(aci)) profilesManager.deleteAll(aci))
.thenRunAsync(() -> { .thenCompose(ignored -> pubSubClientEventManager.requestDisconnection(aci))
clientPresenceManager.disconnectAllPresencesForUuid(aci);
pubSubClientEventManager.requestDisconnection(aci);
}, clientPresenceExecutor)
.thenCompose(ignored -> accounts.reclaimAccount(e.getExistingAccount(), account, additionalWriteItems)) .thenCompose(ignored -> accounts.reclaimAccount(e.getExistingAccount(), account, additionalWriteItems))
.thenCompose(ignored -> { .thenCompose(ignored -> {
// We should have cleared all messages before overwriting the old account, but more may have arrived // We should have cleared all messages before overwriting the old account, but more may have arrived
@ -598,12 +587,11 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
return CompletableFuture.failedFuture(throwable); return CompletableFuture.failedFuture(throwable);
}) })
.whenCompleteAsync((ignored, throwable) -> { .whenComplete((ignored, throwable) -> {
if (throwable == null) { if (throwable == null) {
RedisOperation.unchecked(() -> clientPresenceManager.disconnectPresence(accountIdentifier, deviceId));
pubSubClientEventManager.requestDisconnection(accountIdentifier, List.of(deviceId)); pubSubClientEventManager.requestDisconnection(accountIdentifier, List.of(deviceId));
} }
}, clientPresenceExecutor); });
} }
public Account changeNumber(final Account account, public Account changeNumber(final Account account,
@ -1248,11 +1236,7 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
registrationRecoveryPasswordsManager.removeForNumber(account.getNumber())) registrationRecoveryPasswordsManager.removeForNumber(account.getNumber()))
.thenCompose(ignored -> accounts.delete(account.getUuid(), additionalWriteItems)) .thenCompose(ignored -> accounts.delete(account.getUuid(), additionalWriteItems))
.thenCompose(ignored -> redisDeleteAsync(account)) .thenCompose(ignored -> redisDeleteAsync(account))
.thenRunAsync(() -> { .thenRun(() -> pubSubClientEventManager.requestDisconnection(account.getUuid()));
RedisOperation.unchecked(() -> clientPresenceManager.disconnectAllPresencesForUuid(account.getUuid()));
pubSubClientEventManager.requestDisconnection(account.getUuid());
}, clientPresenceExecutor);
} }
private String getAccountMapKey(String key) { private String getAccountMapKey(String key) {

View File

@ -9,16 +9,12 @@ import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Tags;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor; import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor;
import org.whispersystems.textsecuregcm.metrics.MessageMetrics; import org.whispersystems.textsecuregcm.metrics.MessageMetrics;
import org.whispersystems.textsecuregcm.metrics.OpenWebSocketCounter; import org.whispersystems.textsecuregcm.metrics.OpenWebSocketCounter;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler;
@ -38,8 +34,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
private static final String AUTHENTICATED_TAG_NAME = "authenticated"; private static final String AUTHENTICATED_TAG_NAME = "authenticated";
private static final long RENEW_PRESENCE_INTERVAL_MINUTES = 5;
private static final Logger log = LoggerFactory.getLogger(AuthenticatedConnectListener.class); private static final Logger log = LoggerFactory.getLogger(AuthenticatedConnectListener.class);
private final ReceiptSender receiptSender; private final ReceiptSender receiptSender;
@ -47,7 +41,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
private final MessageMetrics messageMetrics; private final MessageMetrics messageMetrics;
private final PushNotificationManager pushNotificationManager; private final PushNotificationManager pushNotificationManager;
private final PushNotificationScheduler pushNotificationScheduler; private final PushNotificationScheduler pushNotificationScheduler;
private final ClientPresenceManager clientPresenceManager;
private final PubSubClientEventManager pubSubClientEventManager; private final PubSubClientEventManager pubSubClientEventManager;
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
private final Scheduler messageDeliveryScheduler; private final Scheduler messageDeliveryScheduler;
@ -62,7 +55,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
MessageMetrics messageMetrics, MessageMetrics messageMetrics,
PushNotificationManager pushNotificationManager, PushNotificationManager pushNotificationManager,
PushNotificationScheduler pushNotificationScheduler, PushNotificationScheduler pushNotificationScheduler,
ClientPresenceManager clientPresenceManager,
PubSubClientEventManager pubSubClientEventManager, PubSubClientEventManager pubSubClientEventManager,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
@ -73,7 +65,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
this.messageMetrics = messageMetrics; this.messageMetrics = messageMetrics;
this.pushNotificationManager = pushNotificationManager; this.pushNotificationManager = pushNotificationManager;
this.pushNotificationScheduler = pushNotificationScheduler; this.pushNotificationScheduler = pushNotificationScheduler;
this.clientPresenceManager = clientPresenceManager;
this.pubSubClientEventManager = pubSubClientEventManager; this.pubSubClientEventManager = pubSubClientEventManager;
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
this.messageDeliveryScheduler = messageDeliveryScheduler; this.messageDeliveryScheduler = messageDeliveryScheduler;
@ -110,21 +101,11 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
clientReleaseManager, clientReleaseManager,
messageDeliveryLoopMonitor); messageDeliveryLoopMonitor);
final AtomicReference<ScheduledFuture<?>> renewPresenceFutureReference = new AtomicReference<>();
context.addWebsocketClosedListener((closingContext, statusCode, reason) -> { context.addWebsocketClosedListener((closingContext, statusCode, reason) -> {
final ScheduledFuture<?> renewPresenceFuture = renewPresenceFutureReference.get();
if (renewPresenceFuture != null) {
renewPresenceFuture.cancel(false);
}
// We begin the shutdown process by removing this client's "presence," which means it will again begin to // We begin the shutdown process by removing this client's "presence," which means it will again begin to
// receive push notifications for inbound messages. We should do this first because, at this point, the // receive push notifications for inbound messages. We should do this first because, at this point, the
// connection has already closed and attempts to actually deliver a message via the connection will not succeed. // connection has already closed and attempts to actually deliver a message via the connection will not succeed.
// It's preferable to start sending push notifications as soon as possible. // It's preferable to start sending push notifications as soon as possible.
RedisOperation.unchecked(() -> clientPresenceManager.clearPresence(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection));
pubSubClientEventManager.handleClientDisconnected(auth.getAccount().getUuid(), pubSubClientEventManager.handleClientDisconnected(auth.getAccount().getUuid(),
auth.getAuthenticatedDevice().getId()); auth.getAuthenticatedDevice().getId());
@ -153,14 +134,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
// Finally, we register this client's presence, which suppresses push notifications. We do this last because // Finally, we register this client's presence, which suppresses push notifications. We do this last because
// receiving extra push notifications is generally preferable to missing out on a push notification. // receiving extra push notifications is generally preferable to missing out on a push notification.
clientPresenceManager.setPresent(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection);
pubSubClientEventManager.handleClientConnected(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection); pubSubClientEventManager.handleClientConnected(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection);
renewPresenceFutureReference.set(scheduledExecutorService.scheduleAtFixedRate(() -> RedisOperation.unchecked(() ->
clientPresenceManager.renewPresence(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId())),
RENEW_PRESENCE_INTERVAL_MINUTES,
RENEW_PRESENCE_INTERVAL_MINUTES,
TimeUnit.MINUTES));
} catch (final Exception e) { } catch (final Exception e) {
log.warn("Failed to initialize websocket", e); log.warn("Failed to initialize websocket", e);
context.getClient().close(1011, "Unexpected error initializing connection"); context.getClient().close(1011, "Unexpected error initializing connection");

View File

@ -46,7 +46,6 @@ import org.whispersystems.textsecuregcm.metrics.MessageMetrics;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientEventListener; import org.whispersystems.textsecuregcm.push.ClientEventListener;
import org.whispersystems.textsecuregcm.push.DisplacedPresenceListener;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
@ -64,7 +63,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
public class WebSocketConnection implements MessageAvailabilityListener, DisplacedPresenceListener, ClientEventListener { public class WebSocketConnection implements MessageAvailabilityListener, ClientEventListener {
private static final DistributionSummary messageTime = Metrics.summary( private static final DistributionSummary messageTime = Metrics.summary(
name(MessageController.class, "messageDeliveryDuration")); name(MessageController.class, "messageDeliveryDuration"));
@ -513,24 +512,11 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
.increment(); .increment();
} }
@Override
public void handleDisplacement(final boolean connectedElsewhere) {
final Tags tags = Tags.of(
UserAgentTagUtil.getPlatformTag(client.getUserAgent()),
Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere)),
Tag.of(PRESENCE_MANAGER_TAG, "legacy")
);
Metrics.counter(DISPLACEMENT_COUNTER_NAME, tags).increment();
}
@Override @Override
public void handleConnectionDisplaced(final boolean connectedElsewhere) { public void handleConnectionDisplaced(final boolean connectedElsewhere) {
final Tags tags = Tags.of( final Tags tags = Tags.of(
UserAgentTagUtil.getPlatformTag(client.getUserAgent()), UserAgentTagUtil.getPlatformTag(client.getUserAgent()),
Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere)), Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere)));
Tag.of(PRESENCE_MANAGER_TAG, "pubsub")
);
Metrics.counter(DISPLACEMENT_COUNTER_NAME, tags).increment(); Metrics.counter(DISPLACEMENT_COUNTER_NAME, tags).increment();

View File

@ -35,7 +35,6 @@ import org.whispersystems.textsecuregcm.experiment.PushNotificationExperimentSam
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher; import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher;
import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
@ -77,7 +76,6 @@ record CommandDependencies(
ReportMessageManager reportMessageManager, ReportMessageManager reportMessageManager,
MessagesCache messagesCache, MessagesCache messagesCache,
MessagesManager messagesManager, MessagesManager messagesManager,
ClientPresenceManager clientPresenceManager,
KeysManager keysManager, KeysManager keysManager,
APNSender apnSender, APNSender apnSender,
FcmSender fcmSender, FcmSender fcmSender,
@ -118,8 +116,6 @@ record CommandDependencies(
FaultTolerantRedisClient pubsubClient = FaultTolerantRedisClient pubsubClient =
configuration.getRedisPubSubConfiguration().build("pubsub", redisClientResourcesBuilder.build()); configuration.getRedisPubSubConfiguration().build("pubsub", redisClientResourcesBuilder.build());
ScheduledExecutorService recurringJobExecutor = environment.lifecycle()
.scheduledExecutorService(name(name, "recurringJob-%d")).threads(2).build();
Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService( Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService(
environment.lifecycle().executorService("messageDelivery").minThreads(4).maxThreads(4).build()); environment.lifecycle().executorService("messageDelivery").minThreads(4).maxThreads(4).build());
ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle() ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle()
@ -132,8 +128,6 @@ record CommandDependencies(
.executorService(name(name, "storageService-%d")).maxThreads(8).minThreads(8).build(); .executorService(name(name, "storageService-%d")).maxThreads(8).minThreads(8).build();
ExecutorService accountLockExecutor = environment.lifecycle() ExecutorService accountLockExecutor = environment.lifecycle()
.executorService(name(name, "accountLock-%d")).minThreads(8).maxThreads(8).build(); .executorService(name(name, "accountLock-%d")).minThreads(8).maxThreads(8).build();
ExecutorService clientPresenceExecutor = environment.lifecycle()
.executorService(name(name, "clientPresence-%d")).minThreads(8).maxThreads(8).build();
ExecutorService remoteStorageHttpExecutor = environment.lifecycle() ExecutorService remoteStorageHttpExecutor = environment.lifecycle()
.executorService(name(name, "remoteStorage-%d")) .executorService(name(name, "remoteStorage-%d"))
.minThreads(0).maxThreads(Integer.MAX_VALUE).workQueue(new SynchronousQueue<>()) .minThreads(0).maxThreads(Integer.MAX_VALUE).workQueue(new SynchronousQueue<>())
@ -215,8 +209,6 @@ record CommandDependencies(
configuration.getSvr2Configuration()); configuration.getSvr2Configuration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator,
storageServiceExecutor, storageServiceRetryExecutor, configuration.getSecureStorageServiceConfiguration()); storageServiceExecutor, storageServiceRetryExecutor, configuration.getSecureStorageServiceConfiguration());
ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster,
recurringJobExecutor, keyspaceNotificationDispatchExecutor);
PubSubClientEventManager pubSubClientEventManager = new PubSubClientEventManager(messagesCluster, clientEventExecutor); PubSubClientEventManager pubSubClientEventManager = new PubSubClientEventManager(messagesCluster, clientEventExecutor);
MessagesCache messagesCache = new MessagesCache(messagesCluster, keyspaceNotificationDispatchExecutor, MessagesCache messagesCache = new MessagesCache(messagesCluster, keyspaceNotificationDispatchExecutor,
messageDeliveryScheduler, messageDeletionExecutor, Clock.systemUTC(), dynamicConfigurationManager); messageDeliveryScheduler, messageDeletionExecutor, Clock.systemUTC(), dynamicConfigurationManager);
@ -234,8 +226,8 @@ record CommandDependencies(
new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor); new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor);
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
pubsubClient, accountLockManager, keys, messagesManager, profilesManager, pubsubClient, accountLockManager, keys, messagesManager, profilesManager,
secureStorageClient, secureValueRecovery2Client, clientPresenceManager, pubSubClientEventManager, secureStorageClient, secureValueRecovery2Client, pubSubClientEventManager,
registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, clientPresenceExecutor, registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor,
clock, configuration.getLinkDeviceSecretConfiguration().secret().value(), dynamicConfigurationManager); clock, configuration.getLinkDeviceSecretConfiguration().secret().value(), dynamicConfigurationManager);
RateLimiters rateLimiters = RateLimiters.createAndValidate(configuration.getLimitsConfiguration(), RateLimiters rateLimiters = RateLimiters.createAndValidate(configuration.getLimitsConfiguration(),
dynamicConfigurationManager, rateLimitersCluster); dynamicConfigurationManager, rateLimitersCluster);
@ -272,7 +264,6 @@ record CommandDependencies(
environment.lifecycle().manage(apnSender); environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(messagesCache); environment.lifecycle().manage(messagesCache);
environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(pubSubClientEventManager); environment.lifecycle().manage(pubSubClientEventManager);
environment.lifecycle().manage(new ManagedAwsCrt()); environment.lifecycle().manage(new ManagedAwsCrt());
@ -282,7 +273,6 @@ record CommandDependencies(
reportMessageManager, reportMessageManager,
messagesCache, messagesCache,
messagesManager, messagesManager,
clientPresenceManager,
keys, keys,
apnSender, apnSender,
fcmSender, fcmSender,

View File

@ -59,7 +59,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -96,7 +95,6 @@ class LinkedDeviceRefreshRequirementProviderTest {
.build(); .build();
private AccountsManager accountsManager; private AccountsManager accountsManager;
private ClientPresenceManager clientPresenceManager;
private PubSubClientEventManager pubSubClientEventManager; private PubSubClientEventManager pubSubClientEventManager;
private LinkedDeviceRefreshRequirementProvider provider; private LinkedDeviceRefreshRequirementProvider provider;
@ -104,13 +102,12 @@ class LinkedDeviceRefreshRequirementProviderTest {
@BeforeEach @BeforeEach
void setup() { void setup() {
accountsManager = mock(AccountsManager.class); accountsManager = mock(AccountsManager.class);
clientPresenceManager = mock(ClientPresenceManager.class);
pubSubClientEventManager = mock(PubSubClientEventManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class);
provider = new LinkedDeviceRefreshRequirementProvider(accountsManager); provider = new LinkedDeviceRefreshRequirementProvider(accountsManager);
final WebsocketRefreshRequestEventListener listener = final WebsocketRefreshRequestEventListener listener =
new WebsocketRefreshRequestEventListener(clientPresenceManager, pubSubClientEventManager, provider); new WebsocketRefreshRequestEventListener(pubSubClientEventManager, provider);
when(applicationEventListener.onRequest(any())).thenReturn(listener); when(applicationEventListener.onRequest(any())).thenReturn(listener);
@ -121,9 +118,6 @@ class LinkedDeviceRefreshRequirementProviderTest {
.forEach(deviceId -> account.addDevice(DevicesHelper.createDevice((byte) deviceId))); .forEach(deviceId -> account.addDevice(DevicesHelper.createDevice((byte) deviceId)));
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
account.getDevices()
.forEach(device -> when(clientPresenceManager.isPresent(uuid, device.getId())).thenReturn(true));
} }
@Test @Test
@ -145,10 +139,6 @@ class LinkedDeviceRefreshRequirementProviderTest {
assertEquals(initialDeviceCount + addedDeviceNames.size(), account.getDevices().size()); assertEquals(initialDeviceCount + addedDeviceNames.size(), account.getDevices().size());
verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 1);
verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 2);
verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 3);
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 1)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 1));
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 2)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 2));
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 3)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 3));
@ -180,11 +170,10 @@ class LinkedDeviceRefreshRequirementProviderTest {
assertEquals(200, response.getStatus()); assertEquals(200, response.getStatus());
initialDeviceIds.forEach(deviceId -> { initialDeviceIds.forEach(deviceId -> {
verify(clientPresenceManager).disconnectPresence(account.getUuid(), deviceId);
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(deviceId)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(deviceId));
}); });
verifyNoMoreInteractions(clientPresenceManager); verifyNoMoreInteractions(pubSubClientEventManager);
} }
@Test @Test

View File

@ -47,7 +47,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource;
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -75,7 +74,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
private static final AccountAuthenticator AUTHENTICATOR = mock(AccountAuthenticator.class); private static final AccountAuthenticator AUTHENTICATOR = mock(AccountAuthenticator.class);
private static final AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class); private static final AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class);
private static final ClientPresenceManager CLIENT_PRESENCE = mock(ClientPresenceManager.class);
private static final PubSubClientEventManager PUBSUB_CLIENT_PRESENCE = mock(PubSubClientEventManager.class); private static final PubSubClientEventManager PUBSUB_CLIENT_PRESENCE = mock(PubSubClientEventManager.class);
private WebSocketClient client; private WebSocketClient client;
@ -86,7 +84,7 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
@BeforeEach @BeforeEach
void setUp() throws Exception { void setUp() throws Exception {
reset(AUTHENTICATOR, CLIENT_PRESENCE, ACCOUNTS_MANAGER); reset(AUTHENTICATOR, ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE);
client = new WebSocketClient(); client = new WebSocketClient();
client.start(); client.start();
@ -125,9 +123,9 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
webSocketEnvironment.jersey().register(new RemoteAddressFilter()); webSocketEnvironment.jersey().register(new RemoteAddressFilter());
webSocketEnvironment.jersey() webSocketEnvironment.jersey()
.register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, CLIENT_PRESENCE, PUBSUB_CLIENT_PRESENCE)); .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE));
environment.jersey() environment.jersey()
.register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, CLIENT_PRESENCE, PUBSUB_CLIENT_PRESENCE)); .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE));
webSocketEnvironment.setConnectListener(webSocketSessionContext -> { webSocketEnvironment.setConnectListener(webSocketSessionContext -> {
}); });
@ -201,7 +199,7 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
// Event listeners can fire after responses are sent // Event listeners can fire after responses are sent
verify(ACCOUNTS_MANAGER, timeout(5000).times(1)).getByAccountIdentifier(eq(account1.getUuid())); verify(ACCOUNTS_MANAGER, timeout(5000).times(1)).getByAccountIdentifier(eq(account1.getUuid()));
verifyNoMoreInteractions(CLIENT_PRESENCE); verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE);
verifyNoMoreInteractions(ACCOUNTS_MANAGER); verifyNoMoreInteractions(ACCOUNTS_MANAGER);
} }
@ -215,10 +213,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
// Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses // Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses
// are sent, so use a timeout. // are sent, so use a timeout.
verify(CLIENT_PRESENCE, timeout(5000))
.disconnectPresence(eq(account1.getUuid()), eq(authenticatedDevice.getId()));
verifyNoMoreInteractions(CLIENT_PRESENCE);
verify(PUBSUB_CLIENT_PRESENCE, timeout(5000)) verify(PUBSUB_CLIENT_PRESENCE, timeout(5000))
.requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId())); .requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId()));
verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE); verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE);
@ -235,10 +229,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
// Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses // Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses
// are sent, so use a timeout. // are sent, so use a timeout.
verify(CLIENT_PRESENCE, timeout(5000))
.disconnectPresence(eq(account1.getUuid()), eq(authenticatedDevice.getId()));
verifyNoMoreInteractions(CLIENT_PRESENCE);
verify(PUBSUB_CLIENT_PRESENCE, timeout(5000)) verify(PUBSUB_CLIENT_PRESENCE, timeout(5000))
.requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId())); .requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId()));
verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE); verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE);
@ -255,7 +245,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
// Shouldn't even read the account if the method has not been annotated // Shouldn't even read the account if the method has not been annotated
verifyNoMoreInteractions(ACCOUNTS_MANAGER); verifyNoMoreInteractions(ACCOUNTS_MANAGER);
verifyNoMoreInteractions(CLIENT_PRESENCE);
} }
@ParameterizedTest @ParameterizedTest
@ -269,7 +258,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest {
// Shouldn't even read the account if the method has not been annotated // Shouldn't even read the account if the method has not been annotated
verifyNoMoreInteractions(ACCOUNTS_MANAGER); verifyNoMoreInteractions(ACCOUNTS_MANAGER);
verifyNoMoreInteractions(CLIENT_PRESENCE);
} }

View File

@ -33,7 +33,6 @@ import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest; import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
@ -47,7 +46,6 @@ import org.whispersystems.textsecuregcm.util.Pair;
class RegistrationLockVerificationManagerTest { class RegistrationLockVerificationManagerTest {
private final AccountsManager accountsManager = mock(AccountsManager.class); private final AccountsManager accountsManager = mock(AccountsManager.class);
private final ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class);
private final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); private final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class);
private final ExternalServiceCredentialsGenerator svr2CredentialsGenerator = mock( private final ExternalServiceCredentialsGenerator svr2CredentialsGenerator = mock(
ExternalServiceCredentialsGenerator.class); ExternalServiceCredentialsGenerator.class);
@ -58,7 +56,7 @@ class RegistrationLockVerificationManagerTest {
private static PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class); private static PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class); private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager( private final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
accountsManager, clientPresenceManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator, accountsManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator,
registrationRecoveryPasswordsManager, pushNotificationManager, rateLimiters); registrationRecoveryPasswordsManager, pushNotificationManager, rateLimiters);
private final RateLimiter pinLimiter = mock(RateLimiter.class); private final RateLimiter pinLimiter = mock(RateLimiter.class);
@ -109,7 +107,6 @@ class RegistrationLockVerificationManagerTest {
} else { } else {
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
} }
verify(clientPresenceManager).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID));
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(Device.PRIMARY_ID)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(Device.PRIMARY_ID));
try { try {
verify(pushNotificationManager).sendAttemptLoginNotification(any(), eq("failedRegistrationLock")); verify(pushNotificationManager).sendAttemptLoginNotification(any(), eq("failedRegistrationLock"));
@ -133,7 +130,6 @@ class RegistrationLockVerificationManagerTest {
verify(pushNotificationManager, never()).sendAttemptLoginNotification(any(), eq("failedRegistrationLock")); verify(pushNotificationManager, never()).sendAttemptLoginNotification(any(), eq("failedRegistrationLock"));
} catch (NotPushRegisteredException npre) {} } catch (NotPushRegisteredException npre) {}
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
verify(clientPresenceManager, never()).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID));
verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any());
}); });
} }
@ -172,7 +168,6 @@ class RegistrationLockVerificationManagerTest {
verify(account, never()).lockAuthTokenHash(); verify(account, never()).lockAuthTokenHash();
verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber());
verify(clientPresenceManager, never()).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID));
verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any());
} }

View File

@ -79,7 +79,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -111,7 +110,6 @@ class DeviceControllerTest {
private static final Account account = mock(Account.class); private static final Account account = mock(Account.class);
private static final Account maxedAccount = mock(Account.class); private static final Account maxedAccount = mock(Account.class);
private static final Device primaryDevice = mock(Device.class); private static final Device primaryDevice = mock(Device.class);
private static final ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class);
private static final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); private static final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class);
private static final Map<String, Integer> deviceConfiguration = new HashMap<>(); private static final Map<String, Integer> deviceConfiguration = new HashMap<>();
private static final TestClock testClock = TestClock.now(); private static final TestClock testClock = TestClock.now();
@ -133,8 +131,7 @@ class DeviceControllerTest {
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class)) .addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
.addProvider(new RateLimitExceededExceptionMapper()) .addProvider(new RateLimitExceededExceptionMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addProvider(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, .addProvider(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager))
pubSubClientEventManager))
.addProvider(new DeviceLimitExceededExceptionMapper()) .addProvider(new DeviceLimitExceededExceptionMapper())
.addResource(deviceController) .addResource(deviceController)
.build(); .build();
@ -174,8 +171,7 @@ class DeviceControllerTest {
asyncCommands, asyncCommands,
account, account,
maxedAccount, maxedAccount,
primaryDevice, primaryDevice
clientPresenceManager
); );
testClock.unpin(); testClock.unpin();

View File

@ -1,413 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.push;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.event.ClusterTopologyChangedEvent;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@Timeout(value = 10, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
class ClientPresenceManagerTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private ScheduledExecutorService presenceRenewalExecutorService;
private ClientPresenceManager clientPresenceManager;
private static final DisplacedPresenceListener NO_OP = connectedElsewhere -> {
};
private boolean expectExceptionOnClientPresenceManagerStop = false;
@BeforeEach
void setUp() throws Exception {
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> {
connection.sync().flushall();
connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz");
});
presenceRenewalExecutorService = Executors.newSingleThreadScheduledExecutor();
clientPresenceManager = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
presenceRenewalExecutorService,
presenceRenewalExecutorService);
}
@AfterEach
public void tearDown() throws Exception {
presenceRenewalExecutorService.shutdown();
presenceRenewalExecutorService.awaitTermination(1, TimeUnit.MINUTES);
try {
clientPresenceManager.stop();
} catch (final Exception e) {
if (!expectExceptionOnClientPresenceManagerStop) {
throw e;
}
}
}
@Test
void testIsPresent() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId));
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
assertTrue(clientPresenceManager.isPresent(accountUuid, deviceId));
}
@Test
void testIsLocallyPresent() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
assertFalse(clientPresenceManager.isLocallyPresent(accountUuid, deviceId));
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> connection.sync().flushall());
assertTrue(clientPresenceManager.isLocallyPresent(accountUuid, deviceId));
}
@Test
void testLocalDisplacement() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
final AtomicInteger displacementCounter = new AtomicInteger(0);
final DisplacedPresenceListener displacementListener = connectedElsewhere -> displacementCounter.incrementAndGet();
clientPresenceManager.setPresent(accountUuid, deviceId, displacementListener);
assertEquals(0, displacementCounter.get());
clientPresenceManager.setPresent(accountUuid, deviceId, displacementListener);
assertEquals(1, displacementCounter.get());
}
@Test
void testRemoteDisplacement() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
clientPresenceManager.start();
clientPresenceManager.setPresent(accountUuid, deviceId, connectedElsewhere -> displaced.complete(null));
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(
connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId),
UUID.randomUUID().toString()));
displaced.join();
}
@Test
void testRemoteDisplacementAfterTopologyChange() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
clientPresenceManager.start();
clientPresenceManager.setPresent(accountUuid, deviceId, connectedElsewhere -> displaced.complete(null));
clientPresenceManager.getPubSubConnection()
.usePubSubConnection(connection -> connection.getResources().eventBus()
.publish(new ClusterTopologyChangedEvent(List.of(), List.of())));
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(
connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId),
UUID.randomUUID().toString()));
displaced.join();
}
@Test
void testClearPresence() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId));
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
assertFalse(clientPresenceManager.clearPresence(accountUuid, deviceId,
ignored -> fail("this listener should never be called")));
assertTrue(clientPresenceManager.clearPresence(accountUuid, deviceId, NO_OP));
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(
connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId),
UUID.randomUUID().toString()));
assertFalse(clientPresenceManager.clearPresence(accountUuid, deviceId, NO_OP));
}
@Test
void testPruneMissingPeers() {
final String presentPeerId = UUID.randomUUID().toString();
final String missingPeerId = UUID.randomUUID().toString();
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> {
connection.sync().sadd(ClientPresenceManager.MANAGER_SET_KEY, presentPeerId);
connection.sync().sadd(ClientPresenceManager.MANAGER_SET_KEY, missingPeerId);
});
for (int i = 0; i < 10; i++) {
addClientPresence(presentPeerId);
addClientPresence(missingPeerId);
}
clientPresenceManager.getPubSubConnection().usePubSubConnection(
connection -> connection.sync().upstream().commands()
.subscribe(ClientPresenceManager.getManagerPresenceChannel(presentPeerId)));
clientPresenceManager.pruneMissingPeers();
assertEquals(1, (long) REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(
connection -> connection.sync().exists(ClientPresenceManager.getConnectedClientSetKey(presentPeerId))));
assertTrue(REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(
(Function<StatefulRedisClusterConnection<String, String>, Boolean>) connection -> connection.sync()
.sismember(ClientPresenceManager.MANAGER_SET_KEY, presentPeerId)));
assertEquals(0, (long) REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(
connection -> connection.sync().exists(ClientPresenceManager.getConnectedClientSetKey(missingPeerId))));
assertFalse(REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(
(Function<StatefulRedisClusterConnection<String, String>, Boolean>) connection -> connection.sync()
.sismember(ClientPresenceManager.MANAGER_SET_KEY, missingPeerId)));
}
@Test
void testInitialPresenceExpiration() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
{
final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection ->
connection.sync().ttl(ClientPresenceManager.getPresenceKey(accountUuid, deviceId)).intValue());
assertTrue(ttl > 0);
}
}
@Test
void testRenewPresence() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
final String presenceKey = ClientPresenceManager.getPresenceKey(accountUuid, deviceId);
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
connection.sync().set(presenceKey, clientPresenceManager.getManagerId()));
{
final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection ->
connection.sync().ttl(presenceKey).intValue());
assertEquals(-1, ttl);
}
clientPresenceManager.renewPresence(accountUuid, deviceId);
{
final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection ->
connection.sync().ttl(presenceKey).intValue());
assertTrue(ttl > 0);
}
}
@Test
void testExpiredPresence() {
final UUID accountUuid = UUID.randomUUID();
final byte deviceId = 1;
clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP);
assertTrue(clientPresenceManager.isPresent(accountUuid, deviceId));
// Hackily set this key to expire immediately
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
connection.sync().expire(ClientPresenceManager.getPresenceKey(accountUuid, deviceId), 0));
assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId));
}
private void addClientPresence(final String managerId) {
final String clientPresenceKey = ClientPresenceManager.getPresenceKey(UUID.randomUUID(), (byte) 7);
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> {
connection.sync().set(clientPresenceKey, managerId);
connection.sync().sadd(ClientPresenceManager.getConnectedClientSetKey(managerId), clientPresenceKey);
});
}
@Test
void testClearAllOnStop() {
final int localAccounts = 10;
final UUID[] localUuids = new UUID[localAccounts];
final byte[] localDeviceIds = new byte[localAccounts];
for (int i = 0; i < localAccounts; i++) {
localUuids[i] = UUID.randomUUID();
localDeviceIds[i] = (byte) i;
clientPresenceManager.setPresent(localUuids[i], localDeviceIds[i], NO_OP);
}
final UUID displacedAccountUuid = UUID.randomUUID();
final byte displacedAccountDeviceId = 7;
clientPresenceManager.setPresent(displacedAccountUuid, displacedAccountDeviceId, NO_OP);
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> connection.sync()
.set(ClientPresenceManager.getPresenceKey(displacedAccountUuid, displacedAccountDeviceId),
UUID.randomUUID().toString()));
clientPresenceManager.stop();
for (int i = 0; i < localAccounts; i++) {
localUuids[i] = UUID.randomUUID();
localDeviceIds[i] = (byte) i;
assertFalse(clientPresenceManager.isPresent(localUuids[i], localDeviceIds[i]));
}
assertTrue(clientPresenceManager.isPresent(displacedAccountUuid, displacedAccountDeviceId));
expectExceptionOnClientPresenceManagerStop = true;
}
@Nested
class MultiServerTest {
private ClientPresenceManager server1;
private ClientPresenceManager server2;
@BeforeEach
void setup() throws Exception {
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> {
connection.sync().flushall();
connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz");
});
final ScheduledExecutorService scheduledExecutorService1 = mock(ScheduledExecutorService.class);
final ExecutorService keyspaceNotificationExecutorService1 = Executors.newSingleThreadExecutor();
server1 = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
scheduledExecutorService1, keyspaceNotificationExecutorService1);
final ScheduledExecutorService scheduledExecutorService2 = mock(ScheduledExecutorService.class);
final ExecutorService keyspaceNotificationExecutorService2 = Executors.newSingleThreadExecutor();
server2 = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
scheduledExecutorService2, keyspaceNotificationExecutorService2);
server1.start();
server2.start();
}
@AfterEach
void teardown() {
server2.stop();
server1.stop();
}
@Test
void testSetPresentRemotely() {
final UUID uuid1 = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null);
server1.setPresent(uuid1, deviceId, listener1);
server2.setPresent(uuid1, deviceId, connectedElsewhere -> {});
displaced.join();
}
@Test
void testDisconnectPresenceLocally() {
final UUID uuid1 = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null);
server1.setPresent(uuid1, deviceId, listener1);
server1.disconnectPresence(uuid1, deviceId);
displaced.join();
}
@Test
void testDisconnectPresenceRemotely() {
final UUID uuid1 = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null);
server1.setPresent(uuid1, deviceId, listener1);
server2.disconnectPresence(uuid1, deviceId);
displaced.join();
}
@RepeatedTest(value = 100)
void testConcurrentConnection() throws Exception {
final UUID uuid1 = UUID.randomUUID();
final byte deviceId = 1;
final CompletableFuture<?> displaced = new CompletableFuture<>();
final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null);
final Thread server1Thread = new Thread(() -> server1.setPresent(uuid1, deviceId, listener1));
final Thread server2Thread = new Thread(() -> server2.setPresent(uuid1, deviceId, listener1));
server1Thread.start();
server2Thread.start();
displaced.join();
server2Thread.join();
server1Thread.join();
while (server1.isLocallyPresent(uuid1, deviceId) == server2.isLocallyPresent(uuid1, deviceId)) {
Thread.sleep(50);
}
}
}
}

View File

@ -43,7 +43,6 @@ import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey; import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@ -74,7 +73,6 @@ public class AccountCreationDeletionIntegrationTest {
private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault()); private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault());
private ExecutorService accountLockExecutor; private ExecutorService accountLockExecutor;
private ExecutorService clientPresenceExecutor;
private AccountsManager accountsManager; private AccountsManager accountsManager;
private KeysManager keysManager; private KeysManager keysManager;
@ -112,7 +110,6 @@ public class AccountCreationDeletionIntegrationTest {
DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName()); DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName());
accountLockExecutor = Executors.newSingleThreadExecutor(); accountLockExecutor = Executors.newSingleThreadExecutor();
clientPresenceExecutor = Executors.newSingleThreadExecutor();
final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName()); DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName());
@ -141,6 +138,10 @@ public class AccountCreationDeletionIntegrationTest {
when(registrationRecoveryPasswordsManager.removeForNumber(any())) when(registrationRecoveryPasswordsManager.removeForNumber(any()))
.thenReturn(CompletableFuture.completedFuture(null)); .thenReturn(CompletableFuture.completedFuture(null));
final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class);
when(pubSubClientEventManager.requestDisconnection(any()))
.thenReturn(CompletableFuture.completedFuture(null));
accountsManager = new AccountsManager( accountsManager = new AccountsManager(
accounts, accounts,
phoneNumberIdentifiers, phoneNumberIdentifiers,
@ -152,12 +153,10 @@ public class AccountCreationDeletionIntegrationTest {
profilesManager, profilesManager,
secureStorageClient, secureStorageClient,
svr2Client, svr2Client,
mock(ClientPresenceManager.class), pubSubClientEventManager,
mock(PubSubClientEventManager.class),
registrationRecoveryPasswordsManager, registrationRecoveryPasswordsManager,
clientPublicKeysManager, clientPublicKeysManager,
accountLockExecutor, accountLockExecutor,
clientPresenceExecutor,
CLOCK, CLOCK,
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
dynamicConfigurationManager); dynamicConfigurationManager);
@ -166,13 +165,9 @@ public class AccountCreationDeletionIntegrationTest {
@AfterEach @AfterEach
void tearDown() throws InterruptedException { void tearDown() throws InterruptedException {
accountLockExecutor.shutdown(); accountLockExecutor.shutdown();
clientPresenceExecutor.shutdown();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS);
//noinspection ResultOfMethodCallIgnored
clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS);
} }
@CartesianTest @CartesianTest

View File

@ -36,7 +36,6 @@ import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey; import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@ -67,10 +66,8 @@ class AccountsManagerChangeNumberIntegrationTest {
static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private KeysManager keysManager; private KeysManager keysManager;
private ClientPresenceManager clientPresenceManager;
private PubSubClientEventManager pubSubClientEventManager; private PubSubClientEventManager pubSubClientEventManager;
private ExecutorService accountLockExecutor; private ExecutorService accountLockExecutor;
private ExecutorService clientPresenceExecutor;
private AccountsManager accountsManager; private AccountsManager accountsManager;
@ -106,7 +103,6 @@ class AccountsManagerChangeNumberIntegrationTest {
Tables.USED_LINK_DEVICE_TOKENS.tableName()); Tables.USED_LINK_DEVICE_TOKENS.tableName());
accountLockExecutor = Executors.newSingleThreadExecutor(); accountLockExecutor = Executors.newSingleThreadExecutor();
clientPresenceExecutor = Executors.newSingleThreadExecutor();
final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
Tables.DELETED_ACCOUNTS_LOCK.tableName()); Tables.DELETED_ACCOUNTS_LOCK.tableName());
@ -120,7 +116,6 @@ class AccountsManagerChangeNumberIntegrationTest {
final SecureValueRecovery2Client svr2Client = mock(SecureValueRecovery2Client.class); final SecureValueRecovery2Client svr2Client = mock(SecureValueRecovery2Client.class);
when(svr2Client.deleteBackups(any())).thenReturn(CompletableFuture.completedFuture(null)); when(svr2Client.deleteBackups(any())).thenReturn(CompletableFuture.completedFuture(null));
clientPresenceManager = mock(ClientPresenceManager.class);
pubSubClientEventManager = mock(PubSubClientEventManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class);
final PhoneNumberIdentifiers phoneNumberIdentifiers = final PhoneNumberIdentifiers phoneNumberIdentifiers =
@ -149,12 +144,10 @@ class AccountsManagerChangeNumberIntegrationTest {
profilesManager, profilesManager,
secureStorageClient, secureStorageClient,
svr2Client, svr2Client,
clientPresenceManager,
pubSubClientEventManager, pubSubClientEventManager,
registrationRecoveryPasswordsManager, registrationRecoveryPasswordsManager,
clientPublicKeysManager, clientPublicKeysManager,
accountLockExecutor, accountLockExecutor,
clientPresenceExecutor,
mock(Clock.class), mock(Clock.class),
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
dynamicConfigurationManager); dynamicConfigurationManager);
@ -164,13 +157,9 @@ class AccountsManagerChangeNumberIntegrationTest {
@AfterEach @AfterEach
void tearDown() throws InterruptedException { void tearDown() throws InterruptedException {
accountLockExecutor.shutdown(); accountLockExecutor.shutdown();
clientPresenceExecutor.shutdown();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS);
//noinspection ResultOfMethodCallIgnored
clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS);
} }
@Test @Test
@ -285,7 +274,6 @@ class AccountsManagerChangeNumberIntegrationTest {
assertEquals(secondNumber, accountsManager.getByAccountIdentifier(originalUuid).map(Account::getNumber).orElseThrow()); assertEquals(secondNumber, accountsManager.getByAccountIdentifier(originalUuid).map(Account::getNumber).orElseThrow());
verify(clientPresenceManager).disconnectAllPresencesForUuid(existingAccountUuid);
verify(pubSubClientEventManager).requestDisconnection(existingAccountUuid); verify(pubSubClientEventManager).requestDisconnection(existingAccountUuid);
assertEquals(Optional.of(existingAccountUuid), accountsManager.findRecentlyDeletedAccountIdentifier(originalNumber)); assertEquals(Optional.of(existingAccountUuid), accountsManager.findRecentlyDeletedAccountIdentifier(originalNumber));

View File

@ -48,7 +48,6 @@ import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
@ -134,12 +133,10 @@ class AccountsManagerConcurrentModificationIntegrationTest {
mock(ProfilesManager.class), mock(ProfilesManager.class),
mock(SecureStorageClient.class), mock(SecureStorageClient.class),
mock(SecureValueRecovery2Client.class), mock(SecureValueRecovery2Client.class),
mock(ClientPresenceManager.class),
mock(PubSubClientEventManager.class), mock(PubSubClientEventManager.class),
mock(RegistrationRecoveryPasswordsManager.class), mock(RegistrationRecoveryPasswordsManager.class),
mock(ClientPublicKeysManager.class), mock(ClientPublicKeysManager.class),
mock(Executor.class), mock(Executor.class),
mock(Executor.class),
mock(Clock.class), mock(Clock.class),
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
dynamicConfigurationManager dynamicConfigurationManager

View File

@ -14,7 +14,6 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest; import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest;
import org.whispersystems.textsecuregcm.entities.RemoteAttachment; import org.whispersystems.textsecuregcm.entities.RemoteAttachment;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.redis.RedisServerExtension; import org.whispersystems.textsecuregcm.redis.RedisServerExtension;
@ -63,12 +62,10 @@ public class AccountsManagerDeviceTransferIntegrationTest {
mock(ProfilesManager.class), mock(ProfilesManager.class),
mock(SecureStorageClient.class), mock(SecureStorageClient.class),
mock(SecureValueRecovery2Client.class), mock(SecureValueRecovery2Client.class),
mock(ClientPresenceManager.class),
mock(PubSubClientEventManager.class), mock(PubSubClientEventManager.class),
mock(RegistrationRecoveryPasswordsManager.class), mock(RegistrationRecoveryPasswordsManager.class),
mock(ClientPublicKeysManager.class), mock(ClientPublicKeysManager.class),
mock(ExecutorService.class), mock(ExecutorService.class),
mock(ExecutorService.class),
Clock.systemUTC(), Clock.systemUTC(),
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
mock(DynamicConfigurationManager.class)); mock(DynamicConfigurationManager.class));

View File

@ -79,7 +79,6 @@ import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier; import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
@ -118,7 +117,6 @@ class AccountsManagerTest {
private KeysManager keysManager; private KeysManager keysManager;
private MessagesManager messagesManager; private MessagesManager messagesManager;
private ProfilesManager profilesManager; private ProfilesManager profilesManager;
private ClientPresenceManager clientPresenceManager;
private PubSubClientEventManager pubSubClientEventManager; private PubSubClientEventManager pubSubClientEventManager;
private ClientPublicKeysManager clientPublicKeysManager; private ClientPublicKeysManager clientPublicKeysManager;
@ -154,20 +152,10 @@ class AccountsManagerTest {
keysManager = mock(KeysManager.class); keysManager = mock(KeysManager.class);
messagesManager = mock(MessagesManager.class); messagesManager = mock(MessagesManager.class);
profilesManager = mock(ProfilesManager.class); profilesManager = mock(ProfilesManager.class);
clientPresenceManager = mock(ClientPresenceManager.class);
pubSubClientEventManager = mock(PubSubClientEventManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class);
clientPublicKeysManager = mock(ClientPublicKeysManager.class); clientPublicKeysManager = mock(ClientPublicKeysManager.class);
dynamicConfiguration = mock(DynamicConfiguration.class); dynamicConfiguration = mock(DynamicConfiguration.class);
final Executor clientPresenceExecutor = mock(Executor.class);
doAnswer(invocation -> {
final Runnable runnable = invocation.getArgument(0);
runnable.run();
return null;
}).when(clientPresenceExecutor).execute(any());
//noinspection unchecked //noinspection unchecked
asyncCommands = mock(RedisAsyncCommands.class); asyncCommands = mock(RedisAsyncCommands.class);
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK")); when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
@ -250,6 +238,9 @@ class AccountsManagerTest {
.stringAsyncCommands(asyncClusterCommands) .stringAsyncCommands(asyncClusterCommands)
.build(); .build();
when(pubSubClientEventManager.requestDisconnection(any()))
.thenReturn(CompletableFuture.completedFuture(null));
accountsManager = new AccountsManager( accountsManager = new AccountsManager(
accounts, accounts,
phoneNumberIdentifiers, phoneNumberIdentifiers,
@ -261,12 +252,10 @@ class AccountsManagerTest {
profilesManager, profilesManager,
storageClient, storageClient,
svr2Client, svr2Client,
clientPresenceManager,
pubSubClientEventManager, pubSubClientEventManager,
registrationRecoveryPasswordsManager, registrationRecoveryPasswordsManager,
clientPublicKeysManager, clientPublicKeysManager,
mock(Executor.class), mock(Executor.class),
clientPresenceExecutor,
CLOCK, CLOCK,
LINK_DEVICE_SECRET, LINK_DEVICE_SECRET,
dynamicConfigurationManager); dynamicConfigurationManager);
@ -802,7 +791,6 @@ class AccountsManagerTest {
verify(keysManager, times(2)).deleteSingleUsePreKeys(account.getUuid(), linkedDevice.getId()); verify(keysManager, times(2)).deleteSingleUsePreKeys(account.getUuid(), linkedDevice.getId());
verify(keysManager).buildWriteItemsForRemovedDevice(account.getUuid(), account.getPhoneNumberIdentifier(), linkedDevice.getId()); verify(keysManager).buildWriteItemsForRemovedDevice(account.getUuid(), account.getPhoneNumberIdentifier(), linkedDevice.getId());
verify(clientPublicKeysManager).buildTransactWriteItemForDeletion(account.getUuid(), linkedDevice.getId()); verify(clientPublicKeysManager).buildTransactWriteItemForDeletion(account.getUuid(), linkedDevice.getId());
verify(clientPresenceManager).disconnectPresence(account.getUuid(), linkedDevice.getId());
verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(linkedDevice.getId())); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(linkedDevice.getId()));
} }
@ -821,7 +809,6 @@ class AccountsManagerTest {
assertDoesNotThrow(account::getPrimaryDevice); assertDoesNotThrow(account::getPrimaryDevice);
verify(messagesManager, never()).clear(any(), anyByte()); verify(messagesManager, never()).clear(any(), anyByte());
verify(keysManager, never()).deleteSingleUsePreKeys(any(), anyByte()); verify(keysManager, never()).deleteSingleUsePreKeys(any(), anyByte());
verify(clientPresenceManager, never()).disconnectPresence(any(), anyByte());
verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any());
} }
@ -891,7 +878,6 @@ class AccountsManagerTest {
verify(keysManager, times(2)).deleteSingleUsePreKeys(phoneNumberIdentifiersByE164.get(e164)); verify(keysManager, times(2)).deleteSingleUsePreKeys(phoneNumberIdentifiersByE164.get(e164));
verify(messagesManager, times(2)).clear(existingUuid); verify(messagesManager, times(2)).clear(existingUuid);
verify(profilesManager, times(2)).deleteAll(existingUuid); verify(profilesManager, times(2)).deleteAll(existingUuid);
verify(clientPresenceManager).disconnectAllPresencesForUuid(existingUuid);
verify(pubSubClientEventManager).requestDisconnection(existingUuid); verify(pubSubClientEventManager).requestDisconnection(existingUuid);
} }

View File

@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@ -135,6 +134,9 @@ class AccountsManagerUsernameIntegrationTest {
when(messageManager.clear(any())).thenReturn(CompletableFuture.completedFuture(null)); when(messageManager.clear(any())).thenReturn(CompletableFuture.completedFuture(null));
when(profileManager.deleteAll(any())).thenReturn(CompletableFuture.completedFuture(null)); when(profileManager.deleteAll(any())).thenReturn(CompletableFuture.completedFuture(null));
final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class);
when(pubSubClientEventManager.requestDisconnection(any())).thenReturn(CompletableFuture.completedFuture(null));
accountsManager = new AccountsManager( accountsManager = new AccountsManager(
accounts, accounts,
phoneNumberIdentifiers, phoneNumberIdentifiers,
@ -146,12 +148,10 @@ class AccountsManagerUsernameIntegrationTest {
profileManager, profileManager,
mock(SecureStorageClient.class), mock(SecureStorageClient.class),
mock(SecureValueRecovery2Client.class), mock(SecureValueRecovery2Client.class),
mock(ClientPresenceManager.class), pubSubClientEventManager,
mock(PubSubClientEventManager.class),
mock(RegistrationRecoveryPasswordsManager.class), mock(RegistrationRecoveryPasswordsManager.class),
mock(ClientPublicKeysManager.class), mock(ClientPublicKeysManager.class),
Executors.newSingleThreadExecutor(), Executors.newSingleThreadExecutor(),
Executors.newSingleThreadExecutor(),
mock(Clock.class), mock(Clock.class),
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
dynamicConfigurationManager); dynamicConfigurationManager);

View File

@ -33,7 +33,6 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.DeviceInfo; import org.whispersystems.textsecuregcm.entities.DeviceInfo;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.redis.RedisServerExtension; import org.whispersystems.textsecuregcm.redis.RedisServerExtension;
@ -70,7 +69,6 @@ public class AddRemoveDeviceIntegrationTest {
private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault()); private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault());
private ExecutorService accountLockExecutor; private ExecutorService accountLockExecutor;
private ExecutorService clientPresenceExecutor;
private KeysManager keysManager; private KeysManager keysManager;
private ClientPublicKeysManager clientPublicKeysManager; private ClientPublicKeysManager clientPublicKeysManager;
@ -107,7 +105,6 @@ public class AddRemoveDeviceIntegrationTest {
DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName()); DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName());
accountLockExecutor = Executors.newSingleThreadExecutor(); accountLockExecutor = Executors.newSingleThreadExecutor();
clientPresenceExecutor = Executors.newSingleThreadExecutor();
final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName()); DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName());
@ -152,12 +149,10 @@ public class AddRemoveDeviceIntegrationTest {
profilesManager, profilesManager,
secureStorageClient, secureStorageClient,
svr2Client, svr2Client,
mock(ClientPresenceManager.class),
mock(PubSubClientEventManager.class), mock(PubSubClientEventManager.class),
registrationRecoveryPasswordsManager, registrationRecoveryPasswordsManager,
clientPublicKeysManager, clientPublicKeysManager,
accountLockExecutor, accountLockExecutor,
clientPresenceExecutor,
CLOCK, CLOCK,
"link-device-secret".getBytes(StandardCharsets.UTF_8), "link-device-secret".getBytes(StandardCharsets.UTF_8),
dynamicConfigurationManager); dynamicConfigurationManager);
@ -170,13 +165,9 @@ public class AddRemoveDeviceIntegrationTest {
accountsManager.stop(); accountsManager.stop();
accountLockExecutor.shutdown(); accountLockExecutor.shutdown();
clientPresenceExecutor.shutdown();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS);
//noinspection ResultOfMethodCallIgnored
clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS);
} }
@Test @Test

View File

@ -57,7 +57,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor; import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor;
import org.whispersystems.textsecuregcm.metrics.MessageMetrics; import org.whispersystems.textsecuregcm.metrics.MessageMetrics;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler;
@ -125,7 +124,7 @@ class WebSocketConnectionTest {
new WebSocketAccountAuthenticator(accountAuthenticator, mock(PrincipalSupplier.class)); new WebSocketAccountAuthenticator(accountAuthenticator, mock(PrincipalSupplier.class));
AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager, AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager,
new MessageMetrics(), mock(PushNotificationManager.class), mock(PushNotificationScheduler.class), new MessageMetrics(), mock(PushNotificationManager.class), mock(PushNotificationScheduler.class),
mock(ClientPresenceManager.class), mock(PubSubClientEventManager.class), retrySchedulingExecutor, mock(PubSubClientEventManager.class), retrySchedulingExecutor,
messageDeliveryScheduler, clientReleaseManager, mock(MessageDeliveryLoopMonitor.class)); messageDeliveryScheduler, clientReleaseManager, mock(MessageDeliveryLoopMonitor.class));
WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class);

View File

@ -73,7 +73,6 @@ class FinishPushNotificationExperimentCommandTest {
null, null,
null, null,
null, null,
null,
pushNotificationExperimentSamples, pushNotificationExperimentSamples,
null, null,
null, null,

View File

@ -66,7 +66,6 @@ class NotifyIdleDevicesCommandTest {
null, null,
null, null,
null, null,
null,
null); null);
this.idleDeviceNotificationScheduler = idleDeviceNotificationScheduler; this.idleDeviceNotificationScheduler = idleDeviceNotificationScheduler;

View File

@ -62,7 +62,6 @@ class StartPushNotificationExperimentCommandTest {
null, null,
null, null,
null, null,
null,
pushNotificationExperimentSamples, pushNotificationExperimentSamples,
null, null,
null, null,