/*
* Copyright 2011 LMAX Ltd.
*
* 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 com.lmax.disruptor;
import com.lmax.disruptor.util.MutableLong;
import com.lmax.disruptor.util.PaddedAtomicLong;
import java.util.concurrent.locks.LockSupport;
import static com.lmax.disruptor.util.Util.getMinimumSequence;
/**
* Strategy to be used when there are multiple publisher threads claiming sequences.
*
* This strategy requires sufficient cores to allow multiple publishers to be concurrently claiming sequences and those
* thread a contented relatively infrequently.
*/
public final class MultiThreadedLowContentionClaimStrategy
implements ClaimStrategy
{
private final int bufferSize;
private final PaddedAtomicLong claimSequence = new PaddedAtomicLong(Sequencer.INITIAL_CURSOR_VALUE);
private final ThreadLocal<MutableLong> minGatingSequenceThreadLocal = new ThreadLocal<MutableLong>()
{
@Override
protected MutableLong initialValue()
{
return new MutableLong(Sequencer.INITIAL_CURSOR_VALUE);
}
};
/**
* Construct a new multi-threaded publisher {@link ClaimStrategy} for a given buffer size.
*
* @param bufferSize for the underlying data structure.
*/
public MultiThreadedLowContentionClaimStrategy(final int bufferSize)
{
this.bufferSize = bufferSize;
}
@Override
public int getBufferSize()
{
return bufferSize;
}
@Override
public long getSequence()
{
return claimSequence.get();
}
@Override
public boolean hasAvailableCapacity(final int availableCapacity, final Sequence[] dependentSequences)
{
final long wrapPoint = (claimSequence.get() + availableCapacity) - bufferSize;
final MutableLong minGatingSequence = minGatingSequenceThreadLocal.get();
if (wrapPoint > minGatingSequence.get())
{
long minSequence = getMinimumSequence(dependentSequences);
minGatingSequence.set(minSequence);
if (wrapPoint > minSequence)
{
return false;
}
}
return true;
}
@Override
public long incrementAndGet(final Sequence[] dependentSequences)
{
final MutableLong minGatingSequence = minGatingSequenceThreadLocal.get();
waitForCapacity(dependentSequences, minGatingSequence);
final long nextSequence = claimSequence.incrementAndGet();
waitForFreeSlotAt(nextSequence, dependentSequences, minGatingSequence);
return nextSequence;
}
@Override
public long incrementAndGet(final int delta, final Sequence[] dependentSequences)
{
final long nextSequence = claimSequence.addAndGet(delta);
waitForFreeSlotAt(nextSequence, dependentSequences, minGatingSequenceThreadLocal.get());
return nextSequence;
}
@Override
public void setSequence(final long sequence, final Sequence[] dependentSequences)
{
claimSequence.lazySet(sequence);
waitForFreeSlotAt(sequence, dependentSequences, minGatingSequenceThreadLocal.get());
}
@Override
public void serialisePublishing(final long sequence, final Sequence cursor, final int batchSize)
{
final long expectedSequence = sequence - batchSize;
while (expectedSequence != cursor.get())
{
// busy spin
}
cursor.set(sequence);
}
private void waitForCapacity(final Sequence[] dependentSequences, final MutableLong minGatingSequence)
{
final long wrapPoint = (claimSequence.get() + 1L) - bufferSize;
if (wrapPoint > minGatingSequence.get())
{
long minSequence;
while (wrapPoint > (minSequence = getMinimumSequence(dependentSequences)))
{
LockSupport.parkNanos(1L);
}
minGatingSequence.set(minSequence);
}
}
private void waitForFreeSlotAt(final long sequence, final Sequence[] dependentSequences, final MutableLong minGatingSequence)
{
final long wrapPoint = sequence - bufferSize;
if (wrapPoint > minGatingSequence.get())
{
long minSequence;
while (wrapPoint > (minSequence = getMinimumSequence(dependentSequences)))
{
LockSupport.parkNanos(1L);
}
minGatingSequence.set(minSequence);
}
}
}