/*
* Copyright 2015 Netflix, Inc.
*
* 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 com.netflix.eureka.util.batcher;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import com.netflix.eureka.util.batcher.TaskProcessor.ProcessingResult;
import org.junit.After;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Tomasz Bak
*/
public class TaskDispatchersTest {
private static final long SERVER_UNAVAILABLE_SLEEP_TIME_MS = 1000;
private static final long RETRY_SLEEP_TIME_MS = 100;
private static final long MAX_BATCHING_DELAY_MS = 10;
private static final int MAX_BUFFER_SIZE = 1000000;
private static final int WORK_LOAD_SIZE = 2;
private final RecordingProcessor processor = new RecordingProcessor();
private TaskDispatcher<Integer, ProcessingResult> dispatcher;
@After
public void tearDown() throws Exception {
if (dispatcher != null) {
dispatcher.shutdown();
}
}
@Test
public void testSingleTaskDispatcher() throws Exception {
dispatcher = TaskDispatchers.createNonBatchingTaskDispatcher(
"TEST",
MAX_BUFFER_SIZE,
1,
MAX_BATCHING_DELAY_MS,
SERVER_UNAVAILABLE_SLEEP_TIME_MS,
RETRY_SLEEP_TIME_MS,
processor
);
dispatcher.process(1, ProcessingResult.Success, System.currentTimeMillis() + 60 * 1000);
ProcessingResult result = processor.completedTasks.poll(5, TimeUnit.SECONDS);
assertThat(result, is(equalTo(ProcessingResult.Success)));
}
@Test
public void testBatchingDispatcher() throws Exception {
dispatcher = TaskDispatchers.createBatchingTaskDispatcher(
"TEST",
MAX_BUFFER_SIZE,
WORK_LOAD_SIZE,
1,
MAX_BATCHING_DELAY_MS,
SERVER_UNAVAILABLE_SLEEP_TIME_MS,
RETRY_SLEEP_TIME_MS,
processor
);
dispatcher.process(1, ProcessingResult.Success, System.currentTimeMillis() + 60 * 1000);
dispatcher.process(2, ProcessingResult.Success, System.currentTimeMillis() + 60 * 1000);
processor.expectSuccesses(2);
}
@Test
public void testTasksAreDistributedAcrossAllWorkerThreads() throws Exception {
int threadCount = 3;
CountingTaskProcessor countingProcessor = new CountingTaskProcessor();
TaskDispatcher<Integer, Boolean> dispatcher = TaskDispatchers.createBatchingTaskDispatcher(
"TEST",
MAX_BUFFER_SIZE,
WORK_LOAD_SIZE,
threadCount,
MAX_BATCHING_DELAY_MS,
SERVER_UNAVAILABLE_SLEEP_TIME_MS,
RETRY_SLEEP_TIME_MS,
countingProcessor
);
try {
int loops = 1000;
while (true) {
countingProcessor.resetTo(loops);
for (int i = 0; i < loops; i++) {
dispatcher.process(i, true, System.currentTimeMillis() + 60 * 1000);
}
countingProcessor.awaitCompletion();
int minHitPerThread = (int) (loops / threadCount * 0.9);
if (countingProcessor.lowestHit() < minHitPerThread) {
loops *= 2;
} else {
break;
}
if (loops > MAX_BUFFER_SIZE) {
fail("Uneven load distribution");
}
}
} finally {
dispatcher.shutdown();
}
}
static class CountingTaskProcessor implements TaskProcessor<Boolean> {
final ConcurrentMap<Thread, Integer> threadHits = new ConcurrentHashMap<>();
volatile Semaphore completionGuard;
@Override
public ProcessingResult process(Boolean task) {
throw new IllegalStateException("unexpected");
}
@Override
public ProcessingResult process(List<Boolean> tasks) {
Thread currentThread = Thread.currentThread();
Integer current = threadHits.get(currentThread);
if (current == null) {
threadHits.put(currentThread, tasks.size());
} else {
threadHits.put(currentThread, tasks.size() + current);
}
completionGuard.release(tasks.size());
return ProcessingResult.Success;
}
void resetTo(int expectedTasks) {
completionGuard = new Semaphore(-expectedTasks + 1);
}
void awaitCompletion() throws InterruptedException {
assertThat(completionGuard.tryAcquire(5, TimeUnit.SECONDS), is(true));
}
int lowestHit() {
return Collections.min(threadHits.values());
}
}
}