committed by
GitHub
2 changed files with 0 additions and 258 deletions
@ -1,85 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.utils; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.concurrent.Executor; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* This class deduplicate executions of the specified function. |
|||
* Useful in cluster mode, when you get event about partition change multiple times. |
|||
* Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and |
|||
* later, once the processing of first event is done, process last pending task. |
|||
* |
|||
* @param <P> parameters of the function |
|||
*/ |
|||
@Slf4j |
|||
public class EventDeduplicationExecutor<P> { |
|||
private final String name; |
|||
private final ExecutorService executor; |
|||
private final Consumer<P> function; |
|||
private P pendingTask; |
|||
private boolean busy; |
|||
|
|||
public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer<P> function) { |
|||
this.name = name; |
|||
this.executor = executor; |
|||
this.function = function; |
|||
} |
|||
|
|||
public void submit(P params) { |
|||
log.info("[{}] Going to submit: {}", name, params); |
|||
synchronized (EventDeduplicationExecutor.this) { |
|||
if (!busy) { |
|||
busy = true; |
|||
pendingTask = null; |
|||
try { |
|||
log.info("[{}] Submitting task: {}", name, params); |
|||
executor.submit(() -> { |
|||
try { |
|||
log.info("[{}] Executing task: {}", name, params); |
|||
function.accept(params); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process task with parameters: {}", name, params, e); |
|||
throw e; |
|||
} finally { |
|||
unlockAndProcessIfAny(); |
|||
} |
|||
}); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to submit task with parameters: {}", name, params, e); |
|||
unlockAndProcessIfAny(); |
|||
throw e; |
|||
} |
|||
} else { |
|||
log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params); |
|||
pendingTask = params; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void unlockAndProcessIfAny() { |
|||
synchronized (EventDeduplicationExecutor.this) { |
|||
busy = false; |
|||
if (pendingTask != null) { |
|||
submit(pendingTask); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,173 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.util; |
|||
|
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.Mockito; |
|||
import org.mockito.junit.MockitoJUnitRunner; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.utils.EventDeduplicationExecutor; |
|||
|
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.function.Consumer; |
|||
|
|||
@Slf4j |
|||
@RunWith(MockitoJUnitRunner.class) |
|||
public class EventDeduplicationExecutorTest { |
|||
|
|||
ThingsBoardThreadFactory threadFactory = ThingsBoardThreadFactory.forName(getClass().getSimpleName()); |
|||
ExecutorService executor; |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
if (executor != null) { |
|||
executor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowSameThread() throws InterruptedException { |
|||
simpleFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowSameThread() throws InterruptedException { |
|||
periodicFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowSameThread() throws InterruptedException { |
|||
exceptionFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
simpleFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
periodicFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
exceptionFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
simpleFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
periodicFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
exceptionFlow(executor); |
|||
} |
|||
|
|||
private void simpleFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
executor.submit(params1); |
|||
executor.submit(params2); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params1); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
private void periodicFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
executor.submit(params1); |
|||
Thread.sleep(500); |
|||
executor.submit(params2); |
|||
Thread.sleep(500); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params1); |
|||
Mockito.verify(function).accept(params2); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
private void exceptionFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
Mockito.doThrow(new RuntimeException()).when(function).accept("params1"); |
|||
executor.submit(params1); |
|||
executor.submit(params2); |
|||
Thread.sleep(500); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params2); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
public static class StringConsumer implements Consumer<String> { |
|||
@Override |
|||
public void accept(String s) { |
|||
try { |
|||
Thread.sleep(100); |
|||
} catch (InterruptedException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue