Commit 5749fbf88bb94fa1d69725ebaa3e3a4209b07385

Authored by Igor Kulikov
2 parents b578402f 2f6995fc

Merge with master

  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.exception;
  17 +
  18 +public class BufferLimitException extends RuntimeException {
  19 +
  20 + private static final long serialVersionUID = 4513762009041887588L;
  21 +
  22 + public BufferLimitException() {
  23 + super("Rate Limit Buffer is full");
  24 + }
  25 +}
@@ -24,6 +24,7 @@ import com.google.common.util.concurrent.FutureCallback; @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.FutureCallback;
24 import com.google.common.util.concurrent.Futures; 24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListenableFuture; 25 import com.google.common.util.concurrent.ListenableFuture;
26 import com.google.common.util.concurrent.Uninterruptibles; 26 import com.google.common.util.concurrent.Uninterruptibles;
  27 +import org.thingsboard.server.dao.exception.BufferLimitException;
27 import org.thingsboard.server.dao.util.AsyncRateLimiter; 28 import org.thingsboard.server.dao.util.AsyncRateLimiter;
28 29
29 import javax.annotation.Nullable; 30 import javax.annotation.Nullable;
@@ -35,9 +36,15 @@ public class RateLimitedResultSetFuture implements ResultSetFuture { @@ -35,9 +36,15 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
35 private final ListenableFuture<Void> rateLimitFuture; 36 private final ListenableFuture<Void> rateLimitFuture;
36 37
37 public RateLimitedResultSetFuture(Session session, AsyncRateLimiter rateLimiter, Statement statement) { 38 public RateLimitedResultSetFuture(Session session, AsyncRateLimiter rateLimiter, Statement statement) {
38 - this.rateLimitFuture = rateLimiter.acquireAsync(); 39 + this.rateLimitFuture = Futures.withFallback(rateLimiter.acquireAsync(), t -> {
  40 + if (!(t instanceof BufferLimitException)) {
  41 + rateLimiter.release();
  42 + }
  43 + return Futures.immediateFailedFuture(t);
  44 + });
39 this.originalFuture = Futures.transform(rateLimitFuture, 45 this.originalFuture = Futures.transform(rateLimitFuture,
40 (Function<Void, ResultSetFuture>) i -> executeAsyncWithRelease(rateLimiter, session, statement)); 46 (Function<Void, ResultSetFuture>) i -> executeAsyncWithRelease(rateLimiter, session, statement));
  47 +
41 } 48 }
42 49
43 @Override 50 @Override
@@ -108,10 +115,7 @@ public class RateLimitedResultSetFuture implements ResultSetFuture { @@ -108,10 +115,7 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
108 try { 115 try {
109 ResultSetFuture resultSetFuture = Uninterruptibles.getUninterruptibly(originalFuture); 116 ResultSetFuture resultSetFuture = Uninterruptibles.getUninterruptibly(originalFuture);
110 resultSetFuture.addListener(listener, executor); 117 resultSetFuture.addListener(listener, executor);
111 - } catch (CancellationException e) {  
112 - cancel(false);  
113 - return;  
114 - } catch (ExecutionException e) { 118 + } catch (CancellationException | ExecutionException e) {
115 Futures.immediateFailedFuture(e).addListener(listener, executor); 119 Futures.immediateFailedFuture(e).addListener(listener, executor);
116 } 120 }
117 }, executor); 121 }, executor);
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Value; 23 import org.springframework.beans.factory.annotation.Value;
24 import org.springframework.scheduling.annotation.Scheduled; 24 import org.springframework.scheduling.annotation.Scheduled;
25 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.dao.exception.BufferLimitException;
26 27
27 import java.util.concurrent.*; 28 import java.util.concurrent.*;
28 import java.util.concurrent.atomic.AtomicInteger; 29 import java.util.concurrent.atomic.AtomicInteger;
@@ -41,6 +42,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -41,6 +42,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
41 42
42 private final AtomicInteger maxQueueSize = new AtomicInteger(); 43 private final AtomicInteger maxQueueSize = new AtomicInteger();
43 private final AtomicInteger maxGrantedPermissions = new AtomicInteger(); 44 private final AtomicInteger maxGrantedPermissions = new AtomicInteger();
  45 + private final AtomicInteger totalGranted = new AtomicInteger();
  46 + private final AtomicInteger totalReleased = new AtomicInteger();
  47 + private final AtomicInteger totalRequested = new AtomicInteger();
44 48
45 public BufferedRateLimiter(@Value("${cassandra.query.buffer_size}") int queueLimit, 49 public BufferedRateLimiter(@Value("${cassandra.query.buffer_size}") int queueLimit,
46 @Value("${cassandra.query.concurrent_limit}") int permitsLimit, 50 @Value("${cassandra.query.concurrent_limit}") int permitsLimit,
@@ -53,11 +57,13 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -53,11 +57,13 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
53 57
54 @Override 58 @Override
55 public ListenableFuture<Void> acquireAsync() { 59 public ListenableFuture<Void> acquireAsync() {
  60 + totalRequested.incrementAndGet();
56 if (queue.isEmpty()) { 61 if (queue.isEmpty()) {
57 if (permits.incrementAndGet() <= permitsLimit) { 62 if (permits.incrementAndGet() <= permitsLimit) {
58 if (permits.get() > maxGrantedPermissions.get()) { 63 if (permits.get() > maxGrantedPermissions.get()) {
59 maxGrantedPermissions.set(permits.get()); 64 maxGrantedPermissions.set(permits.get());
60 } 65 }
  66 + totalGranted.incrementAndGet();
61 return Futures.immediateFuture(null); 67 return Futures.immediateFuture(null);
62 } 68 }
63 permits.decrementAndGet(); 69 permits.decrementAndGet();
@@ -69,6 +75,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -69,6 +75,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
69 @Override 75 @Override
70 public void release() { 76 public void release() {
71 permits.decrementAndGet(); 77 permits.decrementAndGet();
  78 + totalReleased.incrementAndGet();
72 reprocessQueue(); 79 reprocessQueue();
73 } 80 }
74 81
@@ -80,6 +87,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -80,6 +87,7 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
80 } 87 }
81 LockedFuture lockedFuture = queue.poll(); 88 LockedFuture lockedFuture = queue.poll();
82 if (lockedFuture != null) { 89 if (lockedFuture != null) {
  90 + totalGranted.incrementAndGet();
83 lockedFuture.latch.countDown(); 91 lockedFuture.latch.countDown();
84 } else { 92 } else {
85 permits.decrementAndGet(); 93 permits.decrementAndGet();
@@ -112,17 +120,20 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -112,17 +120,20 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
112 LockedFuture lockedFuture = createLockedFuture(); 120 LockedFuture lockedFuture = createLockedFuture();
113 if (!queue.offer(lockedFuture, 1, TimeUnit.SECONDS)) { 121 if (!queue.offer(lockedFuture, 1, TimeUnit.SECONDS)) {
114 lockedFuture.cancelFuture(); 122 lockedFuture.cancelFuture();
115 - return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); 123 + return Futures.immediateFailedFuture(new BufferLimitException());
  124 + }
  125 + if(permits.get() < permitsLimit) {
  126 + reprocessQueue();
116 } 127 }
117 if(permits.get() < permitsLimit) { 128 if(permits.get() < permitsLimit) {
118 reprocessQueue(); 129 reprocessQueue();
119 } 130 }
120 return lockedFuture.future; 131 return lockedFuture.future;
121 } catch (InterruptedException e) { 132 } catch (InterruptedException e) {
122 - return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject")); 133 + return Futures.immediateFailedFuture(new BufferLimitException());
123 } 134 }
124 } 135 }
125 - return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); 136 + return Futures.immediateFailedFuture(new BufferLimitException());
126 } 137 }
127 138
128 @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") 139 @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
@@ -134,8 +145,11 @@ public class BufferedRateLimiter implements AsyncRateLimiter { @@ -134,8 +145,11 @@ public class BufferedRateLimiter implements AsyncRateLimiter {
134 expiredCount++; 145 expiredCount++;
135 } 146 }
136 } 147 }
137 - log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}] current granted [{}]", maxQueueSize.getAndSet(0),  
138 - maxGrantedPermissions.getAndSet(0), expiredCount, permits.get()); 148 + log.info("Permits maxBuffer [{}] maxPermits [{}] expired [{}] currPermits [{}] currBuffer [{}] " +
  149 + "totalPermits [{}] totalRequests [{}] totalReleased [{}]",
  150 + maxQueueSize.getAndSet(0), maxGrantedPermissions.getAndSet(0), expiredCount,
  151 + permits.get(), queue.size(),
  152 + totalGranted.getAndSet(0), totalRequested.getAndSet(0), totalReleased.getAndSet(0));
139 } 153 }
140 154
141 private class LockedFuture { 155 private class LockedFuture {
@@ -19,16 +19,17 @@ import com.datastax.driver.core.*; @@ -19,16 +19,17 @@ import com.datastax.driver.core.*;
19 import com.datastax.driver.core.exceptions.UnsupportedFeatureException; 19 import com.datastax.driver.core.exceptions.UnsupportedFeatureException;
20 import com.google.common.util.concurrent.Futures; 20 import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture; 21 import com.google.common.util.concurrent.ListenableFuture;
  22 +import com.google.common.util.concurrent.MoreExecutors;
22 import org.junit.Test; 23 import org.junit.Test;
23 import org.junit.runner.RunWith; 24 import org.junit.runner.RunWith;
24 import org.mockito.Mock; 25 import org.mockito.Mock;
25 import org.mockito.Mockito; 26 import org.mockito.Mockito;
26 import org.mockito.runners.MockitoJUnitRunner; 27 import org.mockito.runners.MockitoJUnitRunner;
27 import org.mockito.stubbing.Answer; 28 import org.mockito.stubbing.Answer;
  29 +import org.thingsboard.server.dao.exception.BufferLimitException;
28 import org.thingsboard.server.dao.util.AsyncRateLimiter; 30 import org.thingsboard.server.dao.util.AsyncRateLimiter;
29 31
30 -import java.util.concurrent.ExecutionException;  
31 -import java.util.concurrent.TimeoutException; 32 +import java.util.concurrent.*;
32 33
33 import static org.junit.Assert.*; 34 import static org.junit.Assert.*;
34 import static org.mockito.Mockito.*; 35 import static org.mockito.Mockito.*;
@@ -53,7 +54,7 @@ public class RateLimitedResultSetFutureTest { @@ -53,7 +54,7 @@ public class RateLimitedResultSetFutureTest {
53 54
54 @Test 55 @Test
55 public void doNotReleasePermissionIfRateLimitFutureFailed() throws InterruptedException { 56 public void doNotReleasePermissionIfRateLimitFutureFailed() throws InterruptedException {
56 - when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFailedFuture(new IllegalArgumentException())); 57 + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFailedFuture(new BufferLimitException()));
57 resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); 58 resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
58 Thread.sleep(1000L); 59 Thread.sleep(1000L);
59 verify(rateLimiter).acquireAsync(); 60 verify(rateLimiter).acquireAsync();
@@ -153,4 +154,29 @@ public class RateLimitedResultSetFutureTest { @@ -153,4 +154,29 @@ public class RateLimitedResultSetFutureTest {
153 verify(rateLimiter, times(1)).release(); 154 verify(rateLimiter, times(1)).release();
154 } 155 }
155 156
  157 + @Test
  158 + public void expiredQueryReturnPermit() throws InterruptedException, ExecutionException {
  159 + CountDownLatch latch = new CountDownLatch(1);
  160 + ListenableFuture<Void> future = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)).submit(() -> {
  161 + latch.await();
  162 + return null;
  163 + });
  164 + when(rateLimiter.acquireAsync()).thenReturn(future);
  165 + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
  166 +
  167 + ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
  168 +// TimeUnit.MILLISECONDS.sleep(200);
  169 + future.cancel(false);
  170 + latch.countDown();
  171 +
  172 + try {
  173 + transform.get();
  174 + fail();
  175 + } catch (Exception e) {
  176 + assertTrue(e instanceof ExecutionException);
  177 + }
  178 + verify(rateLimiter, times(1)).acquireAsync();
  179 + verify(rateLimiter, times(1)).release();
  180 + }
  181 +
156 } 182 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.util; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.util;
17 17
18 import com.google.common.util.concurrent.*; 18 import com.google.common.util.concurrent.*;
19 import org.junit.Test; 19 import org.junit.Test;
  20 +import org.thingsboard.server.dao.exception.BufferLimitException;
20 21
21 import javax.annotation.Nullable; 22 import javax.annotation.Nullable;
22 import java.util.concurrent.ExecutionException; 23 import java.util.concurrent.ExecutionException;
@@ -61,8 +62,8 @@ public class BufferedRateLimiterTest { @@ -61,8 +62,8 @@ public class BufferedRateLimiterTest {
61 } catch (Exception e) { 62 } catch (Exception e) {
62 assertTrue(e instanceof ExecutionException); 63 assertTrue(e instanceof ExecutionException);
63 Throwable actualCause = e.getCause(); 64 Throwable actualCause = e.getCause();
64 - assertTrue(actualCause instanceof IllegalStateException);  
65 - assertEquals("Rate Limit Buffer is full. Reject", actualCause.getMessage()); 65 + assertTrue(actualCause instanceof BufferLimitException);
  66 + assertEquals("Rate Limit Buffer is full", actualCause.getMessage());
66 } 67 }
67 } 68 }
68 69
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 }, 15 },
16 "dependencies": { 16 "dependencies": {
17 "@flowjs/ng-flow": "^2.7.1", 17 "@flowjs/ng-flow": "^2.7.1",
18 - "ace-builds": "^1.2.5", 18 + "ace-builds": "1.3.1",
19 "angular": "1.5.8", 19 "angular": "1.5.8",
20 "angular-animate": "1.5.8", 20 "angular-animate": "1.5.8",
21 "angular-aria": "1.5.8", 21 "angular-aria": "1.5.8",