mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 04:58:06 +01:00
Listen for new messages via keyspace notifications.
This commit is contained in:
committed by
Jon Chambers
parent
2c29f831e8
commit
8d3316ccd6
@@ -5,11 +5,14 @@ import junitparams.Parameters;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -20,6 +23,7 @@ public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
private static final UUID DESTINATION_UUID = UUID.randomUUID();
|
||||
private static final int DESTINATION_DEVICE_ID = 7;
|
||||
|
||||
private ExecutorService notificationExecutorService;
|
||||
private RedisClusterMessagesCache messagesCache;
|
||||
|
||||
@Override
|
||||
@@ -27,13 +31,18 @@ public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
try {
|
||||
messagesCache = new RedisClusterMessagesCache(getRedisCluster());
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
getRedisCluster().useWriteCluster(connection -> connection.sync().masters().commands().configSet("notify-keyspace-events", "K$gz"));
|
||||
|
||||
getRedisCluster().useWriteCluster(connection -> connection.sync().flushall());
|
||||
notificationExecutorService = Executors.newSingleThreadExecutor();
|
||||
messagesCache = new RedisClusterMessagesCache(getRedisCluster(), notificationExecutorService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
notificationExecutorService.shutdown();
|
||||
notificationExecutorService.awaitTermination(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,6 +79,12 @@ public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
RedisClusterMessagesCache.getDeviceIdFromQueueName(new String(RedisClusterMessagesCache.getMessageQueueKey(DESTINATION_UUID, DESTINATION_DEVICE_ID), StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueueNameFromKeyspaceChannel() {
|
||||
assertEquals("1b363a31-a429-4fb6-8959-984a025e72ff::7",
|
||||
RedisClusterMessagesCache.getQueueNameFromKeyspaceChannel("__keyspace@0__:user_queue::{1b363a31-a429-4fb6-8959-984a025e72ff::7}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testGetQueuesToPersist(final boolean sealedSender) {
|
||||
@@ -86,4 +101,67 @@ public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
assertEquals(DESTINATION_UUID, RedisClusterMessagesCache.getAccountUuidFromQueueName(queues.get(0)));
|
||||
assertEquals(DESTINATION_DEVICE_ID, RedisClusterMessagesCache.getDeviceIdFromQueueName(queues.get(0)));
|
||||
}
|
||||
|
||||
@Test(timeout = 5_000L)
|
||||
public void testNotifyListenerNewMessage() throws InterruptedException {
|
||||
final AtomicBoolean notified = new AtomicBoolean(false);
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
|
||||
final MessageAvailabilityListener listener = new MessageAvailabilityListener() {
|
||||
@Override
|
||||
public void handleNewMessagesAvailable() {
|
||||
synchronized (notified) {
|
||||
notified.set(true);
|
||||
notified.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessagesPersisted() {
|
||||
}
|
||||
};
|
||||
|
||||
messagesCache.addMessageAvailabilityListener(DESTINATION_UUID, DESTINATION_DEVICE_ID, listener);
|
||||
messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, generateRandomMessage(messageGuid, true));
|
||||
|
||||
synchronized (notified) {
|
||||
while (!notified.get()) {
|
||||
notified.wait();
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(notified.get());
|
||||
}
|
||||
|
||||
@Test(timeout = 5_000L)
|
||||
public void testNotifyListenerPersisted() throws InterruptedException {
|
||||
final AtomicBoolean notified = new AtomicBoolean(false);
|
||||
|
||||
final MessageAvailabilityListener listener = new MessageAvailabilityListener() {
|
||||
@Override
|
||||
public void handleNewMessagesAvailable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessagesPersisted() {
|
||||
synchronized (notified) {
|
||||
notified.set(true);
|
||||
notified.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
messagesCache.addMessageAvailabilityListener(DESTINATION_UUID, DESTINATION_DEVICE_ID, listener);
|
||||
|
||||
messagesCache.lockQueueForPersistence(RedisClusterMessagesCache.getQueueName(DESTINATION_UUID, DESTINATION_DEVICE_ID));
|
||||
messagesCache.unlockQueueForPersistence(RedisClusterMessagesCache.getQueueName(DESTINATION_UUID, DESTINATION_DEVICE_ID));
|
||||
|
||||
synchronized (notified) {
|
||||
while (!notified.get()) {
|
||||
notified.wait();
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(notified.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import io.lettuce.core.cluster.SlotHash;
|
||||
import io.lettuce.core.cluster.api.sync.Executions;
|
||||
import io.lettuce.core.cluster.api.sync.NodeSelection;
|
||||
import io.lettuce.core.cluster.api.sync.NodeSelectionCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
import redis.embedded.Redis;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class RedisClusterUtilTest {
|
||||
|
||||
@Test
|
||||
public void testGetMinimalHashTag() {
|
||||
for (int slot = 0; slot < SlotHash.SLOT_COUNT; slot++) {
|
||||
assertEquals(slot, SlotHash.getSlot(RedisClusterUtil.getMinimalHashTag(slot)));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@Parameters(method = "argumentsForTestAssertKeyspaceNotificationsConfigured")
|
||||
public void testAssertKeyspaceNotificationsConfigured(final String requiredKeyspaceNotifications, final String configuerdKeyspaceNotifications, final boolean expectException) {
|
||||
final RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
final FaultTolerantRedisCluster redisCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
|
||||
when(commands.configGet("notify-keyspace-events")).thenReturn(Map.of("notify-keyspace-events", configuerdKeyspaceNotifications));
|
||||
|
||||
if (expectException) {
|
||||
try {
|
||||
RedisClusterUtil.assertKeyspaceNotificationsConfigured(redisCluster, requiredKeyspaceNotifications);
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (final IllegalStateException ignored) {
|
||||
}
|
||||
} else {
|
||||
RedisClusterUtil.assertKeyspaceNotificationsConfigured(redisCluster, requiredKeyspaceNotifications);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object argumentsForTestAssertKeyspaceNotificationsConfigured() {
|
||||
return new Object[] {
|
||||
new Object[] { "K$gz", "", true },
|
||||
new Object[] { "K$gz", "K$gz", false },
|
||||
new Object[] { "K$gz", "K$gzl", false },
|
||||
new Object[] { "K$gz", "KA", false },
|
||||
new Object[] { "", "A", false },
|
||||
new Object[] { "", "", false },
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user