remove experiment configuration for low urgency pushes

This commit is contained in:
Ravi Khadiwala 2025-06-13 14:33:53 -05:00 committed by ravi-signal
parent 4f1cab407f
commit 295cedc075
6 changed files with 36 additions and 56 deletions

View File

@ -641,7 +641,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
PushNotificationScheduler pushNotificationScheduler = new PushNotificationScheduler(pushSchedulerCluster, PushNotificationScheduler pushNotificationScheduler = new PushNotificationScheduler(pushSchedulerCluster,
apnSender, fcmSender, accountsManager, 0, 0); apnSender, fcmSender, accountsManager, 0, 0);
PushNotificationManager pushNotificationManager = PushNotificationManager pushNotificationManager =
new PushNotificationManager(accountsManager, apnSender, fcmSender, pushNotificationScheduler, experimentEnrollmentManager); new PushNotificationManager(accountsManager, apnSender, fcmSender, pushNotificationScheduler);
WebSocketConnectionEventManager webSocketConnectionEventManager = WebSocketConnectionEventManager webSocketConnectionEventManager =
new WebSocketConnectionEventManager(accountsManager, pushNotificationManager, messagesCluster, clientEventExecutor, asyncOperationQueueingExecutor); new WebSocketConnectionEventManager(accountsManager, pushNotificationManager, messagesCluster, clientEventExecutor, asyncOperationQueueingExecutor);
RateLimiters rateLimiters = RateLimiters.create(dynamicConfigurationManager, rateLimitersCluster); RateLimiters rateLimiters = RateLimiters.create(dynamicConfigurationManager, rateLimitersCluster);

View File

@ -17,7 +17,6 @@ import java.util.function.BiConsumer;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
@ -29,9 +28,6 @@ public class PushNotificationManager {
private final APNSender apnSender; private final APNSender apnSender;
private final FcmSender fcmSender; private final FcmSender fcmSender;
private final PushNotificationScheduler pushNotificationScheduler; private final PushNotificationScheduler pushNotificationScheduler;
private final ExperimentEnrollmentManager experimentEnrollmentManager;
public static final String SCHEDULE_LOW_URGENCY_FCM_PUSH_EXPERIMENT = "scheduleLowUregencyFcmPush";
private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification"); private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification");
private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification"); private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification");
@ -42,14 +38,12 @@ public class PushNotificationManager {
public PushNotificationManager(final AccountsManager accountsManager, public PushNotificationManager(final AccountsManager accountsManager,
final APNSender apnSender, final APNSender apnSender,
final FcmSender fcmSender, final FcmSender fcmSender,
final PushNotificationScheduler pushNotificationScheduler, final PushNotificationScheduler pushNotificationScheduler) {
final ExperimentEnrollmentManager experimentEnrollmentManager) {
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.apnSender = apnSender; this.apnSender = apnSender;
this.fcmSender = fcmSender; this.fcmSender = fcmSender;
this.pushNotificationScheduler = pushNotificationScheduler; this.pushNotificationScheduler = pushNotificationScheduler;
this.experimentEnrollmentManager = experimentEnrollmentManager;
} }
public CompletableFuture<Optional<SendPushNotificationResult>> sendNewMessageNotification(final Account destination, final byte destinationDeviceId, final boolean urgent) throws NotPushRegisteredException { public CompletableFuture<Optional<SendPushNotificationResult>> sendNewMessageNotification(final Account destination, final byte destinationDeviceId, final boolean urgent) throws NotPushRegisteredException {
@ -107,7 +101,7 @@ public class PushNotificationManager {
@VisibleForTesting @VisibleForTesting
CompletableFuture<Optional<SendPushNotificationResult>> sendNotification(final PushNotification pushNotification) { CompletableFuture<Optional<SendPushNotificationResult>> sendNotification(final PushNotification pushNotification) {
if (shouldScheduleNotification(pushNotification)) { if (!pushNotification.urgent()) {
// Schedule a notification for some time in the future (possibly even now!) rather than sending a notification // Schedule a notification for some time in the future (possibly even now!) rather than sending a notification
// directly // directly
return pushNotificationScheduler return pushNotificationScheduler
@ -156,16 +150,6 @@ public class PushNotificationManager {
.thenApply(Optional::of); .thenApply(Optional::of);
} }
private boolean shouldScheduleNotification(final PushNotification pushNotification) {
return !pushNotification.urgent() && switch (pushNotification.tokenType()) {
// APNs imposes a per-device limit on background push notifications
case APN -> true;
case FCM -> experimentEnrollmentManager.isEnrolled(
pushNotification.destination().getUuid(),
SCHEDULE_LOW_URGENCY_FCM_PUSH_EXPERIMENT);
};
}
private static <T> BiConsumer<T, Throwable> logErrors() { private static <T> BiConsumer<T, Throwable> logErrors() {
return (ignored, throwable) -> { return (ignored, throwable) -> {
if (throwable != null) { if (throwable != null) {

View File

@ -244,11 +244,8 @@ public class MessagePersister implements Managed {
} while (!messages.isEmpty()); } while (!messages.isEmpty());
final boolean inSkipExperiment = device.getGcmId() != null && experimentEnrollmentManager.isEnrolled(
accountUuid,
PushNotificationManager.SCHEDULE_LOW_URGENCY_FCM_PUSH_EXPERIMENT);
DistributionSummary.builder(QUEUE_SIZE_DISTRIBUTION_SUMMARY_NAME) DistributionSummary.builder(QUEUE_SIZE_DISTRIBUTION_SUMMARY_NAME)
.tags(Tags.of(platformTag).and("lowUrgencySkip", Boolean.toString(inSkipExperiment))) .tags(Tags.of(platformTag))
.publishPercentileHistogram(true) .publishPercentileHistogram(true)
.register(Metrics.globalRegistry) .register(Metrics.globalRegistry)
.record(messageCount); .record(messageCount);

View File

@ -338,13 +338,8 @@ public class WebSocketConnection implements WebSocketConnectionEventListener {
// Cleared the queue! Send a queue empty message if we need to // Cleared the queue! Send a queue empty message if we need to
consecutiveRetries.set(0); consecutiveRetries.set(0);
if (sentInitialQueueEmptyMessage.compareAndSet(false, true)) { if (sentInitialQueueEmptyMessage.compareAndSet(false, true)) {
final boolean inSkipExperiment = auth.getAuthenticatedDevice().getGcmId() != null && experimentEnrollmentManager.isEnrolled(
auth.getAccount().getUuid(),
PushNotificationManager.SCHEDULE_LOW_URGENCY_FCM_PUSH_EXPERIMENT);
final Tags tags = Tags final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(client.getUserAgent()));
.of(UserAgentTagUtil.getPlatformTag(client.getUserAgent()))
.and("lowUrgencySkip", Boolean.toString(inSkipExperiment));
final long drainDuration = System.currentTimeMillis() - queueDrainStartTime.get(); final long drainDuration = System.currentTimeMillis() - queueDrainStartTime.get();
Metrics.summary(INITIAL_QUEUE_LENGTH_DISTRIBUTION_NAME, tags).record(sentMessageCounter.sum()); Metrics.summary(INITIAL_QUEUE_LENGTH_DISTRIBUTION_NAME, tags).record(sentMessageCounter.sum());

View File

@ -33,7 +33,6 @@ import org.whispersystems.textsecuregcm.backup.Cdn3RemoteStorageManager;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controller; import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controller;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.experiment.PushNotificationExperimentSamples; import org.whispersystems.textsecuregcm.experiment.PushNotificationExperimentSamples;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher; import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher;
@ -118,9 +117,6 @@ record CommandDependencies(
new DynamicConfigurationManager<>( new DynamicConfigurationManager<>(
configuration.getDynamicConfig().build(awsCredentialsProvider, dynamicConfigurationExecutor), DynamicConfiguration.class); configuration.getDynamicConfig().build(awsCredentialsProvider, dynamicConfigurationExecutor), DynamicConfiguration.class);
dynamicConfigurationManager.start(); dynamicConfigurationManager.start();
final ExperimentEnrollmentManager experimentEnrollmentManager =
new ExperimentEnrollmentManager(dynamicConfigurationManager);
final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder(); final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder();
FaultTolerantRedisClusterClient cacheCluster = configuration.getCacheClusterConfiguration() FaultTolerantRedisClusterClient cacheCluster = configuration.getCacheClusterConfiguration()
@ -286,7 +282,7 @@ record CommandDependencies(
PushNotificationScheduler pushNotificationScheduler = new PushNotificationScheduler(pushSchedulerCluster, PushNotificationScheduler pushNotificationScheduler = new PushNotificationScheduler(pushSchedulerCluster,
apnSender, fcmSender, accountsManager, 0, 0); apnSender, fcmSender, accountsManager, 0, 0);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager,
apnSender, fcmSender, pushNotificationScheduler, experimentEnrollmentManager); apnSender, fcmSender, pushNotificationScheduler);
PushNotificationExperimentSamples pushNotificationExperimentSamples = PushNotificationExperimentSamples pushNotificationExperimentSamples =
new PushNotificationExperimentSamples(dynamoDbAsyncClient, new PushNotificationExperimentSamples(dynamoDbAsyncClient,
configuration.getDynamoDbTables().getPushNotificationExperimentSamples().getTableName(), configuration.getDynamoDbTables().getPushNotificationExperimentSamples().getTableName(),

View File

@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.junitpioneer.jupiter.cartesian.CartesianTest; import org.junitpioneer.jupiter.cartesian.CartesianTest;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
@ -36,7 +35,6 @@ class PushNotificationManagerTest {
private APNSender apnSender; private APNSender apnSender;
private FcmSender fcmSender; private FcmSender fcmSender;
private PushNotificationScheduler pushNotificationScheduler; private PushNotificationScheduler pushNotificationScheduler;
private ExperimentEnrollmentManager experimentEnrollmentManager;
private PushNotificationManager pushNotificationManager; private PushNotificationManager pushNotificationManager;
@ -46,17 +44,15 @@ class PushNotificationManagerTest {
apnSender = mock(APNSender.class); apnSender = mock(APNSender.class);
fcmSender = mock(FcmSender.class); fcmSender = mock(FcmSender.class);
pushNotificationScheduler = mock(PushNotificationScheduler.class); pushNotificationScheduler = mock(PushNotificationScheduler.class);
experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
AccountsHelper.setupMockUpdate(accountsManager); AccountsHelper.setupMockUpdate(accountsManager);
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
pushNotificationScheduler, experimentEnrollmentManager); pushNotificationScheduler);
} }
@ParameterizedTest @Test
@ValueSource(booleans = {true, false}) void sendNewUrgentMessageNotification() throws NotPushRegisteredException {
void sendNewMessageNotification(final boolean urgent) throws NotPushRegisteredException {
final Account account = mock(Account.class); final Account account = mock(Account.class);
final Device device = mock(Device.class); final Device device = mock(Device.class);
@ -66,13 +62,30 @@ class PushNotificationManagerTest {
when(device.getGcmId()).thenReturn(deviceToken); when(device.getGcmId()).thenReturn(deviceToken);
when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device)); when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device));
when(fcmSender.sendNotification(any())) when(fcmSender.sendNotification(any()))
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, Optional.empty(), false, Optional.empty()))); .thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, Optional.empty(), false, Optional.empty())));
pushNotificationManager.sendNewMessageNotification(account, Device.PRIMARY_ID, true);
pushNotificationManager.sendNewMessageNotification(account, Device.PRIMARY_ID, urgent); verify(fcmSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, true));
verify(fcmSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent));
} }
@Test
void sendNewNonUrgentMessageNotification() throws NotPushRegisteredException {
final Account account = mock(Account.class);
final Device device = mock(Device.class);
final String deviceToken = "token";
when(device.getId()).thenReturn(Device.PRIMARY_ID);
when(device.getGcmId()).thenReturn(deviceToken);
when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device));
when(pushNotificationScheduler.scheduleBackgroundNotification(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(null));
pushNotificationManager.sendNewMessageNotification(account, Device.PRIMARY_ID, false);
verify(pushNotificationScheduler).scheduleBackgroundNotification(PushNotification.TokenType.FCM, account, device);
}
@Test @Test
void sendRegistrationChallengeNotification() { void sendRegistrationChallengeNotification() {
final String deviceToken = "token"; final String deviceToken = "token";
@ -135,9 +148,8 @@ class PushNotificationManagerTest {
} }
} }
@ParameterizedTest @Test
@ValueSource(booleans = {true, false}) void testSendNotificationFcm() {
void testSendNotificationFcm(final boolean urgent) {
final Account account = mock(Account.class); final Account account = mock(Account.class);
final Device device = mock(Device.class); final Device device = mock(Device.class);
@ -145,7 +157,7 @@ class PushNotificationManagerTest {
when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device)); when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device));
final PushNotification pushNotification = new PushNotification( final PushNotification pushNotification = new PushNotification(
"token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent); "token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, true);
when(fcmSender.sendNotification(pushNotification)) when(fcmSender.sendNotification(pushNotification))
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, Optional.empty(), false, Optional.empty()))); .thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, Optional.empty(), false, Optional.empty())));
@ -162,10 +174,9 @@ class PushNotificationManagerTest {
@CartesianTest @CartesianTest
void testSendOrScheduleNotification( void testSendOrScheduleNotification(
@CartesianTest.Enum(PushNotification.TokenType.class) PushNotification.TokenType tokenType, @CartesianTest.Enum(PushNotification.TokenType.class) PushNotification.TokenType tokenType,
@CartesianTest.Values(booleans = {false, true}) final boolean urgent, @CartesianTest.Values(booleans = {false, true}) final boolean urgent) {
@CartesianTest.Values(booleans = {false, true}) final boolean inExperiment) {
final boolean expectSchedule = !urgent && (tokenType == PushNotification.TokenType.APN || inExperiment); final boolean expectSchedule = !urgent;
final Account account = mock(Account.class); final Account account = mock(Account.class);
final Device device = mock(Device.class); final Device device = mock(Device.class);
@ -175,9 +186,6 @@ class PushNotificationManagerTest {
when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device)); when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(device));
when(account.getUuid()).thenReturn(aci); when(account.getUuid()).thenReturn(aci);
when(experimentEnrollmentManager.isEnrolled(aci, PushNotificationManager.SCHEDULE_LOW_URGENCY_FCM_PUSH_EXPERIMENT))
.thenReturn(inExperiment);
final PushNotification pushNotification = new PushNotification( final PushNotification pushNotification = new PushNotification(
"token", tokenType, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent); "token", tokenType, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent);