package com.bigdata.service.ndx.pipeline;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import com.bigdata.btree.keys.KVO;
/**
* Extends {@link KVO} to allow duplicates to be gathered together in a
* doubly-linked list. This is used to facilitate duplicate removal where the
* goal is to eliminate the index write for the duplicate instance(s) but where
* we still want to take some after action for each distinct instance.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*
* @param <O>
* The generic type of the unserialized value object.
*
* @see IDuplicateRemover
*/
public class KVOList<O> extends KVO<O> {
/**
* A doubly-linked chain.
* <p>
* Note: it is less expensive to allocate this with the {@link KVOList} than
* to allocate an {@link AtomicReference} and then allocate this list lazily
* but with guaranteed consistency.
*/
private final ConcurrentLinkedQueue<KVO<O>> duplicateList = new ConcurrentLinkedQueue<KVO<O>>();
private volatile boolean done = false;
/**
*
* @param key
* The unsigned byte[] key (required).
* @param val
* The byte[] value (optional).
* @param obj
* The paired application object (optional).
* @param latch
* The object that maintains the counter.
*/
public KVOList(final byte[] key, final byte[] val, final O obj) {
super(key, val, obj);
}
/**
* Add a reference to the duplicates list. This is MUTEX with
* {@link #done()}.
*
* @param o
* A duplicate of this object.
*/
// Note: MUTUX with done().
synchronized//
public void add(final KVO<O> o) {
if (o == null)
throw new IllegalArgumentException();
if(done)
throw new IllegalStateException();
duplicateList.add(o);
if (o instanceof KVOList<?>) {
/*
* During a redirect, the last chunk written by the sink may be
* combined with another chunk drained from the master. In these
* cases we can see KVOLists identified as duplicates for KVOLists
* which already have a list of duplicates. We handle this by
* merging those lists.
*
* Note: This is not a atomic operation if concurrent inserts are
* allowed on the other KVOList. However, locking would make us open
* to deadlock. The atomic guarantee arises from the fact that this
* code is invoked from the sink, and the sink is single threaded,
* so we don't really need all this synchronization in KVOList
* anyway!
*
* FIXME Review that MUXTEX guarantee!!!!
*
* The atomic guarantee breaks down into three cases.
*
* (1) The other list (the own passed in by the caller) is immutable
* since this occurs when handling a stale locator exception, and
* that behavior is single threaded. Hence the source list will not
* have concurrent add()s.
*
* (3) add() and done() must be mutually exclusive to ensure that
* the behavior applied by done() is applied to all duplicates (this
* is a race condition). The typical behavior is to decrement a
* Latch.
*
* (2) add() must not be allowed for this list once done() has been
* invoked. The guarantee here arises from how duplicates are
* detected. Duplicate detection occurs when taking a chunk from the
* sink's queue. All items which can be duplicates wind up at the
* same sink. The logic which takes a chunk from the sink's queue
* and eliminates any duplicates is single threaded.
*
* The second problem is making sure that no new
*
* @todo Review this guarantee if we move to an NIO model for the
* data transfers for the sinks. It will probably still be Ok since
* the single threaded concerns here are the transfer of chunks from
* the master and the handling of stale locator exceptions. In both
* of those cases the other list (the one being added to this list)
* will be immutable.
*/
final KVOList<O> t = (KVOList<O>) o;
duplicateList.addAll(t.duplicateList);
t.duplicateList.clear();
}
}
/**
* The #of duplicates on the internal list. This will report ZERO (0) if
* there are no duplicates (the internal list is empty).
*/
public int getDuplicateCount() {
return duplicateList.size();
}
/**
* Return <code>true</code> iff no duplicates have been assigned.
*/
public boolean isDuplicateListEmpty() {
return duplicateList.isEmpty();
}
/**
* Extended to map the operation over the duplicate list.
*/
@Override
// Note: MUTEX with add(o)
synchronized//
public void done() {
if(done)
throw new IllegalStateException();
done = true;
super.done();
for (KVO<O> o : duplicateList) {
o.done();
}
}
/**
* An operation which can be mapped over the duplicate list.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan
* Thompson</a>
* @version $Id$
* @param <O>
*/
static public interface Op<O> {
public void apply(KVO<O> o);
}
/**
* Maps the operation across the duplicate list (the operation is NOT
* applied to the original).
*
* @param op
* The operation.
*/
public void map(final Op<O> op) {
if (op == null)
throw new IllegalArgumentException();
// op.apply(this);
for (KVO<O> o : duplicateList) {
op.apply( o );
}
}
}