Browse Source

test: fixed class DefaultTbQueueRequestTemplate

pull/4753/head
Sergey Matvienko 5 years ago
parent
commit
28235732c6
  1. 90
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java
  2. 37
      common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java

90
common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java

@ -36,10 +36,12 @@ import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> extends AbstractTbQueueTemplate public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> extends AbstractTbQueueTemplate
@ -48,16 +50,15 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
private final TbQueueAdmin queueAdmin; private final TbQueueAdmin queueAdmin;
private final TbQueueProducer<Request> requestTemplate; private final TbQueueProducer<Request> requestTemplate;
private final TbQueueConsumer<Response> responseTemplate; private final TbQueueConsumer<Response> responseTemplate;
final ConcurrentMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests; final ConcurrentHashMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests = new ConcurrentHashMap<>();
final boolean internalExecutor; final boolean internalExecutor;
final ExecutorService executor; final ExecutorService executor;
final long maxRequestTimeout; final long maxRequestTimeoutNs;
final long maxPendingRequests; final long maxPendingRequests;
final long pollInterval; final long pollInterval;
volatile long tickTs = 0L;
volatile long tickSize = 0L;
volatile boolean stopped = false; volatile boolean stopped = false;
long nextCleanupMs = 0L; long nextCleanupNs = 0L;
private final Lock cleanerLock = new ReentrantLock();
private MessagesStats messagesStats; private MessagesStats messagesStats;
@ -72,8 +73,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
this.queueAdmin = queueAdmin; this.queueAdmin = queueAdmin;
this.requestTemplate = requestTemplate; this.requestTemplate = requestTemplate;
this.responseTemplate = responseTemplate; this.responseTemplate = responseTemplate;
this.pendingRequests = new ConcurrentHashMap<>(); this.maxRequestTimeoutNs = TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout);
this.maxRequestTimeout = maxRequestTimeout;
this.maxPendingRequests = maxPendingRequests; this.maxPendingRequests = maxPendingRequests;
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.internalExecutor = (executor == null); this.internalExecutor = (executor == null);
@ -88,7 +88,6 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
public void init() { public void init() {
queueAdmin.createTopicIfNotExists(responseTemplate.getTopic()); queueAdmin.createTopicIfNotExists(responseTemplate.getTopic());
requestTemplate.init(); requestTemplate.init();
tickTs = getCurrentTime();
responseTemplate.subscribe(); responseTemplate.subscribe();
executor.submit(this::mainLoop); executor.submit(this::mainLoop);
} }
@ -105,7 +104,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
} }
void fetchAndProcessResponses() { void fetchAndProcessResponses() {
final int pendingRequestsCount = pendingRequests.size(); final long pendingRequestsCount = pendingRequests.mappingCount();
log.info("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount); log.info("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount);
List<Response> responses = doPoll(); //poll js responses List<Response> responses = doPoll(); //poll js responses
//if (responses.size() > 0) { //if (responses.size() > 0) {
@ -113,25 +112,36 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
//} //}
responses.forEach(this::processResponse); //this can take a long time responses.forEach(this::processResponse); //this can take a long time
responseTemplate.commit(); responseTemplate.commit();
tickTs = getCurrentTime(); tryCleanStaleRequests();
tickSize = pendingRequests.size(); }
if (nextCleanupMs < tickTs) {
//cleanup; private boolean tryCleanStaleRequests() {
pendingRequests.forEach((key, value) -> { if (!cleanerLock.tryLock()) {
if (value.expTime < tickTs) { return false;
ResponseMetaData<Response> staleRequest = pendingRequests.remove(key); }
if (staleRequest != null) { try {
setTimeoutException(key, staleRequest, tickTs); log.trace("tryCleanStaleRequest...");
final long currentNs = getCurrentClockNs();
if (nextCleanupNs < currentNs) {
pendingRequests.forEach((key, value) -> {
if (value.expTime < currentNs) {
ResponseMetaData<Response> staleRequest = pendingRequests.remove(key);
if (staleRequest != null) {
setTimeoutException(key, staleRequest, currentNs);
}
} }
} });
}); setupNextCleanup();
setupNextCleanup(); }
} finally {
cleanerLock.unlock();
} }
return true;
} }
void setupNextCleanup() { void setupNextCleanup() {
nextCleanupMs = tickTs + maxRequestTimeout; nextCleanupNs = getCurrentClockNs() + maxRequestTimeoutNs;
log.info("setupNextCleanup {}", nextCleanupMs); log.info("setupNextCleanup {}", nextCleanupNs);
} }
List<Response> doPoll() { List<Response> doPoll() {
@ -146,11 +156,11 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
} }
} }
void setTimeoutException(UUID key, ResponseMetaData<Response> staleRequest, long tickTs) { void setTimeoutException(UUID key, ResponseMetaData<Response> staleRequest, long currentNs) {
if (tickTs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { if (currentNs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) {
log.info("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); log.info("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key);
} else { } else {
log.error("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); log.error("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key);
} }
staleRequest.future.setException(new TimeoutException()); staleRequest.future.setException(new TimeoutException());
@ -197,23 +207,31 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
@Override @Override
public ListenableFuture<Response> send(Request request) { public ListenableFuture<Response> send(Request request) {
if (tickSize > maxPendingRequests) { if (pendingRequests.mappingCount() >= maxPendingRequests) {
log.warn("Pending request map is full [{}]! Consider to increase maxPendingRequests or increase processing performance", maxPendingRequests);
return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!")); return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!"));
} }
UUID requestId = UUID.randomUUID(); UUID requestId = UUID.randomUUID();
request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId)); request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic())); request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()));
long currentTime = getCurrentTime(); request.getHeaders().put(REQUEST_TIME, longToBytes(getCurrentTimeMs()));
request.getHeaders().put(REQUEST_TIME, longToBytes(currentTime)); long currentClockNs = getCurrentClockNs();
SettableFuture<Response> future = SettableFuture.create(); SettableFuture<Response> future = SettableFuture.create();
ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future, currentTime, maxRequestTimeout); ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(currentClockNs + maxRequestTimeoutNs, future, currentClockNs, maxRequestTimeoutNs);
log.info("pending {}", responseMetaData); log.info("pending {}", responseMetaData); //TODO trace
pendingRequests.putIfAbsent(requestId, responseMetaData); if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) {
log.warn("Pending request already exists [{}]!", maxPendingRequests);
return Futures.immediateFailedFuture(new RuntimeException("Pending request already exists !" + requestId));
}
sendToRequestTemplate(request, requestId, future, responseMetaData); sendToRequestTemplate(request, requestId, future, responseMetaData);
return future; return future;
} }
long getCurrentTime() { long getCurrentClockNs() {
return System.nanoTime(); //MONOTONIC clock instead wall clock
}
long getCurrentTimeMs() { //Wall clock to send Ts to the an external service
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
@ -261,8 +279,8 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
return "ResponseMetaData{" + return "ResponseMetaData{" +
"submitTime=" + submitTime + "submitTime=" + submitTime +
", calculatedExpTime=" + (submitTime + timeout) + ", calculatedExpTime=" + (submitTime + timeout) +
", expTime=" + expTime +
", deltaMs=" + (expTime - submitTime) + ", deltaMs=" + (expTime - submitTime) +
", expTime=" + expTime +
", future=" + future + ", future=" + future +
'}'; '}';
} }

37
common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java

@ -29,6 +29,7 @@
* OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
*/ */
package org.thingsboard.server.queue.common; package org.thingsboard.server.queue.common;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -52,9 +53,9 @@ import java.util.concurrent.atomic.AtomicLong;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willDoNothing;
@ -86,7 +87,7 @@ public class DefaultTbQueueRequestTemplateTest {
ExecutorService executor; ExecutorService executor;
String topic = "js-responses-tb-node-0"; String topic = "js-responses-tb-node-0";
long maxRequestTimeout = 10; long maxRequestTimeout = 10;
long maxPendingRequests = 32; long maxPendingRequests = 1000;
long pollInterval = 5; long pollInterval = 5;
DefaultTbQueueRequestTemplate inst; DefaultTbQueueRequestTemplate inst;
@ -109,12 +110,12 @@ public class DefaultTbQueueRequestTemplateTest {
@Test @Test
public void givenInstance_whenVerifyInitialParameters_thenOK() { public void givenInstance_whenVerifyInitialParameters_thenOK() {
assertEquals(maxPendingRequests, inst.maxPendingRequests); assertThat(inst.maxPendingRequests, equalTo(maxPendingRequests));
assertEquals(maxRequestTimeout, inst.maxRequestTimeout); assertThat(inst.maxRequestTimeoutNs, equalTo(TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout)));
assertEquals(pollInterval, inst.pollInterval); assertThat(inst.pollInterval, equalTo(pollInterval));
assertEquals(executorMock, inst.executor); assertThat(inst.executor, is(executorMock));
assertFalse(inst.stopped); assertThat(inst.stopped, is(false));
assertFalse(inst.internalExecutor); assertThat(inst.internalExecutor, is(false));
} }
@Test @Test
@ -122,8 +123,8 @@ public class DefaultTbQueueRequestTemplateTest {
willDoNothing().given(inst).mainLoop(); willDoNothing().given(inst).mainLoop();
inst.init(); inst.init();
assertNotEquals(0, inst.tickTs); //assertNotEquals(0, inst.tickTs);
assertEquals(0, inst.nextCleanupMs); assertEquals(0, inst.nextCleanupNs);
verify(queueAdmin, times(1)).createTopicIfNotExists(topic); verify(queueAdmin, times(1)).createTopicIfNotExists(topic);
verify(requestTemplate, times(1)).init(); verify(requestTemplate, times(1)).init();
verify(responseTemplate, times(1)).subscribe(); verify(responseTemplate, times(1)).subscribe();
@ -168,7 +169,7 @@ public class DefaultTbQueueRequestTemplateTest {
for (int i = 0; i < msgCount; i++) { for (int i = 0; i < msgCount; i++) {
inst.send(getRequestMsgMock()); inst.send(getRequestMsgMock());
} }
assertEquals(msgCount, inst.pendingRequests.size()); assertEquals(msgCount, inst.pendingRequests.mappingCount());
verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any()); verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any());
} }
@ -176,15 +177,14 @@ public class DefaultTbQueueRequestTemplateTest {
public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() { public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() {
willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any());
inst.init(); inst.init();
assertEquals(0, inst.tickSize);
int msgOverflowCount = 10; int msgOverflowCount = 10;
for (int i = 0; i < inst.maxPendingRequests; i++) { for (int i = 0; i < inst.maxPendingRequests; i++) {
assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only
} }
for (int i = 0; i < msgOverflowCount; i++) { for (int i = 0; i < msgOverflowCount; i++) {
assertFalse("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future assertTrue("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future
} }
assertThat(inst.pendingRequests.size(), equalTo(inst.maxPendingRequests)); assertThat(inst.pendingRequests.mappingCount(), equalTo(inst.maxPendingRequests));
verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any());
} }
@ -195,17 +195,18 @@ public class DefaultTbQueueRequestTemplateTest {
willAnswer(x -> { willAnswer(x -> {
log.info("currentTime={}", currentTime.get()); log.info("currentTime={}", currentTime.get());
return currentTime.get(); return currentTime.get();
}).given(inst).getCurrentTime(); }).given(inst).getCurrentClockNs();
inst.init(); inst.init();
inst.setupNextCleanup(); inst.setupNextCleanup();
willReturn(Collections.emptyList()).given(inst).doPoll(); willReturn(Collections.emptyList()).given(inst).doPoll();
willDoNothing().given(inst).processResponse(any()); willDoNothing().given(inst).processResponse(any());
//when //when
for (int i = 0; i <= inst.maxRequestTimeout*2; i++) { long stepNs = TimeUnit.MILLISECONDS.toNanos(1);
currentTime.incrementAndGet(); for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) {
currentTime.addAndGet(stepNs);
assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only
if (i % (inst.maxRequestTimeout * 3 / 2) == 0) { if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) {
inst.fetchAndProcessResponses(); inst.fetchAndProcessResponses();
} }
} }

Loading…
Cancel
Save