mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 06:18:06 +01:00
Added a push latency manager.
This commit is contained in:
committed by
Jon Chambers
parent
6e9b70a8d6
commit
901ba6e87f
@@ -0,0 +1,118 @@
|
||||
package org.whispersystems.textsecuregcm.metrics;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PushLatencyManagerTest {
|
||||
|
||||
private PushLatencyManager pushLatencyManager;
|
||||
|
||||
private RedisServer redisServer;
|
||||
private Jedis jedis;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
redisServer = new RedisServer(6379);
|
||||
jedis = new Jedis("localhost", 6379);
|
||||
|
||||
final ReplicatedJedisPool jedisPool = mock(ReplicatedJedisPool.class);
|
||||
final ThreadPoolExecutor threadPoolExecutor = mock(ThreadPoolExecutor.class);
|
||||
|
||||
pushLatencyManager = new PushLatencyManager(jedisPool, threadPoolExecutor);
|
||||
|
||||
redisServer.start();
|
||||
|
||||
when(jedisPool.getWriteResource()).thenReturn(jedis);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
invocation.getArgument(0, Runnable.class).run();
|
||||
return null;
|
||||
}).when(threadPoolExecutor).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
redisServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLatency() {
|
||||
final String accountNumber = "mock-account";
|
||||
final long deviceId = 1;
|
||||
|
||||
final long expectedLatency = 1234;
|
||||
final long pushSentTimestamp = System.currentTimeMillis();
|
||||
final long clearQueueTimestamp = pushSentTimestamp + expectedLatency;
|
||||
|
||||
{
|
||||
assertFalse(pushLatencyManager.getLatencyAndClearTimestamp(accountNumber, deviceId, System.currentTimeMillis()).isPresent());
|
||||
}
|
||||
|
||||
{
|
||||
pushLatencyManager.recordPushSent(accountNumber, deviceId, pushSentTimestamp);
|
||||
|
||||
assertEquals(Optional.of(expectedLatency),
|
||||
pushLatencyManager.getLatencyAndClearTimestamp(accountNumber, deviceId, clearQueueTimestamp));
|
||||
|
||||
assertFalse(pushLatencyManager.getLatencyAndClearTimestamp(accountNumber, deviceId, System.currentTimeMillis()).isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThreadPoolDoesNotBlock() throws InterruptedException {
|
||||
final AtomicBoolean canReturnJedisInstance = new AtomicBoolean(false);
|
||||
final ReplicatedJedisPool blockingJedisPool = mock(ReplicatedJedisPool.class);
|
||||
final PushLatencyManager blockingPushLatencyManager = new PushLatencyManager(blockingJedisPool);
|
||||
|
||||
// One unqueued execution (single-thread pool) plus a full queue
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(PushLatencyManager.QUEUE_SIZE + 1);
|
||||
|
||||
when(blockingJedisPool.getWriteResource()).thenAnswer((Answer<Jedis>)invocationOnMock -> {
|
||||
synchronized (canReturnJedisInstance) {
|
||||
while (!canReturnJedisInstance.get()) {
|
||||
canReturnJedisInstance.wait();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return jedis;
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < PushLatencyManager.QUEUE_SIZE * 2; i++) {
|
||||
blockingPushLatencyManager.recordPushSent("account-" + i, 1);
|
||||
}
|
||||
|
||||
synchronized (canReturnJedisInstance) {
|
||||
canReturnJedisInstance.set(true);
|
||||
canReturnJedisInstance.notifyAll();
|
||||
}
|
||||
|
||||
if (!countDownLatch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("Timed out waiting for countdown latch");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public class MessageControllerTest {
|
||||
|
||||
OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false);
|
||||
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(AuthHelper.VALID_UUID), eq(1L))).thenReturn(messagesList);
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(AuthHelper.VALID_UUID), eq(1L), anyString())).thenReturn(messagesList);
|
||||
|
||||
OutgoingMessageEntityList response =
|
||||
resources.getJerseyTest().target("/v1/messages/")
|
||||
@@ -294,7 +294,7 @@ public class MessageControllerTest {
|
||||
|
||||
OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false);
|
||||
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(AuthHelper.VALID_UUID), eq(1L))).thenReturn(messagesList);
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(AuthHelper.VALID_UUID), eq(1L), anyString())).thenReturn(messagesList);
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest().target("/v1/messages/")
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
public class MatcherHelper {
|
||||
public static Account accountWithNumber(final String accountNumber) {
|
||||
return ArgumentMatchers.argThat(account -> accountNumber.equals(account.getNumber()));
|
||||
}
|
||||
|
||||
public static Device deviceWithId(final long deviceId) {
|
||||
return ArgumentMatchers.argThat(device -> device.getId() == deviceId);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.whispersystems.textsecuregcm.tests.websocket;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
@@ -136,12 +137,15 @@ public class WebSocketConnectionTest {
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId()))
|
||||
String userAgent = "user-agent";
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId(), userAgent))
|
||||
.thenReturn(outgoingMessagesList);
|
||||
|
||||
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.getUserAgent()).thenReturn(userAgent);
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<CompletableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
@@ -220,12 +224,15 @@ public class WebSocketConnectionTest {
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId()))
|
||||
String userAgent = "user-agent";
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId(), userAgent))
|
||||
.thenReturn(pendingMessagesList);
|
||||
|
||||
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.getUserAgent()).thenReturn(userAgent);
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<CompletableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
@@ -328,12 +335,15 @@ public class WebSocketConnectionTest {
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId()))
|
||||
String userAgent = "user-agent";
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId(), userAgent))
|
||||
.thenReturn(pendingMessagesList);
|
||||
|
||||
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.getUserAgent()).thenReturn(userAgent);
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<CompletableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user