Query for more stored messages if an update happens while we're already processing a batch.
This commit is contained in:
		
							parent
							
								
									8f53152c3e
								
							
						
					
					
						commit
						f766c57743
					
				| 
						 | 
					@ -65,6 +65,7 @@ public class WebSocketConnection implements DispatchChannel, MessageAvailability
 | 
				
			||||||
  private final WebSocketClient  client;
 | 
					  private final WebSocketClient  client;
 | 
				
			||||||
  private final String           connectionId;
 | 
					  private final String           connectionId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private       int           storedMessageState           = 0;
 | 
				
			||||||
  private       boolean       processingStoredMessages     = false;
 | 
					  private       boolean       processingStoredMessages     = false;
 | 
				
			||||||
  private final AtomicBoolean sentInitialQueueEmptyMessage = new AtomicBoolean(false);
 | 
					  private final AtomicBoolean sentInitialQueueEmptyMessage = new AtomicBoolean(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,6 +94,11 @@ public class WebSocketConnection implements DispatchChannel, MessageAvailability
 | 
				
			||||||
      switch (pubSubMessage.getType().getNumber()) {
 | 
					      switch (pubSubMessage.getType().getNumber()) {
 | 
				
			||||||
        case PubSubMessage.Type.QUERY_DB_VALUE:
 | 
					        case PubSubMessage.Type.QUERY_DB_VALUE:
 | 
				
			||||||
          pubSubPersistedMeter.mark();
 | 
					          pubSubPersistedMeter.mark();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          synchronized (this) {
 | 
				
			||||||
 | 
					            storedMessageState++;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          processStoredMessages();
 | 
					          processStoredMessages();
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case PubSubMessage.Type.DELIVER_VALUE:
 | 
					        case PubSubMessage.Type.DELIVER_VALUE:
 | 
				
			||||||
| 
						 | 
					@ -184,12 +190,15 @@ public class WebSocketConnection implements DispatchChannel, MessageAvailability
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @VisibleForTesting
 | 
					  @VisibleForTesting
 | 
				
			||||||
  void processStoredMessages() {
 | 
					  void processStoredMessages() {
 | 
				
			||||||
 | 
					    final int processedState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    synchronized (this) {
 | 
					    synchronized (this) {
 | 
				
			||||||
      if (processingStoredMessages) {
 | 
					      if (processingStoredMessages) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      processingStoredMessages = true;
 | 
					      processingStoredMessages = true;
 | 
				
			||||||
 | 
					      processedState           = storedMessageState;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    OutgoingMessageEntityList messages    = messagesManager.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId(), client.getUserAgent());
 | 
					    OutgoingMessageEntityList messages    = messagesManager.getMessagesForDevice(account.getNumber(), account.getUuid(), device.getId(), client.getUserAgent());
 | 
				
			||||||
| 
						 | 
					@ -227,7 +236,7 @@ public class WebSocketConnection implements DispatchChannel, MessageAvailability
 | 
				
			||||||
        processingStoredMessages = false;
 | 
					        processingStoredMessages = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (messages.hasMore()) {
 | 
					      if (messages.hasMore() || storedMessageState > processedState) {
 | 
				
			||||||
        processStoredMessages();
 | 
					        processStoredMessages();
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        final boolean shouldSendEmptyQueueMessage;
 | 
					        final boolean shouldSendEmptyQueueMessage;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -522,6 +522,57 @@ public class WebSocketConnectionTest {
 | 
				
			||||||
    verify(client, times(1)).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), any(List.class), eq(Optional.empty()));
 | 
					    verify(client, times(1)).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), any(List.class), eq(Optional.empty()));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void testRequeryAfterOnStateMismatch() throws InterruptedException {
 | 
				
			||||||
 | 
					    final MessagesManager     messagesManager = mock(MessagesManager.class);
 | 
				
			||||||
 | 
					    final WebSocketClient     client          = mock(WebSocketClient.class);
 | 
				
			||||||
 | 
					    final WebSocketConnection connection      = new WebSocketConnection(pushSender, receiptSender, messagesManager, account, device, client, "concurrency");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when(account.getNumber()).thenReturn("+18005551234");
 | 
				
			||||||
 | 
					    when(account.getUuid()).thenReturn(UUID.randomUUID());
 | 
				
			||||||
 | 
					    when(device.getId()).thenReturn(1L);
 | 
				
			||||||
 | 
					    when(client.getUserAgent()).thenReturn("Test-UA");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final List<OutgoingMessageEntity> firstPageMessages =
 | 
				
			||||||
 | 
					            List.of(createMessage(1L, false, "sender1", UUID.randomUUID(), 1111, false, "first"),
 | 
				
			||||||
 | 
					                    createMessage(2L, false, "sender1", UUID.randomUUID(), 2222, false, "second"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final List<OutgoingMessageEntity> secondPageMessages =
 | 
				
			||||||
 | 
					            List.of(createMessage(3L, false, "sender1", UUID.randomUUID(), 3333, false, "third"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final OutgoingMessageEntityList firstPage  = new OutgoingMessageEntityList(firstPageMessages, false);
 | 
				
			||||||
 | 
					    final OutgoingMessageEntityList secondPage = new OutgoingMessageEntityList(secondPageMessages, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when(messagesManager.getMessagesForDevice(account.getNumber(), account.getUuid(), 1L, client.getUserAgent()))
 | 
				
			||||||
 | 
					            .thenReturn(firstPage)
 | 
				
			||||||
 | 
					            .thenReturn(secondPage)
 | 
				
			||||||
 | 
					            .thenReturn(new OutgoingMessageEntityList(Collections.emptyList(), false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
 | 
				
			||||||
 | 
					    when(successResponse.getStatus()).thenReturn(200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final byte[] queryDbMessageBytes = PubSubProtos.PubSubMessage.newBuilder()
 | 
				
			||||||
 | 
					                                                                 .setType(PubSubProtos.PubSubMessage.Type.QUERY_DB)
 | 
				
			||||||
 | 
					                                                                 .build()
 | 
				
			||||||
 | 
					                                                                 .toByteArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final CountDownLatch sendLatch = new CountDownLatch(firstPageMessages.size() + secondPageMessages.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), any(List.class), any(Optional.class))).thenAnswer((Answer<CompletableFuture<WebSocketResponseMessage>>)invocation -> {
 | 
				
			||||||
 | 
					      connection.onDispatchMessage("channel", queryDbMessageBytes);
 | 
				
			||||||
 | 
					      sendLatch.countDown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return CompletableFuture.completedFuture(successResponse);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connection.processStoredMessages();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendLatch.await();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    verify(client, times(firstPageMessages.size() + secondPageMessages.size())).sendRequest(eq("PUT"), eq("/api/v1/message"), any(List.class), any(Optional.class));
 | 
				
			||||||
 | 
					    verify(client).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), any(List.class), eq(Optional.empty()));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, UUID senderUuid, long timestamp, boolean receipt, String content) {
 | 
					  private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, UUID senderUuid, long timestamp, boolean receipt, String content) {
 | 
				
			||||||
    return new OutgoingMessageEntity(id, cached, UUID.randomUUID(), receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE,
 | 
					    return new OutgoingMessageEntity(id, cached, UUID.randomUUID(), receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE,
 | 
				
			||||||
                                     null, timestamp, sender, senderUuid, 1, content.getBytes(), null, 0);
 | 
					                                     null, timestamp, sender, senderUuid, 1, content.getBytes(), null, 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue