/* * 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 java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static com.lmax.disruptor.AlertException.ALERT_EXCEPTION; import static com.lmax.disruptor.Util.getMinimumSequence; /** * Strategy employed for making {@link Consumer}s wait on a {@link RingBuffer}. */ public interface WaitStrategy { /** * Wait for the given sequence to be available for consumption in a {@link RingBuffer} * * @param consumers further back the chain that must advance first * @param ringBuffer on which to wait. * @param barrier the consumer is waiting on. * @param sequence to be waited on. * @return the sequence that is available which may be greater than the requested sequence. * @throws AlertException if the status of the Disruptor has changed. * @throws InterruptedException if the thread is interrupted. */ long waitFor(Consumer[] consumers, RingBuffer ringBuffer, ConsumerBarrier barrier, long sequence) throws AlertException, InterruptedException; /** * Wait for the given sequence to be available for consumption in a {@link RingBuffer} with a timeout specified. * * @param consumers further back the chain that must advance first * @param ringBuffer on which to wait. * @param barrier the consumer is waiting on. * @param sequence to be waited on. * @param timeout value to abort after. * @param units of the timeout value. * @return the sequence that is available which may be greater than the requested sequence. * @throws AlertException if the status of the Disruptor has changed. * @throws InterruptedException if the thread is interrupted. */ long waitFor(Consumer[] consumers, RingBuffer ringBuffer, ConsumerBarrier barrier, long sequence, long timeout, TimeUnit units) throws AlertException, InterruptedException; /** * Signal those waiting that the {@link RingBuffer} cursor has advanced. */ void signalAll(); /** * Strategy options which are available to those waiting on a {@link RingBuffer} */ enum Option { /** This strategy uses a condition variable inside a lock to block the consumer which saves CPU resource as the expense of lock contention. */ BLOCKING { @Override public WaitStrategy newInstance() { return new BlockingStrategy(); } }, /** This strategy calls Thread.yield() in a loop as a waiting strategy which reduces contention at the expense of CPU resource. */ YIELDING { @Override public WaitStrategy newInstance() { return new YieldingStrategy(); } }, /** This strategy call spins in a loop as a waiting strategy which is lowest and most consistent latency but ties up a CPU */ BUSY_SPIN { @Override public WaitStrategy newInstance() { return new BusySpinStrategy(); } }; /** * Used by the {@link com.lmax.disruptor.RingBuffer} as a polymorphic constructor. * * @return a new instance of the WaitStrategy */ abstract WaitStrategy newInstance(); } /** * Blocking strategy that uses a lock and condition variable for {@link Consumer}s waiting on a barrier. * * This strategy should be used when performance and low-latency are not as important as CPU resource. */ static final class BlockingStrategy implements WaitStrategy { private final Lock lock = new ReentrantLock(); private final Condition consumerNotifyCondition = lock.newCondition(); @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence) throws AlertException, InterruptedException { long availableSequence; if ((availableSequence = ringBuffer.getCursor()) < sequence) { lock.lock(); try { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } consumerNotifyCondition.await(); } } finally { lock.unlock(); } } if (0 != consumers.length) { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } } } return availableSequence; } @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence, final long timeout, final TimeUnit units) throws AlertException, InterruptedException { long availableSequence; if ((availableSequence = ringBuffer.getCursor()) < sequence) { lock.lock(); try { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } if (!consumerNotifyCondition.await(timeout, units)) { break; } } } finally { lock.unlock(); } } if (0 != consumers.length) { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } } } return availableSequence; } @Override public void signalAll() { lock.lock(); try { consumerNotifyCondition.signalAll(); } finally { lock.unlock(); } } } /** * Yielding strategy that uses a Thread.yield() for {@link Consumer}s waiting on a barrier. * * This strategy is a good compromise between performance and CPU resource. */ static final class YieldingStrategy implements WaitStrategy { @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence) throws AlertException, InterruptedException { long availableSequence; if (0 == consumers.length) { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } Thread.yield(); } } else { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } Thread.yield(); } } return availableSequence; } @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence, final long timeout, final TimeUnit units) throws AlertException, InterruptedException { final long timeoutMs = units.convert(timeout, TimeUnit.MILLISECONDS); final long currentTime = System.currentTimeMillis(); long availableSequence; if (0 == consumers.length) { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } Thread.yield(); if (timeoutMs < (System.currentTimeMillis() - currentTime)) { break; } } } else { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } Thread.yield(); if (timeoutMs < (System.currentTimeMillis() - currentTime)) { break; } } } return availableSequence; } @Override public void signalAll() { } } /** * Busy Spin strategy that uses a busy spin loop for {@link Consumer}s waiting on a barrier. * * This strategy will use CPU resource to avoid syscalls which can introduce latency jitter. It is best * used when threads can be bound to specific CPU cores. */ static final class BusySpinStrategy implements WaitStrategy { @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence) throws AlertException, InterruptedException { long availableSequence; if (0 == consumers.length) { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } } } else { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } } } return availableSequence; } @Override public long waitFor(final Consumer[] consumers, final RingBuffer ringBuffer, final ConsumerBarrier barrier, final long sequence, final long timeout, final TimeUnit units) throws AlertException, InterruptedException { final long timeoutMs = units.convert(timeout, TimeUnit.MILLISECONDS); final long currentTime = System.currentTimeMillis(); long availableSequence; if (0 == consumers.length) { while ((availableSequence = ringBuffer.getCursor()) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } if (timeoutMs < (System.currentTimeMillis() - currentTime)) { break; } } } else { while ((availableSequence = getMinimumSequence(consumers)) < sequence) { if (barrier.isAlerted()) { throw ALERT_EXCEPTION; } if (timeoutMs < (System.currentTimeMillis() - currentTime)) { break; } } } return availableSequence; } @Override public void signalAll() { } } }