/* * 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 java.util.concurrent.TimeUnit; import static com.lmax.disruptor.Util.ceilingNextPowerOfTwo; import static com.lmax.disruptor.Util.getMinimumSequence; /** * Ring based store of reusable entries containing the data representing an {@link AbstractEntry} being exchanged between producers and consumers. * * @param <T> AbstractEntry implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class RingBuffer<T extends AbstractEntry> { /** Set to -1 as sequence starting point */ public static final long INITIAL_CURSOR_VALUE = -1L; public long p1, p2, p3, p4, p5, p6, p7; // cache line padding private volatile long cursor = INITIAL_CURSOR_VALUE; public long p8, p9, p10, p11, p12, p13, p14; // cache line padding private final AbstractEntry[] entries; private final int ringModMask; private final ClaimStrategy claimStrategy; private final ClaimStrategy.Option claimStrategyOption; private final WaitStrategy waitStrategy; /** * Construct a RingBuffer with the full option set. * * @param entryFactory to create {@link AbstractEntry}s for filling the RingBuffer * @param size of the RingBuffer that will be rounded up to the next power of 2 * @param claimStrategyOption threading strategy for producers claiming {@link AbstractEntry}s in the ring. * @param waitStrategyOption waiting strategy employed by consumers waiting on {@link AbstractEntry}s becoming available. */ public RingBuffer(final EntryFactory<T> entryFactory, final int size, final ClaimStrategy.Option claimStrategyOption, final WaitStrategy.Option waitStrategyOption) { int sizeAsPowerOfTwo = ceilingNextPowerOfTwo(size); ringModMask = sizeAsPowerOfTwo - 1; entries = new AbstractEntry[sizeAsPowerOfTwo]; this.claimStrategyOption = claimStrategyOption; claimStrategy = claimStrategyOption.newInstance(); waitStrategy = waitStrategyOption.newInstance(); fill(entryFactory); } /** * Construct a RingBuffer with default strategies of: * {@link ClaimStrategy.Option#MULTI_THREADED} and {@link WaitStrategy.Option#BLOCKING} * * @param entryFactory to create {@link AbstractEntry}s for filling the RingBuffer * @param size of the RingBuffer that will be rounded up to the next power of 2 */ public RingBuffer(final EntryFactory<T> entryFactory, final int size) { this(entryFactory, size, ClaimStrategy.Option.MULTI_THREADED, WaitStrategy.Option.BLOCKING); } /** * Create a {@link ConsumerBarrier} that gates on the RingBuffer and a list of {@link Consumer}s * * @param consumersToTrack this barrier will track * @return the barrier gated as required */ public ConsumerBarrier<T> createConsumerBarrier(final Consumer... consumersToTrack) { return new ConsumerTrackingConsumerBarrier(consumersToTrack); } /** * Create a {@link ProducerBarrier} on this RingBuffer that tracks dependent {@link Consumer}s. * * @param consumersToTrack to be tracked to prevent wrapping. * @return a {@link ProducerBarrier} with the above configuration. */ public ProducerBarrier<T> createProducerBarrier(final Consumer... consumersToTrack) { return new ConsumerTrackingProducerBarrier(consumersToTrack); } /** * Create a {@link ForceFillProducerBarrier} on this RingBuffer that tracks dependent {@link Consumer}s. * This barrier is to be used for filling a RingBuffer when no other producers exist. * * @param consumersToTrack to be tracked to prevent wrapping. * @return a {@link ForceFillProducerBarrier} with the above configuration. */ public ForceFillProducerBarrier<T> createForceFillProducerBarrier(final Consumer... consumersToTrack) { return new ForceFillConsumerTrackingProducerBarrier(consumersToTrack); } /** * The capacity of the RingBuffer to hold entries. * * @return the size of the RingBuffer. */ public int getCapacity() { return entries.length; } /** * Get the current sequence that producers have committed to the RingBuffer. * * @return the current committed sequence. */ public long getCursor() { return cursor; } /** * Get the {@link AbstractEntry} for a given sequence in the RingBuffer. * * @param sequence for the {@link AbstractEntry} * @return {@link AbstractEntry} for the sequence */ @SuppressWarnings("unchecked") public T getEntry(final long sequence) { return (T)entries[(int)sequence & ringModMask]; } private void fill(final EntryFactory<T> entryFactory) { for (int i = 0; i < entries.length; i++) { entries[i] = entryFactory.create(); } } /** * ConsumerBarrier handed out for gating consumers of the RingBuffer and dependent {@link Consumer}(s) */ private final class ConsumerTrackingConsumerBarrier implements ConsumerBarrier<T> { private volatile boolean alerted = false; private final Consumer[] consumers; public ConsumerTrackingConsumerBarrier(final Consumer... consumers) { this.consumers = consumers; } @Override @SuppressWarnings("unchecked") public T getEntry(final long sequence) { return (T)entries[(int)sequence & ringModMask]; } @Override public long waitFor(final long sequence) throws AlertException, InterruptedException { return waitStrategy.waitFor(consumers, RingBuffer.this, this, sequence); } @Override public long waitFor(final long sequence, final long timeout, final TimeUnit units) throws AlertException, InterruptedException { return waitStrategy.waitFor(consumers, RingBuffer.this, this, sequence, timeout, units); } @Override public long getCursor() { return cursor; } @Override public boolean isAlerted() { return alerted; } @Override public void alert() { alerted = true; waitStrategy.signalAll(); } @Override public void clearAlert() { alerted = false; } } /** * {@link ProducerBarrier} that tracks multiple {@link Consumer}s when trying to claim * an {@link AbstractEntry} in the {@link RingBuffer}. */ private final class ConsumerTrackingProducerBarrier implements ProducerBarrier<T> { private final Consumer[] consumers; private long lastConsumerMinimum = RingBuffer.INITIAL_CURSOR_VALUE; public ConsumerTrackingProducerBarrier(final Consumer... consumers) { if (0 == consumers.length) { throw new IllegalArgumentException("There must be at least one Consumer to track for preventing ring wrap"); } this.consumers = consumers; } @Override @SuppressWarnings("unchecked") public T nextEntry() { final long sequence = claimStrategy.incrementAndGet(); ensureConsumersAreInRange(sequence); AbstractEntry entry = entries[(int)sequence & ringModMask]; entry.setSequence(sequence); return (T)entry; } @Override public void commit(final T entry) { commit(entry.getSequence(), 1); } @Override public SequenceBatch nextEntries(final SequenceBatch sequenceBatch) { final long sequence = claimStrategy.incrementAndGet(sequenceBatch.getSize()); sequenceBatch.setEnd(sequence); ensureConsumersAreInRange(sequence); for (long i = sequenceBatch.getStart(), end = sequenceBatch.getEnd(); i <= end; i++) { AbstractEntry entry = entries[(int)i & ringModMask]; entry.setSequence(i); } return sequenceBatch; } @Override public void commit(final SequenceBatch sequenceBatch) { commit(sequenceBatch.getEnd(), sequenceBatch.getSize()); } @Override @SuppressWarnings("unchecked") public T getEntry(final long sequence) { return (T)entries[(int)sequence & ringModMask]; } @Override public long getCursor() { return cursor; } private void ensureConsumersAreInRange(final long sequence) { final long wrapPoint = sequence - entries.length; while (wrapPoint > lastConsumerMinimum && wrapPoint > (lastConsumerMinimum = getMinimumSequence(consumers))) { Thread.yield(); } } private void commit(final long sequence, final long batchSize) { if (ClaimStrategy.Option.MULTI_THREADED == claimStrategyOption) { final long expectedSequence = sequence - batchSize; int counter = 1000; while (expectedSequence != cursor) { if (0 == --counter) { counter = 1000; Thread.yield(); } } } cursor = sequence; waitStrategy.signalAll(); } } /** * {@link ForceFillProducerBarrier} that tracks multiple {@link Consumer}s when trying to claim * a {@link AbstractEntry} in the {@link RingBuffer}. */ private final class ForceFillConsumerTrackingProducerBarrier implements ForceFillProducerBarrier<T> { private final Consumer[] consumers; private long lastConsumerMinimum = RingBuffer.INITIAL_CURSOR_VALUE; public ForceFillConsumerTrackingProducerBarrier(final Consumer... consumers) { if (0 == consumers.length) { throw new IllegalArgumentException("There must be at least one Consumer to track for preventing ring wrap"); } this.consumers = consumers; } @Override @SuppressWarnings("unchecked") public T claimEntry(final long sequence) { ensureConsumersAreInRange(sequence); AbstractEntry entry = entries[(int)sequence & ringModMask]; entry.setSequence(sequence); return (T)entry; } @Override public void commit(final T entry) { long sequence = entry.getSequence(); claimStrategy.setSequence(sequence); cursor = sequence; waitStrategy.signalAll(); } @Override public long getCursor() { return cursor; } private void ensureConsumersAreInRange(final long sequence) { final long wrapPoint = sequence - entries.length; while (wrapPoint > lastConsumerMinimum && wrapPoint > (lastConsumerMinimum = getMinimumSequence(consumers))) { Thread.yield(); } } } }