/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
*
* 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.apache.streams.local.queues;
import org.apache.streams.util.ComponentUtils;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ManagementFactory;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.management.InstanceNotFoundException;
import javax.management.ObjectName;
/**
* MultiThread unit tests for {@link org.apache.streams.local.queues.ThroughputQueue}
*/
public class ThroughputQueueMultiThreadTest extends RandomizedTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ThroughputQueueMultiThreadTest.class);
private static final String MBEAN_ID = "testQueue";
private static final String STREAM_ID = "test_stream";
private static long STREAM_START_TIME = (new DateTime()).getMillis();
/**
* Remove registered mbeans from previous tests
* @throws Exception
*/
@After
public void unregisterMXBean() throws Exception {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(String.format(ThroughputQueue.NAME_TEMPLATE, MBEAN_ID, STREAM_ID, STREAM_START_TIME)));
} catch (InstanceNotFoundException ife) {
//No-op
}
}
@After
public void removeLocalMBeans() {
try {
ComponentUtils.removeAllMBeansOfDomain("org.apache.streams.local");
} catch (Exception e) {
//No op. proceed to next test
}
}
/**
* Test that queue will block on puts when the queue is full
* @throws InterruptedException
*/
@Test
public void testBlockOnFullQueue() throws InterruptedException {
int queueSize = randomIntBetween(1, 3000);
ExecutorService executor = Executors.newSingleThreadExecutor();
CountDownLatch full = new CountDownLatch(1);
CountDownLatch finished = new CountDownLatch(1);
ThroughputQueue queue = new ThroughputQueue(queueSize);
BlocksOnFullQueue testThread = new BlocksOnFullQueue(full, finished, queue, queueSize);
executor.submit(testThread);
full.await();
assertEquals(queueSize, queue.size());
assertEquals(queueSize, queue.getCurrentSize());
assertFalse(testThread.isComplete()); //test that it is blocked
safeSleep(1000);
assertFalse(testThread.isComplete()); //still blocked
queue.take();
finished.await();
assertEquals(queueSize, queue.size());
assertEquals(queueSize, queue.getCurrentSize());
assertTrue(testThread.isComplete());
executor.shutdownNow();
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
}
/**
* Test that queue will block on Take when queue is empty
* @throws InterruptedException
*/
@Test
public void testBlockOnEmptyQueue() throws InterruptedException {
int queueSize = randomIntBetween(1, 3000);
ExecutorService executor = Executors.newSingleThreadExecutor();
CountDownLatch empty = new CountDownLatch(1);
CountDownLatch finished = new CountDownLatch(1);
ThroughputQueue queue = new ThroughputQueue();
BlocksOnEmptyQueue testThread = new BlocksOnEmptyQueue(empty, finished, queueSize, queue);
for(int i=0; i < queueSize; ++i) {
queue.put(i);
}
executor.submit(testThread);
empty.await();
assertEquals(0, queue.size());
assertEquals(0, queue.getCurrentSize());
assertFalse(testThread.isComplete());
safeSleep(1000);
assertFalse(testThread.isComplete());
queue.put(1);
finished.await();
assertEquals(0, queue.size());
assertEquals(0, queue.getCurrentSize());
assertTrue(testThread.isComplete());
executor.shutdownNow();
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
}
/**
* Test multiple threads putting and taking from the queue while
* this thread repeatedly calls the MXBean measurement methods.
* Should hammer the queue with request from multiple threads
* of all request types. Purpose is to expose current modification exceptions
* and/or dead locks.
*/
@Test
@Repeat(iterations = 3)
public void testMultiThreadAccessAndInteruptResponse() throws Exception {
int putTakeThreadCount = randomIntBetween(1, 10);
int dataCount = randomIntBetween(1, 2000000);
int pollCount = randomIntBetween(1, 2000000);
int maxSize = randomIntBetween(1, 1000);
CountDownLatch finished = new CountDownLatch(putTakeThreadCount);
ThroughputQueue queue = new ThroughputQueue(maxSize, MBEAN_ID);
ExecutorService executor = Executors.newFixedThreadPool(putTakeThreadCount * 2);
for(int i=0; i < putTakeThreadCount; ++i) {
executor.submit(new PutData(finished, queue, dataCount));
executor.submit(new TakeData(queue));
}
for(int i=0; i < pollCount; ++i) {
queue.getAvgWait();
queue.getAdded();
queue.getCurrentSize();
queue.getMaxWait();
queue.getRemoved();
queue.getThroughput();
}
finished.await();
while(!queue.isEmpty()) {
LOGGER.info("Waiting for queue to be emptied...");
safeSleep(500);
}
long totalData = ((long) dataCount) * putTakeThreadCount;
assertEquals(totalData, queue.getAdded());
assertEquals(totalData, queue.getRemoved());
executor.shutdown();
executor.awaitTermination(1000, TimeUnit.MILLISECONDS); //shutdown puts
executor.shutdownNow();
executor.awaitTermination(1000, TimeUnit.MILLISECONDS); //shutdown takes
//Randomized should not report thread leak
}
private void safeSleep(long sleep) {
try {
Thread.sleep(sleep);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
/**
* Helper runnable for test {@link ThroughputQueueMultiThreadTest#testBlockOnFullQueue()}
*/
private class BlocksOnFullQueue implements Runnable {
private CountDownLatch full;
volatile private boolean complete;
private int queueSize;
private CountDownLatch finished;
private BlockingQueue queue;
public BlocksOnFullQueue(CountDownLatch latch, CountDownLatch finished, BlockingQueue queue, int queueSize) {
this.full = latch;
this.complete = false;
this.queueSize = queueSize;
this.finished = finished;
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < this.queueSize; ++i) {
this.queue.put(i);
}
this.full.countDown();
this.queue.put(0);
this.complete = true;
this.finished.countDown();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
public boolean isComplete() {
return this.complete;
}
}
/**
* Helper runnable class for test {@link ThroughputQueueMultiThreadTest#testBlockOnEmptyQueue()}
*/
private class BlocksOnEmptyQueue implements Runnable {
private CountDownLatch full;
volatile private boolean complete;
private int queueSize;
private CountDownLatch finished;
private BlockingQueue queue;
public BlocksOnEmptyQueue(CountDownLatch full, CountDownLatch finished, int queueSize, BlockingQueue queue) {
this.full = full;
this.finished = finished;
this.queueSize = queueSize;
this.queue = queue;
this.complete = false;
}
@Override
public void run() {
try {
for(int i=0; i < this.queueSize; ++i) {
this.queue.take();
}
this.full.countDown();
this.queue.take();
this.complete = true;
this.finished.countDown();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
public boolean isComplete() {
return this.complete;
}
}
private class PutData implements Runnable {
private BlockingQueue queue;
private int dataCount;
private CountDownLatch finished;
public PutData(CountDownLatch finished, BlockingQueue queue, int dataCount) {
this.queue = queue;
this.dataCount = dataCount;
this.finished = finished;
}
@Override
public void run() {
try {
for(int i=0; i < this.dataCount; ++i) {
this.queue.put(i);
}
} catch (InterruptedException ie) {
LOGGER.error("PUT DATA interupted !");
Thread.currentThread().interrupt();
}
this.finished.countDown();
}
}
private class TakeData implements Runnable {
private BlockingQueue queue;
public TakeData(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while(true) {
this.queue.take();
}
} catch (InterruptedException ie) {
LOGGER.error("PUT DATA interupted !");
Thread.currentThread().interrupt();
}
}
}
}