![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.service.ndx.pipeline.AbstractPendingSetMasterTask Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.bigdata.BigdataStatics;
import com.bigdata.btree.BigdataMap;
import com.bigdata.relation.accesspath.BlockingBuffer;
import com.bigdata.service.AbstractDistributedFederation;
import com.bigdata.service.master.INotifyOutcome;
/**
* Extends the master task to track outstanding asynchronous operations on work
* items.
*
* The clients notify the {@link AbstractPendingSetSubtask} as each operation
* completes. The subtask notifies the master, which then clears the entry from
* its {@link #pendingMap} and also clears the entry from any other subtask that
* had been tasked with the same work item (this permits subtasks to terminate
* as soon as their work is complete regardless of which subtask actually
* performed the work). The master will not terminate until all outstanding
* asynchronous operations (the pending set) are complete.
*
* @author Bryan Thompson
* @version $Id$
*/
abstract public class AbstractPendingSetMasterTask/
H extends AbstractPendingSetMasterStats, //
E, //
S extends AbstractPendingSetSubtask, //
L> //
extends AbstractMasterTask //
implements INotifyOutcome
{
/**
* Log may be used to see just success/error reporting for the master
* without the log information from the base class.
*/
static protected transient final Logger log = Logger
.getLogger(AbstractPendingSetMasterTask.class);
/**
* Lock used to serialize operations on the {@link #pendingMap}.
*/
private final ReentrantLock lock = new ReentrantLock();
private final AbstractDistributedFederation> fed;
public AbstractDistributedFederation> getFederation() {
return fed;
}
/**
* A proxy for this class which is used by the client task to send
* asynchronous notifications.
*/
protected final INotifyOutcome masterProxy;
/**
* Return the pending map. The pending map reflects the resources which are
* in process. Resources are added to this collection when they are posted
* to a client for processing and are removed when the client asynchronously
* reports success or failure for the resource.
*/
abstract protected Map> getPendingMap();
/**
* @param stats
* @param buffer
* @param sinkIdleTimeoutNanos
* @param sinkPollTimeoutNanos
*/
public AbstractPendingSetMasterTask(final AbstractDistributedFederation> fed,
final H stats, final BlockingBuffer buffer,
final long sinkIdleTimeoutNanos, final long sinkPollTimeoutNanos) {
super(stats, buffer, sinkIdleTimeoutNanos, sinkPollTimeoutNanos);
if (fed == null)
throw new IllegalArgumentException();
this.fed = fed;
this.masterProxy = (INotifyOutcome) fed
.getProxy(this, true/* enableDGC */);
}
final protected boolean nothingPending() {
lock.lock();
try {
return getPendingMap().isEmpty();
} finally {
lock.unlock();
}
}
final public int getPendingSetSize() {
lock.lock();
try {
return getPendingMap().size();
} finally {
lock.unlock();
}
}
/**
* Add a work item to the pending set.
*
* Note: This method is written such that a {@link BigdataMap} could be used
* as the implementation object. (The tuple is always updated by an insert
* when its value's state is changed.)
*
* @param e
* The work item.
* @param locator
* The locator of the subtask/client that will process that work
* item.
*
* @return true
iff the pending set did not contain an entry
* for that work item. Since entries are cleared from the map when
* work is successfully complete or if all pending operations fail
* for a work item, a true
return does not
* conclusively indicate a new work item.
*/
protected boolean addPending(final E e, final AbstractPendingSetSubtask sink, final L locator) {
if (e == null)
throw new IllegalArgumentException();
if (sink == null)
throw new IllegalArgumentException();
if (locator == null)
throw new IllegalArgumentException();
final boolean modifiedMap;
lock.lock();
try {
Collection locators = getPendingMap().remove(e);
if (locators == null) {
locators = new LinkedHashSet();
locators.add(locator);
getPendingMap().put(e, locators);
sink.getPendingSet().add(e);
// added to the map.
modifiedMap = true;
} else {
// already in the map.
locators.add(locator);
getPendingMap().put(e, locators);
sink.getPendingSet().add(e);
modifiedMap = false;
}
if (BigdataStatics.debug || log.isDebugEnabled()) {
String msg = "Added pending: size=" + getPendingSetSize()
+ ", resource=" + e + ", locator=" + locator
+ ", sinkSize=" + sink.getPendingSetSize();
if (BigdataStatics.debug)
System.err.println(msg);
if (log.isDebugEnabled())
log.debug(msg);
}
return modifiedMap;
} finally {
lock.unlock();
}
}
/**
* Remove a work item from the pending set.
*
* Note: This method is written such that a {@link BigdataMap} could be used
* as the implementation object. (The tuple is always updated by an insert
* when its value's state is changed.)
*
* @param e
* The work item.
* @param locator
* The subtask / client locator.
* @param cause
* null
unless an error is being reported.
*
* @return true
iff the work item was cleared from the
* pending set (present on entry but cleared on exit).
*
* @todo unit tests for the add/remove pending methods since they are a bit
* complex internally.
*/
protected boolean removePending(final E e, final L locator,
final Throwable cause) {
if (e == null)
throw new IllegalArgumentException();
if (locator == null)
throw new IllegalArgumentException();
// if (cause == null)
// throw new IllegalArgumentException();
boolean notify = false;
final int sizeUnderLock;
lock.lock();
try {
if (cause == null) {
/*
* Successful completion.
*/
final Collection locators = getPendingMap().remove(e);
if (locators == null) {
/*
* Presume already successful since not in the map. Return
* false since map was not modified.
*/
return false;
}
// for each locator tasked with that item.
for (L t : locators) {
// clear item from the sink's pending set.
final S sink;
try {
sink = super.getSink(t, false/* reopen */);
} catch (InterruptedException ex) {
halt(ex);
throw new RuntimeException(ex);
}
sink.removePending(e);
}
// notify (success).
notify = locators != null;
// no more requests remain for that work item.
return true;
}
/*
* Error reported.
*/
final Collection locators = getPendingMap().get(e);
if (locators == null) {
/*
* Presume already successful since not in the map. Return false
* since map was not modified.
*/
return false;
}
// remove the entry for the locator which reported the error.
locators.remove(locator);
{
// clear item from the sink's pending set.
final S sink;
try {
sink = super.getSink(locator, false/* reopen */);
} catch (InterruptedException ex) {
halt(ex);
throw new RuntimeException(ex);
}
sink.removePending(e);
}
if (locators.isEmpty()) {
// no outstanding requests remain, so will notify error.
getPendingMap().remove(e);
// will notify.
notify = true;
// no more requests for that work item.
return true;
} else {
// otherwise outstanding requests remain, so update map.
getPendingMap().put(e, locators);
// requests remain for that work item.
return false;
}
} finally {
try {
sizeUnderLock = getPendingMap().size();
} finally {
lock.unlock();
}
// notify once we have released the lock.
if (notify) {
if (cause == null) {
didSucceed(e);
} else {
didFail(e, cause);
}
}
if (BigdataStatics.debug || log.isDebugEnabled()) {
final String msg = "resource=" + e + ", notify=" + notify
+ ", pendingSetSize=" + sizeUnderLock + ", locator="
+ locator + (cause == null ? "" : "cause=" + cause);
if (BigdataStatics.debug)
System.err.println(msg);
if (log.isDebugEnabled())
log.debug(msg);
}
}
}
/**
* Return a new pending map instance. The size of this collection places a
* machine limit on the #of resources which may be processed concurrently. A
* {@link BigdataMap} may be used if sufficient RAM is not available.
*/
abstract protected Map> newPendingMap();
/**
* The resource is removed from the {@link #pendingMap} and the pending set
* for each sink for which there is an outstanding request for that
* resource. {@link #didSucceed(Object)} will be invoked the first time
* a request succeeds for that resource.
*/
final public void success(final E e, final L locator) {
removePending(e, locator, null/* cause */);
}
/**
* The resource is removed from the pending set for the sink associated with
* that locator. If there are no more outstanding requests for that resource
* in the {@link #pendingMap} then the resource is removed from the pending
* map as well. {@link #didFail(Object, Throwable)} will be invoked if no
* requests remain for that resource in the {@link #pendingMap}.
*/
final public void error(final E resource, final L locator, final Throwable cause) {
removePending(resource, locator, null/* cause */);
// if (removePending(resource, locator, null/* cause */)) {
//
// // all pending operations have failed for this resource.
// log.error(resource, cause);
//
// }
}
/**
* Hook provides notification the first time work for the resource has been
* successfully completed for any set of concurrent outstanding work
* requests and may be extended if necessary. The final outcome
* for each resource is not retained. Therefore if the same resource is
* resubmitted after its successful completion or its failure, then this
* method may be invoked again for that resource. The default implementation
* logs the event @ INFO.
*/
protected void didSucceed(final E e) {
if (log.isInfoEnabled()) {
// an asynchronous operation has succeeded for this resource.
log.info(e.toString());
}
}
/**
* Hook provides notification if all outstanding work requests for the
* resource have failed. There may be more than one request to perform the
* same work. This method is not invoked until all such requests have failed
* and is not invoked if any of those requests succeed. Note that work
* requests MUST be idempotent. The default implementation logs the event @ ERROR.
*
* @param resource
* The resource.
* @param cause
* The exception.
*/
protected void didFail(final E resource, final Throwable cause) {
// all pending operations have failed for this resource.
log.error(resource, cause);
}
}