/*
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 Jul 13, 2009
*/
package com.bigdata.service.ndx.pipeline;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import com.bigdata.relation.accesspath.BlockingBuffer;
import com.bigdata.service.IRemoteExecutor;
import com.bigdata.service.master.IAsynchronousClientTask;
/**
* Extended to assign chunks of work items to a remote
* {@link IAsynchronousClientTask}, to track the set of outstanding
* asynchronous operations for a specific client task (the "pending set"), and
* to close the client task when the sink not assign any more work to that
* client.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public abstract class AbstractPendingSetSubtask<//
HS extends AbstractSubtaskStats, //
M extends AbstractPendingSetMasterTask<? extends AbstractPendingSetMasterStats<L, HS>, E, ? extends AbstractPendingSetSubtask, L>, //
E, //
L> //
extends AbstractSubtask<HS, M, E, L> {
final protected IAsynchronousClientTask<?, E> clientTask;
/**
* Lock used ONLY for the {@link #pendingSet}.
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* Condition signaled when the {@link #pendingSet} is empty.
*/
private final Condition pendingSetEmpty = lock.newCondition();
/**
* {@inheritDoc}
*
* @param clientTask
* The proxy for the remote client task.
*/
public AbstractPendingSetSubtask(final M master, final L locator,
final IAsynchronousClientTask<?, E> clientTask,
final BlockingBuffer<E[]> buffer) {
super(master, locator, buffer);
if (clientTask == null)
throw new IllegalArgumentException();
this.clientTask = clientTask;
}
/**
* Return the pending set.
*/
abstract protected Set<E> getPendingSet();
public int getPendingSetSize() {
lock.lock();
try {
return getPendingSet().size();
} finally {
lock.unlock();
}
}
@Override
protected final void cancelRemoteTask(final boolean mayInterruptIfRunning)
throws InterruptedException {
try {
// cancel the remote task.
clientTask.getFuture().cancel(mayInterruptIfRunning);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
protected final void awaitPending() throws InterruptedException {
/*
* Instruct the client task that we will not assign it any new work.
*/
try {
clientTask.close();
} catch (RemoteException ex) {
throw new RuntimeException(toString(), ex);
}
/*
* Wait for the pending set to empty.
*/
lock.lockInterruptibly();
try {
if (!getPendingSet().isEmpty()) {
pendingSetEmpty.await();
}
} finally {
lock.unlock();
}
}
final protected boolean addPending(final E e) {
lock.lock();
try {
return getPendingSet().add(e);
} finally {
lock.unlock();
}
}
final protected boolean removePending(final E e) {
lock.lock();
try {
final boolean ret = getPendingSet().remove(e);
if (getPendingSet().isEmpty()) {
pendingSetEmpty.signal();
}
return ret;
} finally {
lock.unlock();
}
}
/**
* Submits the chunk of resources for processing by the remote client task.
* Clients should accept resources for asynchronous processing, notifying
* the sink as resources succeed or fail.
*
* @param chunk
* A chunk of resources to be processed.
*
* @return <code>true</code> iff the client will not accept additional
* chunks.
*
* @throws IOException
* RMI error.
* @throws ExecutionException
* @throws InterruptedException
*/
protected boolean handleChunk(final E[] chunk) throws ExecutionException,
InterruptedException, IOException {
assert chunk != null;
assert chunk.length > 0;
final int chunkSize = chunk.length;
/*
* Instantiate the procedure using the data from the chunk and submit it
* to be executed on the DataService using an RMI call.
*/
final long beginNanos = System.nanoTime();
try {
// /*
// * @todo isolate this as a retry policy, but note that we need to
// be
// * able to indicate when the error is fatal, when the error was
// * handled by a redirect and hence the sink should close, and when
// * the error was handled by a successful retry.
// */
// boolean done = false;
// final int maxtries = 3;
// final long retryDelayNanos = TimeUnit.MILLISECONDS.toNanos(1000);
// for (int ntries = 0; ntries < maxtries; ntries++) {
//
// try {
/*
* Submit (blocks until chunk is queued by the client task).
*
* @todo hold lock while adding the elements in the chunk to the
* pending set.
*/
for(E e : chunk) {
master.addPending(e, this, locator);
}
// @todo retry any errors here? E.g., RMI transient failures?
try {
// Task the client with a chunk of resources.
clientTask.accept(chunk);
} catch(Throwable t) {
// Halt the master.
master.halt(t);
throw new RuntimeException(t);
}
// done = true;
// break;
//
// } catch (ExecutionException ex) {
//
// if (ntries + 1 < maxtries) {
//
// log.error("Will retry (" + ntries + " of " + maxtries
// + "): " + this, ex);
//
// continue;
//
// }
//
// log.fatal(this, ex);
//
// throw ex;
//
// }
// }
// if (!done) {
// // should not reach this point.
// throw new AssertionError();
// }
if (log.isDebugEnabled())
log.debug(stats);
// keep reading.
return false;
} finally {
final long elapsedNanos = System.nanoTime() - beginNanos;
// update the local statistics.
synchronized (stats) {
stats.chunksOut.incrementAndGet();
stats.elementsOut.addAndGet(chunkSize);
stats.elapsedChunkWritingNanos += elapsedNanos;
}
// update the master's statistics.
synchronized (master.stats) {
master.stats.chunksOut.incrementAndGet();
master.stats.elementsOut.addAndGet(chunkSize);
// note: duplicate elimination is not being performed.
// master.stats.duplicateCount.addAndGet(duplicateCount);
master.stats.elapsedSinkChunkWritingNanos += elapsedNanos;
}
}
}
/**
* @todo This should handle the redirect of the pendingSet if the remote
* client task dies. To be robust, we need to notice client death even
* if it occurs when we are not invoking client#accept(chunk). Doing
* that will require some changes to the logic in this class, perhaps
* only in {@link #awaitPending()} which might have to poll the future
* of the client to make sure that it is still alive (or check w/ zk
* but zk can disconnect clients overly eagerly).
*
* @todo This class ALSO needs to handle the resubmit of the resources
* associated with the current submit. That will occur only when the
* remote client task dies while invoking client#accept(chunk). The
* {@link AbstractPendingSetMasterTask} needs to start a new client
* task for the given {@link ClientLocator}, ideally on a different
* {@link IRemoteExecutor} service. If there is no such available
* service then it could multiplex multiple client#s onto the same
* client, essentially doubling the load for some client. Or we could
* hash partition based on the #of remaining clients, which would
* distribute the load evenly over the remaining clients.
*/
@Override
protected void notifyClientOfRedirect(L locator, Throwable cause) {
throw new UnsupportedOperationException();
}
}