/** * 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); }