/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Sep 10, 2008 */ package com.bigdata.relation.accesspath; import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase2; import com.bigdata.util.DaemonThreadFactory; /** * Test suite for {@link BlockingBuffer} and its {@link IAsynchronousIterator} * when using an array type for the elements (chunk processing). * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ * * @todo test ordered chunk process also. */ public class TestBlockingBufferWithChunks extends TestCase2 { /** * */ public TestBlockingBufferWithChunks() { } /** * @param arg0 */ public TestBlockingBufferWithChunks(String arg0) { super(arg0); } private final ExecutorService service = Executors .newCachedThreadPool(DaemonThreadFactory.defaultThreadFactory()); protected void tearDown() throws Exception { service.shutdownNow(); super.tearDown(); } /** * Basic test of the ability to add to a buffer with a fixed capacity queue * and to drain the elements from the queue including tests of the * non-blocking aspects of the API. * * @throws TimeoutException * @throws ExecutionException * @throws InterruptedException */ public void test_blockingBuffer() throws InterruptedException, ExecutionException, TimeoutException { final Integer e0 = new Integer(0); final Integer e1 = new Integer(1); final Integer e2 = new Integer(2); final int queueCapacity = 3; final BlockingQueue<Integer[]> queue = new ArrayBlockingQueue<Integer[]>( queueCapacity); final int chunkSize = 4; final long chunkTimeout = 1000; final TimeUnit chunkTimeoutUnit = TimeUnit.MILLISECONDS; /* * The test timeout is just a smidge longer than the chunk timeout. * * Note: use Long.MAX_VALUE iff debugging. */ // final long testTimeout = Long.MAX_VALUE; final long testTimeout = chunkTimeout + 20; final boolean ordered = false; final BlockingBuffer<Integer[]> buffer = new BlockingBuffer<Integer[]>( queue, chunkSize, chunkTimeout, chunkTimeoutUnit, ordered); // buffer is empty. assertTrue(buffer.isOpen()); assertTrue(buffer.isEmpty()); assertEquals("chunkCount", 0L, buffer.getChunksAddedCount()); assertEquals("elementCount", 0L, buffer.getElementsAddedCount()); final IAsynchronousIterator<Integer[]> itr = buffer.iterator(); // nothing available from the iterator (non-blocking test). assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // add an element to the buffer - should not block. buffer.add(new Integer[]{e0}); // should be one element and one chunk accepted by the buffer. assertTrue(buffer.isOpen()); assertFalse(buffer.isEmpty()); assertEquals("chunkCount", 1L, buffer.getChunksAddedCount()); assertEquals("elementCount", 1L, buffer.getElementsAddedCount()); // something should be available now (non-blocking). assertTrue(itr.hasNext(1, TimeUnit.NANOSECONDS)); // something should be available now (blocking). assertTrue(itr.hasNext()); // add another element to the buffer - should not block. buffer.add(new Integer[]{e1}); // should be two elements and two chunks accepted into the buffer assertTrue(buffer.isOpen()); assertFalse(buffer.isEmpty()); assertEquals("chunkCount", 2L, buffer.getChunksAddedCount()); assertEquals("elementCount", 2L, buffer.getElementsAddedCount()); final ReentrantLock lock = new ReentrantLock(); final Condition cond = lock.newCondition(); final AtomicBoolean proceedFlag = new AtomicBoolean(false); // future of task writing a 3rd element on the buffer. final Future<?> producerFuture = service.submit(new Callable<Void>() { public Void call() throws Exception { lock.lockInterruptibly(); try { if (!proceedFlag.get()) { cond.await(); } /* * add another element - should block until we take an * element using the iterator. */ buffer.add(new Integer[] { e2 }); /* * itr.hasNext() will block until the buffer is closed. */ buffer.close(); } finally { lock.unlock(); } // done. return null; } }); // future of task draining the buffer. final Future<?> consumerFuture = service.submit(new Callable<Void>() { public Void call() throws Exception { try { lock.lockInterruptibly(); try { assertTrue(itr.hasNext()); // take the first chunk - two elements. if (log.isInfoEnabled()) log.info("Awaiting first chunk"); assertSameArray(new Integer[] { e0, e1 }, itr.next(50, TimeUnit.MILLISECONDS)); if (log.isInfoEnabled()) log.info("Have first chunk"); /* * Verify that we obtained the first chunk before the * buffer was closed. Otherwise next() blocked * attempting to compile a full chunk until the producer * timeout, at which point the producer closed the * buffer and next() noticed the closed buffer and * returned. */ assertTrue(buffer.isOpen()); assertFalse("buffer was closed.", itr.isExhausted()); /* * Verify that nothing is available from the iterator * (non-blocking test). */ assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // Signal the producer that it should continue. proceedFlag.set(true); cond.signal(); } finally { lock.unlock(); } // should block until we close the buffer. assertTrue(itr.hasNext()); // last chunk assertSameArray(new Integer[] { e2 }, itr.next()); // should be immediately false. assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); // should be immediately null. assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // The synchronous API should also report an exhausted // itr. assertFalse(itr.hasNext()); try { itr.next(); fail("Expecting: " + NoSuchElementException.class); } catch (NoSuchElementException ex) { if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); } return null; } catch (Throwable t) { log.error("Consumer failed or blocked: " + t, t); throw new Exception(t); } } }); // wait a little bit for the producer future. producerFuture.get(testTimeout, chunkTimeoutUnit); // wait a little bit for the consumer future. consumerFuture.get(testTimeout, chunkTimeoutUnit); } /* * This is commented out since the only difference between next() with an * infinite chunk timeout and next(timeout := Long.MAX_VALUE) is that the * latter will check to see if the timeout has expired after invoking * hasNext(timeout). With an infinite timeout (Long.MAX_VALUE) this will * never be true so the two cases are in fact equivalent. */ // /** // * Test liveness with an infinite chunk timeout. The buffer is setup such // * that it contains one or two chunks, which could be combined into a single // * chunk. next(timeout) should notice as soon as the buffer is closed (a few // * ms of latency at most) and combine whatever is remaining in the buffer // * into a chunk and return that to the consumer. // * // * @throws TimeoutException // * @throws ExecutionException // * @throws InterruptedException // */ // public void test_chunkCombinerNoticesCloseWithInfiniteTimeout() // throws InterruptedException, ExecutionException, TimeoutException { // // final Integer e0 = new Integer(0); // final Integer e1 = new Integer(1); // final Integer e2 = new Integer(2); // // final int queueCapacity = 3; // final BlockingQueue<Integer[]> queue = new ArrayBlockingQueue<Integer[]>( // queueCapacity); // final int chunkSize = 4; // final long chunkTimeout = Long.MAX_VALUE; // final TimeUnit chunkTimeoutUnit = TimeUnit.MILLISECONDS; // final boolean ordered = false; // // final BlockingBuffer<Integer[]> buffer = new BlockingBuffer<Integer[]>( // queue, chunkSize, chunkTimeout, chunkTimeoutUnit, ordered); // // // buffer is empty. // assertTrue(buffer.isOpen()); // assertTrue(buffer.isEmpty()); // assertEquals("chunkCount", 0L, buffer.getChunkCount()); // assertEquals("elementCount", 0L, buffer.getElementCount()); // // final IAsynchronousIterator<Integer[]> itr = buffer.iterator(); // // // nothing available from the iterator (non-blocking test). // assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); // assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // // // add an element to the buffer - should not block. // buffer.add(new Integer[]{e0}); // // // should be one element and one chunk accepted by the buffer. // assertTrue(buffer.isOpen()); // assertFalse(buffer.isEmpty()); // assertEquals("chunkCount", 1L, buffer.getChunkCount()); // assertEquals("elementCount", 1L, buffer.getElementCount()); // // // something should be available now (non-blocking). // assertTrue(itr.hasNext(1, TimeUnit.NANOSECONDS)); // // // something should be available now (blocking). // assertTrue(itr.hasNext()); // // // add another element to the buffer - should not block. // buffer.add(new Integer[]{e1}); // // // should be two elements and two chunks accepted into the buffer // assertTrue(buffer.isOpen()); // assertFalse(buffer.isEmpty()); // assertEquals("chunkCount", 2L, buffer.getChunkCount()); // assertEquals("elementCount", 2L, buffer.getElementCount()); // // final ReentrantLock lock = new ReentrantLock(); // final Condition cond = lock.newCondition(); // final AtomicBoolean proceedFlag = new AtomicBoolean(false); // // // future of task writing a 3rd element on the buffer. // final Future producerFuture = service.submit(new Callable<Void>() { // public Void call() throws Exception { // // lock.lockInterruptibly(); // try { // if (!proceedFlag.get()) { // cond.await(); // } // /* // * add another element - should block until we take an // * element using the iterator. // */ // buffer.add(new Integer[] { e2 }); // // /* // * itr.hasNext() will block until the buffer is closed. // */ // buffer.close(); // } finally { // lock.unlock(); // } // // done. // return null; // // } // }); // // // future of task draining the buffer. // final Future consumerFuture = service.submit(new Callable<Void>() { // public Void call() throws Exception { // // try { // lock.lockInterruptibly(); // try { // // assertTrue(itr.hasNext()); // // // take the first chunk - two elements. // if (log.isInfoEnabled()) // log.info("Awaiting first chunk"); // assertSameArray(new Integer[] { e0, e1 }, itr.next(50, // TimeUnit.MILLISECONDS)); // if (log.isInfoEnabled()) // log.info("Have first chunk"); // // /* // * Verify that we obtained the first chunk before the // * buffer was closed. Otherwise next() blocked // * attempting to compile a full chunk until the producer // * timeout, at which point the producer closed the // * buffer and next() noticed the closed buffer and // * returned. // */ // assertTrue(buffer.isOpen()); // assertFalse("buffer was closed.", itr.isExhausted()); // // /* // * Verify that nothing is available from the iterator // * (non-blocking test). // */ // assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); // assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // // // Signal the producer that it should continue. // proceedFlag.set(true); // cond.signal(); // // } finally { // // lock.unlock(); // // } // // // last chunk // assertSameArray(new Integer[] { e2 }, itr.next()); // // // should be immediately false. // assertFalse(itr.hasNext(1, TimeUnit.NANOSECONDS)); // // should be immediately null. // assertNull(itr.next(1, TimeUnit.NANOSECONDS)); // // // The synchronous API should also report an exhausted // // itr. // assertFalse(itr.hasNext()); // try { // itr.next(); // fail("Expecting: " + NoSuchElementException.class); // } catch (NoSuchElementException ex) { // if (log.isInfoEnabled()) // log.info("Ignoring expected exception: " + ex); // } // // return null; // // } catch (Throwable t) { // log.error("Consumer failed or blocked: " + t, t); // throw new Exception(t); // } // // } // }); // // final long testTimeout = 1000; // // // wait a little bit for the producer future. // producerFuture.get(testTimeout, TimeUnit.MILLISECONDS); // // // wait a little bit for the consumer future. // consumerFuture.get(testTimeout, TimeUnit.MILLISECONDS); // // } }