/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.common.collection.ring; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; import org.modeshape.common.collection.ring.GarbageCollectingConsumer.Collectable; import org.modeshape.common.util.CheckArg; /** * A single-threaded cursor for a ring buffer that ensures it does not pass the slowest {@link Pointer} that is consuming entries. * If the cursor needs to advance but cannot due to a slow {@link Pointer}, then the supplied {@link WaitStrategy strategy} will * be used. As new positions are {@link #publish(long) published}, the supplied {@link WaitStrategy} (used by consumers waiting * for this cursor to advance) will be {@link WaitStrategy#signalAllWhenBlocking() signalled}. * * @author Randall Hauch (rhauch@redhat.com) */ public class SingleProducerCursor implements Cursor { private static final AtomicReferenceFieldUpdater<SingleProducerCursor, Pointer[]> STAY_BEHIND_UPDATER = AtomicReferenceFieldUpdater.newUpdater(SingleProducerCursor.class, Pointer[].class, "stayBehinds"); private final int bufferSize; protected final Pointer current = new Pointer(Pointer.INITIAL_VALUE); protected final WaitStrategy waitStrategy; private long nextPosition = Pointer.INITIAL_VALUE; private long slowestConsumerPosition = Pointer.INITIAL_VALUE; protected volatile long finalPosition = Long.MAX_VALUE; protected volatile Pointer[] stayBehinds = new Pointer[0]; public SingleProducerCursor( int bufferSize, WaitStrategy waitStrategy ) { CheckArg.isPositive(bufferSize, "cursor.getBufferSize()"); CheckArg.isPowerOfTwo(bufferSize, "cursor.getBufferSize()"); this.bufferSize = bufferSize; this.waitStrategy = waitStrategy; } @Override public long getCurrent() { return nextPosition; } @Override public int getBufferSize() { return bufferSize; } @Override public long claim() { return claimUpTo(1); } @Override public long claim( int number ) { return claimUpTo(number); } /** * Claim up to the supplied number of positions. * * @param number the maximum number of positions to claim for writing; must be positive * @return the highest position that were claimed */ protected long claimUpTo( int number ) { assert number > 0; long nextPosition = this.nextPosition; long maxPosition = nextPosition + number; long wrapPoint = maxPosition - bufferSize; long cachedSlowestConsumerPosition = this.slowestConsumerPosition; if (wrapPoint > cachedSlowestConsumerPosition || cachedSlowestConsumerPosition > nextPosition) { long minPosition; while (wrapPoint > (minPosition = positionOfSlowestPointer(nextPosition))) { // This takes on the order of tens of nanoseconds, so it's a useful activity to pause a bit. LockSupport.parkNanos(1L); waitStrategy.signalAllWhenBlocking(); } this.slowestConsumerPosition = minPosition; } this.nextPosition = maxPosition; return maxPosition; } protected long positionOfSlowestPointer( long minimumPosition ) { return Pointers.getMinimum(stayBehinds, minimumPosition); } protected long positionOfSlowestConsumer() { return slowestConsumerPosition; } @Override public boolean publish( long position ) { if (finalPosition != Long.MAX_VALUE) return false; current.set(position); waitStrategy.signalAllWhenBlocking(); return true; } @Override public long getHighestPublishedPosition( long lowerPosition, long upperPosition ) { // There is only one producer, so we know that all supplied entries are okay return upperPosition; } @Override public PointerBarrier newBarrier() { return new PointerBarrier() { private boolean closed = false; @Override public long waitFor( long position ) throws InterruptedException, TimeoutException { if (position > finalPosition) { // The consumer is waiting for a position beyond the final position, meaning we're done ... return -1; } long availableSequence = waitStrategy.waitFor(position, current, current, this); if (availableSequence < position) { return availableSequence; } return getHighestPublishedPosition(position, availableSequence); } @Override public boolean isComplete() { return closed || SingleProducerCursor.this.isComplete(); } @Override public void close() { this.closed = true; } }; } @Override public void signalConsumers() { waitStrategy.signalAllWhenBlocking(); } @Override public void complete() { finalPosition = current.get(); waitStrategy.signalAllWhenBlocking(); } @Override public boolean isComplete() { return finalPosition == current.get(); } @Override public Pointer newPointer() { Pointer result = new Pointer(current.get()); this.stayBehind(result); return result; } @Override public void stayBehind( Pointer... pointers ) { Pointers.add(this, STAY_BEHIND_UPDATER, this, pointers); } @Override public boolean ignore( Pointer pointer ) { return Pointers.remove(this, STAY_BEHIND_UPDATER, pointer); } @Override public GarbageCollectingConsumer createGarbageCollectingConsumer( Collectable collectable ) { return new GarbageCollectingConsumer(this, current, waitStrategy, collectable); } }