/* * 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 static com.lmax.disruptor.util.Util.getMinimumSequence; import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.locks.LockSupport; import com.lmax.disruptor.util.MutableLong; import com.lmax.disruptor.util.PaddedAtomicLong; /** * Strategy to be used when there are multiple publisher threads claiming sequences. * * This strategy is reasonably forgiving when the multiple publisher threads are highly contended or working in an * environment where there is insufficient CPUs to handle multiple publisher threads. It requires 2 CAS operations * for a single publisher, compared to the {@link MultiThreadedLowContentionClaimStrategy} strategy which needs only a single * CAS and a lazySet per publication. */ public final class MultiThreadedClaimStrategy implements ClaimStrategy { private static final int RETRIES = 1000; private final int bufferSize; private final PaddedAtomicLong claimSequence = new PaddedAtomicLong(Sequencer.INITIAL_CURSOR_VALUE); private final AtomicLongArray pendingPublication; private final int pendingMask; 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. * @param pendingBufferSize number of item that can be pending for serialisation */ public MultiThreadedClaimStrategy(final int bufferSize, final int pendingBufferSize) { if (Integer.bitCount(pendingBufferSize) != 1) { throw new IllegalArgumentException("pendingBufferSize must be a power of 2, was: " + pendingBufferSize); } this.bufferSize = bufferSize; this.pendingPublication = new AtomicLongArray(pendingBufferSize); this.pendingMask = pendingBufferSize - 1; } /** * Construct a new multi-threaded publisher {@link ClaimStrategy} for a given buffer size. * * @param bufferSize for the underlying data structure. */ public MultiThreadedClaimStrategy(final int bufferSize) { this(bufferSize, 1024); } @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) { int counter = RETRIES; while (sequence - cursor.get() > pendingPublication.length()) { if (--counter == 0) { Thread.yield(); counter = RETRIES; } } long expectedSequence = sequence - batchSize; for (long pendingSequence = expectedSequence + 1; pendingSequence <= sequence; pendingSequence++) { pendingPublication.set((int) pendingSequence & pendingMask, pendingSequence); } long cursorSequence = cursor.get(); if (cursorSequence >= sequence) { return; } expectedSequence = Math.max(expectedSequence, cursorSequence); long nextSequence = expectedSequence + 1; while (cursor.compareAndSet(expectedSequence, nextSequence)) { expectedSequence = nextSequence; nextSequence++; if (pendingPublication.get((int) nextSequence & pendingMask) != nextSequence) { break; } } } 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); } } }