/*
* Bitronix Transaction Manager
*
* Copyright (c) 2011, Juergen Kellerer.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.journal.nio.util;
import bitronix.tm.journal.nio.NioJournalConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
/**
* Implements a bounded blocking queue that maintains a sequence number with all added elements.
* <p/>
* The sequence number is created when putting elements to the queue and is based on an ever increasing
* element sequence.
* <p/>
* This queue maintains the maximum sequence number of the latest addition in a ThreadLocal, allowing
* multiple threads to have different maximum sequence numbers. This feature is used to determine the sequence
* of the latest addition to this queue when a thread needs to wait on elements being processed outside
* of the queue.
*
* @author Juergen_Kellerer, 2011-08-23
* @version 1.0
*/
public final class SequencedBlockingQueue<E> extends ArrayBlockingQueue<SequencedQueueEntry<E>> implements NioJournalConstants {
private static final long serialVersionUID = -6301155749228585459L;
private static final Logger log = LoggerFactory.getLogger(SequencedBlockingQueue.class);
private static final boolean trace = log.isTraceEnabled();
/**
* Helper method that unwraps entries to their wrapped elements.
*
* @param source the source collection to read from.
* @param target the target collection to write the unwrapped content to.
* @param <E> the type of the element that is wrapped.
*/
public static <E> void unwrap(Collection<SequencedQueueEntry<E>> source, Collection<? super E> target) {
if (trace) log.trace("Unwrapping {} sources into target list of size {}", source.size(), target.size());
for (SequencedQueueEntry<E> entry : source) target.add(entry.getElement());
}
private final ReentrantLock lock;
private final AtomicLong enlistedElementSequence = new AtomicLong();
private final ThreadLocal<Long> lastEnlistedElementSequenceNumber = new ThreadLocal<Long>();
/**
* Creates a instance of SequencedBlockingQueue with a capacity of {@link #CONCURRENCY} lock-free puts.
*/
public SequencedBlockingQueue() {
super(CONCURRENCY);
ReentrantLock lock = null;
try {
lock = AccessController.doPrivileged(new PrivilegedAction<ReentrantLock>() {
public ReentrantLock run() {
try {
Field lockField = ArrayBlockingQueue.class.getDeclaredField("lock");
lockField.setAccessible(true);
return (ReentrantLock) lockField.get(SequencedBlockingQueue.this);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
});
} catch (SecurityException e) {
log.info("Access to shared ReentrantLock instance is not granted in the current security context.");
} finally {
if (lock == null) {
log.info("Failed to access shared ReentrantLock instance, will need to acquire a separate lock when putting entries into this queue.");
lock = new ReentrantLock(false);
}
}
this.lock = lock;
}
/**
* Enlist the given element inside the specified queue and updates a thread local with the
* enlisted element's sequence number.
* <p/>
* The method waits for space to become available and does not return before enlisting
* succeeded or the current thread was interrupted.
*
* @param element the element to enlist.
* @return the sequence number that was assigned with the enlisted element.
* @throws InterruptedException in case of the calling thread was interrupted before enlisting succeeded.
*/
public long putElement(E element) throws InterruptedException {
if (trace) log.trace("Putting the element {} to the queue of pending records.", element);
final SequencedQueueEntry<E> entry = new SequencedQueueEntry<E>(element);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
put(entry);
// Note: The sequence number is created when the entry was successfully put into the queue.
// This avoids that getMaxElementNumber() can return a number of a non-enlisted entry.
final long sequenceNumber = enlistedElementSequence.incrementAndGet();
entry.setSequenceNumber(sequenceNumber);
lastEnlistedElementSequenceNumber.set(sequenceNumber);
return sequenceNumber;
} finally {
lock.unlock();
}
}
/**
* Returns the maximum element sequence number added by any thread.
*
* @return the maximum element sequence number added by any thread.
*/
public long getMaxElementSequenceNumber() {
return enlistedElementSequence.get();
}
/**
* Returns the maximum element sequence number of the element added by the calling thread.
* <p/>
* If the thread did not add any elements, the method returns the same result as
* {@link #getMaxElementSequenceNumber()}.
*
* @param clearThreadLocal if true, clears the thread local sequence number to ensure subsequent
* calls will return the maximum number instead.
* @return the maximum element sequence number of the element added by the calling thread.
*/
public long getMaxElementSequenceNumberForCurrentThread(boolean clearThreadLocal) {
Long maxElementSequence = lastEnlistedElementSequenceNumber.get();
if (maxElementSequence == null) {
maxElementSequence = getMaxElementSequenceNumber();
if (trace) {
log.trace("The current thread has no enlisted sequence, using the latest sequence {} " +
"instead to wait on force.", maxElementSequence);
}
} else if (clearThreadLocal) {
// clearing the sequence, subsequent calls should force everything.
lastEnlistedElementSequenceNumber.set(null);
}
return maxElementSequence;
}
/**
* Drains all queued elements to the target collection.
*
* @param target the target collection to add the queued elements to.
* @return the number of elements added to target.
*/
public int drainElementsTo(Collection<? super E> target) {
return drainElementsTo(new LinkedList<SequencedQueueEntry<E>>(), target);
}
/**
* Drains all queued elements to the target collection.
*
* @param entries a collection that takes the entries containing the elements.
* @param target the target collection to add the queued elements to.
* @return the number of elements added to target.
*/
public int drainElementsTo(List<SequencedQueueEntry<E>> entries, Collection<? super E> target) {
try {
return transferElements(entries, target, false, null, null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
/**
* Takes one entry from this queue and drains any further queued elements to the target collection.
* <p/>
* This method blocks until at least one element was added.
*
* @param target the target collection to add the queued elements to.
* @return the number of elements added to target.
* @throws InterruptedException In case of the calling thread was interrupted.
*/
public int takeAndDrainElementsTo(Collection<? super E> target) throws InterruptedException {
return takeAndDrainElementsTo(new LinkedList<SequencedQueueEntry<E>>(), target);
}
/**
* Takes one entry from this queue and drains any further queued elements to the target collection.
* <p/>
* This method blocks until at least one element was added.
*
* @param entries a collection that takes the entries containing the elements.
* @param target the target collection to add the queued elements to.
* @return the number of elements added to target.
* @throws InterruptedException In case of the calling thread was interrupted.
*/
public int takeAndDrainElementsTo(List<SequencedQueueEntry<E>> entries, Collection<? super E> target) throws InterruptedException {
return transferElements(entries, target, true, null, null);
}
/**
* Waits up to maxBlockTime for an entry to get available in the queue and drains any further queued elements to the target collection.
* <p/>
* This method blocks up to maxBlockTime until at least one element was added. If no element was added during maxBlockTime, the
* method returns without adding elements.
*
* @param entries a collection that takes the entries containing the elements.
* @param target the target collection to add the queued elements to.
* @param maxBlockTime the max time to wait for elements to become available.
* @param timeUnit the time unit of maxBlockTime.
* @return the number of elements added to target. 0 if a timeout occurred.
* @throws InterruptedException In case of the calling thread was interrupted.
*/
public int pollAndDrainElementsTo(List<SequencedQueueEntry<E>> entries, Collection<? super E> target,
long maxBlockTime, TimeUnit timeUnit) throws InterruptedException {
return transferElements(entries, target, true, maxBlockTime, timeUnit);
}
private int transferElements(final List<SequencedQueueEntry<E>> entries, final Collection<? super E> elements,
final boolean block, final Long maxBlockTime, final TimeUnit timeUnit) throws InterruptedException {
int transferCount = 0;
final int startIndex = entries.size();
if (block) {
final SequencedQueueEntry<E> headEntry;
if (maxBlockTime == null)
headEntry = take();
else
headEntry = poll(maxBlockTime, timeUnit);
if (headEntry == null)
return 0;
else {
entries.add(headEntry);
transferCount++;
}
}
transferCount += drainTo(entries);
if (startIndex == 0)
unwrap(entries, elements);
else
unwrap(entries.subList(startIndex, entries.size()), elements);
return transferCount;
}
}