package edu.washington.escience.myria.parallel.ipc;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.group.ChannelGroup;
/**
* A set of channels. Ordered by a comparator.
* */
public final class ChannelPrioritySet {
/**
* Snapshot iterator that works off copy of underlying q array.
*/
final class Itr implements Iterator<Channel> {
/** Array of all elements. */
private final Object[] array;
/** Index of next element to return. */
private int cursor;
/**
* @param array the data array.
* */
Itr(final Object[] array) {
this.array = array;
}
@Override
public boolean hasNext() {
return cursor < array.length;
}
@Override
public Channel next() {
if (cursor >= array.length) {
throw new NoSuchElementException();
}
return (Channel) array[cursor++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* guard the modification of this data structure.
* */
private final Lock updateLock = new ReentrantLock();
/**
* Expose update lock for deadlock removing.
*
* @return update lock
* */
Lock getUpdateLock() {
return updateLock;
}
/**
* size restriction, lower bound.
* */
private final int lowerBound;
/**
* size restriction, upper bound.
* */
private final int upperBound;
/**
* using PriorityQueue for ordering.
* */
private final PriorityQueue<Channel> orderedChannels;
/**
* using HashSet to keep elements unique.
* */
private final HashSet<Channel> set;
/**
* Creates a {@code PriorityBlockingQueue} with the specified initial capacity that orders its elements according to
* the specified comparator.
*
* @param initialCapacity the initial capacity for this priority queue
* @param comparator the comparator that will be used to order this priority queue. If {@code null}, the
* {@linkplain Comparable natural ordering} of the elements will be used.
* @param upperBound channel pool upper bound
* @param lowerBound channel pool lower bound
* @throws IllegalArgumentException if {@code initialCapacity} is less than 1
*/
public ChannelPrioritySet(
final int initialCapacity,
final int lowerBound,
final int upperBound,
final Comparator<? super Channel> comparator) {
orderedChannels = new PriorityQueue<Channel>(initialCapacity, comparator);
set = new HashSet<Channel>(initialCapacity);
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
/**
* Inserts.
*
* @param e the channel to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws ClassCastException if the specified element cannot be compared with elements currently in the priority
* queue according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean add(final Channel e) {
updateLock.lock();
try {
if (set.add(e)) {
return orderedChannels.add(e);
} else {
return false;
}
} finally {
updateLock.unlock();
}
}
/**
* @return a read-only collection of all channels within this set.
* */
public Collection<Channel> allChannels() {
return Collections.unmodifiableCollection(orderedChannels);
}
/**
* Returns an iterator over the elements in this queue. The iterator does not return the elements in any particular
* order.
*
* <p>
* The returned iterator is a "weakly consistent" iterator that will never throw
* {@link java.util.ConcurrentModificationException ConcurrentModificationException}, and guarantees to traverse
* elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any
* modifications subsequent to construction.
*
* @return an iterator over the elements in this queue
*/
public Iterator<Channel> iterator() {
return new Itr(toArray());
}
/**
* peek and reserve the channel, so that the channel will never be disconnected.
*
* @return the top channel .
* */
public Channel peekAndReserve() {
updateLock.lock();
try {
final Channel cc = orderedChannels.peek();
if (cc != null) {
final ChannelContext bc = ChannelContext.getChannelContext(cc);
(bc.getRegisteredChannelContext()).incReference();
}
return cc;
} finally {
updateLock.unlock();
}
}
/**
* release a channel.
*
* @param ch channel.
* @param recyclableConnections collection of recyclable connections
* @param trash the trash bin.
* */
public void release(
final Channel ch,
final ChannelGroup trash,
final ConcurrentHashMap<Channel, Channel> recyclableConnections) {
final ChannelContext cc = ChannelContext.getChannelContext(ch);
final ChannelContext.RegisteredChannelContext ecc = cc.getRegisteredChannelContext();
updateLock.lock();
try {
final int size = size();
final int currentReferenced = ecc.decReference();
if (currentReferenced <= 0) {
if (size > upperBound) {
// release
cc.reachUpperbound(trash, this);
} else if (size > lowerBound) {
// wait for timeout then release
cc.considerRecycle(recyclableConnections);
}
}
} finally {
updateLock.unlock();
}
}
/**
* Removes a single instance of the specified element from this queue, if it is present. More formally, removes an
* element {@code e} such that {@code o.equals(e)}, if this queue contains one or more such elements. Returns
* {@code true} if and only if this queue contained the specified element (or equivalently, if this queue changed as a
* result of the call).
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(final Channel o) {
updateLock.lock();
try {
if (set.remove(o)) {
return orderedChannels.remove(o);
} else {
return false;
}
} finally {
updateLock.unlock();
}
}
/**
* @return size of this channel set.
* */
public int size() {
updateLock.lock();
try {
return set.size();
} finally {
updateLock.unlock();
}
}
/**
* @return an array representation of all the channels in this set.
* */
public Object[] toArray() {
updateLock.lock();
try {
return orderedChannels.toArray();
} finally {
updateLock.unlock();
}
}
/**
* Returns an array containing all of the elements in this queue; the runtime type of the returned array is that of
* the specified array. The returned array elements are in no particular order. If the queue fits in the specified
* array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and
* the size of this queue.
*
* <p>
* If this queue fits in the specified array with room to spare (i.e., the array has more elements than this queue),
* the element in the array immediately following the end of the queue is set to {@code null}.
*
* <p>
* Like the {@link #toArray()} method, this method acts as bridge between array-based and collection-based APIs.
* Further, this method allows precise control over the runtime type of the output array, and may, under certain
* circumstances, be used to save allocation costs.
*
* <p>
* Suppose {@code x} is a queue known to contain only strings. The following code can be used to dump the queue into a
* newly allocated array of {@code String}:
*
* <pre>
* String[] y = x.toArray(new String[0]);</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to {@code toArray()}.
*
* @param a the array into which the elements of the queue are to be stored, if it is big enough; otherwise, a new
* array of the same runtime type is allocated for this purpose
* @return an array containing all of the elements in this queue
* @throws ArrayStoreException if the runtime type of the specified array is not a supertype of the runtime type of
* every element in this queue
* @throws NullPointerException if the specified array is null
* @param <T> the type of the array.
*/
public <T> T[] toArray(final T[] a) {
updateLock.lock();
try {
return orderedChannels.toArray(a);
} finally {
updateLock.unlock();
}
}
@Override
public String toString() {
updateLock.lock();
try {
return orderedChannels.toString();
} finally {
updateLock.unlock();
}
}
/**
* Update the priority value of element e.
*
* It's implemented as first remove e from queue and then add e back. If e is not in queue, simply add it to queue.
*
* @param e element.
* @return true if this queue changed.
* */
public boolean update(final Channel e) {
updateLock.lock();
try {
if (remove(e)) {
return add(e);
} else {
return false;
}
} finally {
updateLock.unlock();
}
}
}