/* 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 Aug 27, 2008 */ package com.bigdata.service.proxy; import java.io.IOException; import java.io.Serializable; import java.rmi.Remote; import java.util.Arrays; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import com.bigdata.relation.accesspath.BlockingBuffer; import com.bigdata.relation.accesspath.BufferClosedException; import com.bigdata.relation.accesspath.IAsynchronousIterator; import com.bigdata.relation.accesspath.IBlockingBuffer; import com.bigdata.service.DataService; import com.bigdata.service.IBigdataFederation; import com.bigdata.util.DaemonThreadFactory; /** * Wraps an {@link RemoteAsynchronousIterator} so that it looks like an * {@link IAsynchronousIterator} again. * <p> * This implementation uses the caller's {@link ExecutorService} to run a task * that migrates data from the remote {@link IAsynchronousIterator} into a local * {@link BlockingBuffer}. The client implementation uses purely local * operations to test the contents of the local {@link BlockingBuffer}. This * means that operations like {@link #hasNext(long, TimeUnit)} may be used with * short timeouts without possibility of blocking IO. * <p> * Note: This class is NOT thread-safe. There should be only a single process * draining the iterator. * <p> * Note: In order to conserve resources, the caller is advised to use * {@link #start(ExecutorService)} before making any other requests. This will * allow the caller to specify an {@link ExecutorService} that will be tasked * with buffering elements from the {@link RemoteAsynchronousIterator}. When * {@link #start(ExecutorService)} is not invoked explicitly, a new * {@link Thread} will be allocated. Typically, an {@link ExecutorService} can * be much more efficient than creating a new {@link Thread}. * * @todo Note that we only use three methods to implement * {@link ClientAsynchronousIterator} - hasNext(timeout,unit), * isExhausted(), and next(). The rest of the * {@link RemoteAsynchronousIterator} API does not need to be implemented. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class ClientAsynchronousIterator<E> implements IAsynchronousIterator<E>, Serializable { /** * */ private static final long serialVersionUID = -6809878299983373766L; protected transient static final Logger log = Logger .getLogger(ClientAsynchronousIterator.class); protected transient static final boolean INFO = log.isInfoEnabled(); protected transient static final boolean DEBUG = log.isDebugEnabled(); /** * The source - the reference is cleared when the iterator is closed. * <p> * Note: This is a {@link Remote} interface. All methods on this interface * are RMI calls and both declare and can throw {@link IOException}. */ private final RemoteAsynchronousIterator<E> remoteIterator; /** * The capacity that will be used when we allocate the {@link #localBuffer}. */ private final int capacity; /** * Elements are transferred asynchronously from the {@link #remoteIterator} * into the {@link #localBuffer} by a {@link ReaderTask}. This will be * <code>null</code> until the iterator is {@link #start(ExecutorService)}ed. */ private transient BlockingBuffer<E> localBuffer; /** * The {@link Future} for the {@link ReaderTask} that fills the * {@link #localBuffer} from the {@link #remoteIterator}. This will be * <code>null</code> until the iterator is {@link #start(ExecutorService)}ed. */ private transient Future<Void> future; /** * Iterator draining the {@link #localBuffer}. This will be * <code>null</code> until the iterator is {@link #start(ExecutorService)}ed. */ private transient IAsynchronousIterator<E> localIterator; /** * * @param remoteIterator * A proxy for the remote iterator from which the elements are * being read. * @param capacity * The capacity of the internal {@link IBlockingBuffer}. */ public ClientAsynchronousIterator( final RemoteAsynchronousIterator<E> remoteIterator, final int capacity) { if (remoteIterator == null) throw new IllegalArgumentException(); if (capacity <= 0) throw new IllegalArgumentException(); // save reference. this.remoteIterator = remoteIterator; this.capacity = capacity; /* * Note: The [localBuffer] is not defined until someone calls start()! */ } /** * Start the {@link ReaderTask} that will populate the local buffer with * elements from the remote iterator. * <p> * When {@link #start(ExecutorService)} is not invoked explicitly, a new * {@link Thread} will be allocated and the {@link ReaderTask} will be run * on that {@link Thread}. Typically, an {@link ExecutorService} can be * much more efficient than creating a new {@link Thread}. Therefore the * caller is encourged to break encapsulation and specify the * {@link ExecutorService} on which the {@link ReaderTask} will run * directly. * * @throws IllegalStateException * if the {@link ReaderTask} is already running. */ public void start(final ExecutorService executorService) { if (executorService == null) throw new IllegalArgumentException(); if (future != null) throw new IllegalStateException(); // allocate local buffer. this.localBuffer = new BlockingBuffer<E>(capacity); /** * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/707"> * BlockingBuffer.close() does not unblock threads </a> */ // Wrap computation as FutureTask. final FutureTask<Void> ft = new FutureTask<Void>(new ReaderTask()); /* * Set future on the local buffer so that we can interrupt it when the * client side of the iterator is closed. */ this.localBuffer.setFuture(future = ft); // save reference to iterator draining the [localBuffer]. this.localIterator = localBuffer.iterator(); // start reader. executorService.submit(ft); } /** * Start the {@link ReaderTask} iff it is not already running. */ final protected void start() { if (future != null) return; /* * Note: This is resource intensive if you have a lot of these * asynchronous iterators. That is why the caller should use * start(ExecutorService) and save themselves some pain. */ this.executorService = Executors .newSingleThreadExecutor(new DaemonThreadFactory (getClass().getName()+".executorService")); start(executorService); log.warn("Running reader on private executor service"); } /** * Assigned IFF we need to create our own {@link ExecutorService} on which * to run the {@link ReaderTask}. * <p> * Note: The whole reason for {@link #start(ExecutorService)} is that the * {@link ExecutorService} is NOT serializable. Typically this task will be * run on the {@link ExecutorService} associated with the local * {@link IBigdataFederation} object for a {@link DataService}. */ private transient ExecutorService executorService; /** * Timeout for the {@link ReaderTask} when it invokes * {@link RemoteAsynchronousIterator#hasNext(long, TimeUnit)}. * <p> * Note: A timeout is used instead of * {@link RemoteAsynchronousIterator#hasNext()} in order to avoid blocking * during an RMI call on a remote thread. */ static protected transient final long timeout = 1L; /** * The units for {@link #timeout}. */ static protected transient final TimeUnit unit = TimeUnit.MILLISECONDS; /** * Enables low-level trace of the {@link ReaderTask}. */ static protected transient final boolean trace = false; /** * Task polls the remote {@link IAsynchronousIterator} copying elements into * the #localBuffer. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ private class ReaderTask implements Callable<Void> { @Override public Void call() throws Exception { // final Thread t = Thread.currentThread(); boolean interrupted = false; try { /* * Note: remoteIterator#hasNext() is an RMI call. Therefore it * is NOT interruptable. * * In order to make this loop terminate in a timely fashion if * the local Thread is interrupted, we poll the remoteIterator * with a timeout and use remoteIterator#isExhausted() to * determine whether a [false] return really represents an * exhausted source. * * That helps as long as the RMI itself does not hang since * hasNext( timeout, unit) will terminate quickly. */ while (true) { if (trace) System.err.println("Polling remote iterator: "); // poll remote iterator with timeout. if (!remoteIterator.hasNext(timeout, unit)) { if (trace) System.err.print('~'); if (Thread.interrupted()) { // thread interrupted, so we are done. interrupted = true; break; // break out of while(true) } if (remoteIterator.isExhausted()) { if (trace) System.err.println("Remote iterator is exhausted."); // remote iterator is exhausted, so we are done. break; } if (!localBuffer.isOpen()) { // local buffer is closed, so we are done. break; } /* * continue polling until source proven to be exhausted, * this thread is interrupted, or the source is known to * have an element available for transfer to the local * buffer. */ continue; } /* * read an element from the remote iterator. while this is a * blocking operation we have already proven that there is * an element waiting for us so the operation should have * the minimum possible latency, which depends of course on * how much data there is in the serialized element that we * are requesting. */ // don't call blocking method next() if we were interrupted. if (Thread.interrupted()) { // thread interrupted, so we are done. interrupted = true; break; // break out of while(true) } if(trace) System.err.print(">>"); // final E e = remoteIterator.next(); final E e = remoteIterator.nextElement().get(); if (trace) System.err .println("read " + (e.getClass().getComponentType() != null ? Arrays .toString((Object[]) e) : e.toString())); try { /* * Add the element to the local buffer (blocking, but it * monitors the caller's thread and will notice if it is * interrupted). */ localBuffer.add(e); } catch (BufferClosedException ex) { /* * The local buffer has been closed so we ignore the * exception and break out of the loop. */ if (INFO) log.info(ex.getLocalizedMessage()); // done. break; } } if (INFO) log.info("Reader is done: interrupted" + interrupted); return null; } finally { /* * Close the local buffer. It will no longer accept new elements * and none will be placed into the buffer by this reader. Once * the buffer is drained via the localIterator the localIterator * will report that it is exhausted. */ localBuffer.close(); /* * Make sure that the remote iterator is closed. * * Note: There are several reasons why the reader may be done. * It may be that the remote iterator has been exhausted. * However, it may also be that this task was interrupted or * that the local buffer was closed. */ try { if(trace) System.err.println("Closing remote iterator"); remoteIterator.close(); } catch (Throwable ex) { /* * Note: try-catch is here in case there is an IOException. */ log.warn(ex.getLocalizedMessage()); } if (executorService != null) { /* * We created our own service on which to the ReaderTask. Therefore * we also need to make sure that it gets shutdown! */ executorService.shutdown(); } } } } @Override public void close() { if (future == null) { /* * The ReaderTask was never started. */ try { /* * Directly close the remote iterator (RMI). */ remoteIterator.close(); } catch (IOException ex) { log.warn(ex); } // done. return; } /* * Close the local buffer. This will cause the ReaderTask to abort as * soon as it notices that the buffer has been closed since it will no * longer be able to add new elements into the local buffer. */ localBuffer.close(); if (!future.isDone()) { /* * Cancel the ReaderTask if it is not done. */ future.cancel(true/* mayInterruptIfRunning */); } /* * Close the local iterator - the caller will no longer read from it. * * Note: The reader task will close the remote iterator. */ localIterator.close(); if (executorService != null) { /* * We created our own service on which to the ReaderTask. Therefore * we also need to make sure that it gets shutdown! */ executorService.shutdown(); } } public boolean hasNext(long timeout, TimeUnit unit) throws InterruptedException { start(); return localIterator.hasNext(timeout, unit); } public boolean isExhausted() { start(); return localIterator.isExhausted(); } public E next(long timeout, TimeUnit unit) throws InterruptedException { start(); return localIterator.next(timeout, unit); } public boolean hasNext() { start(); return localIterator.hasNext(); } public E next() { start(); return localIterator.next(); } /** * Operation is not supported. * * @throws UnsupportedOperationException * always. */ public void remove() { throw new UnsupportedOperationException(); } }