com.lmax.disruptor.dsl.Disruptor Maven / Gradle / Ivy
/*
* 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.dsl;
import com.lmax.disruptor.BatchEventProcessor;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.EventProcessor;
import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.EventTranslatorThreeArg;
import com.lmax.disruptor.EventTranslatorTwoArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.Sequence;
import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.TimeoutException;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.WorkerPool;
import com.lmax.disruptor.util.Util;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A DSL-style API for setting up the disruptor pattern around a ring buffer
* (aka the Builder pattern).
*
* A simple example of setting up the disruptor with two event handlers that
* must process events in order:
*
* Disruptor<MyEvent> disruptor = new Disruptor<MyEvent>(MyEvent.FACTORY, 32, Executors.newCachedThreadPool());
* EventHandler<MyEvent> handler1 = new EventHandler<MyEvent>() { ... };
* EventHandler<MyEvent> handler2 = new EventHandler<MyEvent>() { ... };
* disruptor.handleEventsWith(handler1);
* disruptor.after(handler1).handleEventsWith(handler2);
*
* RingBuffer ringBuffer = disruptor.start();
*
*
* @param the type of event used.
*/
public class Disruptor
{
private final RingBuffer ringBuffer;
private final Executor executor;
private final ConsumerRepository consumerRepository = new ConsumerRepository<>();
private final AtomicBoolean started = new AtomicBoolean(false);
private ExceptionHandler super T> exceptionHandler = new ExceptionHandlerWrapper<>();
/**
* Create a new Disruptor. Will default to {@link com.lmax.disruptor.BlockingWaitStrategy} and
* {@link ProducerType}.MULTI
*
* @deprecated Use a {@link ThreadFactory} instead of an {@link Executor} as a the ThreadFactory
* is able to report errors when it is unable to construct a thread to run a producer.
*
* @param eventFactory the factory to create events in the ring buffer.
* @param ringBufferSize the size of the ring buffer.
* @param executor an {@link Executor} to execute event processors.
*/
@Deprecated
public Disruptor(final EventFactory eventFactory, final int ringBufferSize, final Executor executor)
{
this(RingBuffer.createMultiProducer(eventFactory, ringBufferSize), executor);
}
/**
* Create a new Disruptor.
*
* @deprecated Use a {@link ThreadFactory} instead of an {@link Executor} as a the ThreadFactory
* is able to report errors when it is unable to construct a thread to run a producer.
*
* @param eventFactory the factory to create events in the ring buffer.
* @param ringBufferSize the size of the ring buffer, must be power of 2.
* @param executor an {@link Executor} to execute event processors.
* @param producerType the claim strategy to use for the ring buffer.
* @param waitStrategy the wait strategy to use for the ring buffer.
*/
@Deprecated
public Disruptor(
final EventFactory eventFactory,
final int ringBufferSize,
final Executor executor,
final ProducerType producerType,
final WaitStrategy waitStrategy)
{
this(RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), executor);
}
/**
* Create a new Disruptor. Will default to {@link com.lmax.disruptor.BlockingWaitStrategy} and
* {@link ProducerType}.MULTI
*
* @param eventFactory the factory to create events in the ring buffer.
* @param ringBufferSize the size of the ring buffer.
* @param threadFactory a {@link ThreadFactory} to create threads to for processors.
*/
public Disruptor(final EventFactory eventFactory, final int ringBufferSize, final ThreadFactory threadFactory)
{
this(RingBuffer.createMultiProducer(eventFactory, ringBufferSize), new BasicExecutor(threadFactory));
}
/**
* Create a new Disruptor.
*
* @param eventFactory the factory to create events in the ring buffer.
* @param ringBufferSize the size of the ring buffer, must be power of 2.
* @param threadFactory a {@link ThreadFactory} to create threads for processors.
* @param producerType the claim strategy to use for the ring buffer.
* @param waitStrategy the wait strategy to use for the ring buffer.
*/
public Disruptor(
final EventFactory eventFactory,
final int ringBufferSize,
final ThreadFactory threadFactory,
final ProducerType producerType,
final WaitStrategy waitStrategy)
{
this(
RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy),
new BasicExecutor(threadFactory));
}
/**
* Private constructor helper
*/
private Disruptor(final RingBuffer ringBuffer, final Executor executor)
{
this.ringBuffer = ringBuffer;
this.executor = executor;
}
/**
* Set up event handlers to handle events from the ring buffer. These handlers will process events
* as soon as they become available, in parallel.
*
* This method can be used as the start of a chain. For example if the handler A
must
* process events before handler B
:
* dw.handleEventsWith(A).then(B);
*
* This call is additive, but generally should only be called once when setting up the Disruptor instance
*
* @param handlers the event handlers that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
@SuppressWarnings("varargs")
@SafeVarargs
public final EventHandlerGroup handleEventsWith(final EventHandler super T>... handlers)
{
return createEventProcessors(new Sequence[0], handlers);
}
/**
* Set up custom event processors to handle events from the ring buffer. The Disruptor will
* automatically start these processors when {@link #start()} is called.
*
* This method can be used as the start of a chain. For example if the handler A
must
* process events before handler B
:
* dw.handleEventsWith(A).then(B);
*
* Since this is the start of the chain, the processor factories will always be passed an empty Sequence
* array, so the factory isn't necessary in this case. This method is provided for consistency with
* {@link EventHandlerGroup#handleEventsWith(EventProcessorFactory...)} and {@link EventHandlerGroup#then(EventProcessorFactory...)}
* which do have barrier sequences to provide.
*
* This call is additive, but generally should only be called once when setting up the Disruptor instance
*
* @param eventProcessorFactories the event processor factories to use to create the event processors that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
@SafeVarargs
public final EventHandlerGroup handleEventsWith(final EventProcessorFactory... eventProcessorFactories)
{
final Sequence[] barrierSequences = new Sequence[0];
return createEventProcessors(barrierSequences, eventProcessorFactories);
}
/**
* Set up custom event processors to handle events from the ring buffer. The Disruptor will
* automatically start this processors when {@link #start()} is called.
*
* This method can be used as the start of a chain. For example if the processor A
must
* process events before handler B
:
* dw.handleEventsWith(A).then(B);
*
* @param processors the event processors that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
public EventHandlerGroup handleEventsWith(final EventProcessor... processors)
{
for (final EventProcessor processor : processors)
{
consumerRepository.add(processor);
}
final Sequence[] sequences = new Sequence[processors.length];
for (int i = 0; i < processors.length; i++)
{
sequences[i] = processors[i].getSequence();
}
ringBuffer.addGatingSequences(sequences);
return new EventHandlerGroup<>(this, consumerRepository, Util.getSequencesFor(processors));
}
/**
* Set up a {@link WorkerPool} to distribute an event to one of a pool of work handler threads.
* Each event will only be processed by one of the work handlers.
* The Disruptor will automatically start this processors when {@link #start()} is called.
*
* @param workHandlers the work handlers that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
@SafeVarargs
@SuppressWarnings("varargs")
public final EventHandlerGroup handleEventsWithWorkerPool(final WorkHandler... workHandlers)
{
return createWorkerPool(new Sequence[0], workHandlers);
}
/**
* Specify an exception handler to be used for any future event handlers.
*
* Note that only event handlers set up after calling this method will use the exception handler.
*
* @param exceptionHandler the exception handler to use for any future {@link EventProcessor}.
* @deprecated This method only applies to future event handlers. Use setDefaultExceptionHandler instead which applies to existing and new event handlers.
*/
public void handleExceptionsWith(final ExceptionHandler super T> exceptionHandler)
{
this.exceptionHandler = exceptionHandler;
}
/**
* Specify an exception handler to be used for event handlers and worker pools created by this Disruptor.
*
* The exception handler will be used by existing and future event handlers and worker pools created by this Disruptor instance.
*
* @param exceptionHandler the exception handler to use.
*/
@SuppressWarnings("unchecked")
public void setDefaultExceptionHandler(final ExceptionHandler super T> exceptionHandler)
{
checkNotStarted();
if (!(this.exceptionHandler instanceof ExceptionHandlerWrapper))
{
throw new IllegalStateException("setDefaultExceptionHandler can not be used after handleExceptionsWith");
}
((ExceptionHandlerWrapper)this.exceptionHandler).switchTo(exceptionHandler);
}
/**
* Override the default exception handler for a specific handler.
* disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);
*
* @param eventHandler the event handler to set a different exception handler for.
* @return an ExceptionHandlerSetting dsl object - intended to be used by chaining the with method call.
*/
public ExceptionHandlerSetting handleExceptionsFor(final EventHandler eventHandler)
{
return new ExceptionHandlerSetting<>(eventHandler, consumerRepository);
}
/**
* Create a group of event handlers to be used as a dependency.
* For example if the handler A
must process events before handler B
:
*
* dw.after(A).handleEventsWith(B);
*
* @param handlers the event handlers, previously set up with {@link #handleEventsWith(com.lmax.disruptor.EventHandler[])},
* that will form the barrier for subsequent handlers or processors.
* @return an {@link EventHandlerGroup} that can be used to setup a dependency barrier over the specified event handlers.
*/
@SafeVarargs
@SuppressWarnings("varargs")
public final EventHandlerGroup after(final EventHandler... handlers)
{
final Sequence[] sequences = new Sequence[handlers.length];
for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++)
{
sequences[i] = consumerRepository.getSequenceFor(handlers[i]);
}
return new EventHandlerGroup<>(this, consumerRepository, sequences);
}
/**
* Create a group of event processors to be used as a dependency.
*
* @param processors the event processors, previously set up with {@link #handleEventsWith(com.lmax.disruptor.EventProcessor...)},
* that will form the barrier for subsequent handlers or processors.
* @return an {@link EventHandlerGroup} that can be used to setup a {@link SequenceBarrier} over the specified event processors.
* @see #after(com.lmax.disruptor.EventHandler[])
*/
public EventHandlerGroup after(final EventProcessor... processors)
{
for (final EventProcessor processor : processors)
{
consumerRepository.add(processor);
}
return new EventHandlerGroup<>(this, consumerRepository, Util.getSequencesFor(processors));
}
/**
* Publish an event to the ring buffer.
*
* @param eventTranslator the translator that will load data into the event.
*/
public void publishEvent(final EventTranslator eventTranslator)
{
ringBuffer.publishEvent(eventTranslator);
}
/**
* Publish an event to the ring buffer.
*
* @param Class of the user supplied argument.
* @param eventTranslator the translator that will load data into the event.
* @param arg A single argument to load into the event
*/
public void publishEvent(final EventTranslatorOneArg eventTranslator, final A arg)
{
ringBuffer.publishEvent(eventTranslator, arg);
}
/**
* Publish a batch of events to the ring buffer.
*
* @param Class of the user supplied argument.
* @param eventTranslator the translator that will load data into the event.
* @param arg An array single arguments to load into the events. One Per event.
*/
public void publishEvents(final EventTranslatorOneArg eventTranslator, final A[] arg)
{
ringBuffer.publishEvents(eventTranslator, arg);
}
/**
* Publish an event to the ring buffer.
*
* @param Class of the user supplied argument.
* @param Class of the user supplied argument.
* @param eventTranslator the translator that will load data into the event.
* @param arg0 The first argument to load into the event
* @param arg1 The second argument to load into the event
*/
public void publishEvent(final EventTranslatorTwoArg eventTranslator, final A arg0, final B arg1)
{
ringBuffer.publishEvent(eventTranslator, arg0, arg1);
}
/**
* Publish an event to the ring buffer.
*
* @param eventTranslator the translator that will load data into the event.
* @param Class of the user supplied argument.
* @param Class of the user supplied argument.
* @param Class of the user supplied argument.
* @param arg0 The first argument to load into the event
* @param arg1 The second argument to load into the event
* @param arg2 The third argument to load into the event
*/
public void publishEvent(final EventTranslatorThreeArg eventTranslator, final A arg0, final B arg1, final C arg2)
{
ringBuffer.publishEvent(eventTranslator, arg0, arg1, arg2);
}
/**
* Starts the event processors and returns the fully configured ring buffer.
*
* The ring buffer is set up to prevent overwriting any entry that is yet to
* be processed by the slowest event processor.
*
* This method must only be called once after all event processors have been added.
*
* @return the configured ring buffer.
*/
public RingBuffer start()
{
checkOnlyStartedOnce();
for (final ConsumerInfo consumerInfo : consumerRepository)
{
consumerInfo.start(executor);
}
return ringBuffer;
}
/**
* Calls {@link com.lmax.disruptor.EventProcessor#halt()} on all of the event processors created via this disruptor.
*/
public void halt()
{
for (final ConsumerInfo consumerInfo : consumerRepository)
{
consumerInfo.halt();
}
}
/**
* Waits until all events currently in the disruptor have been processed by all event processors
* and then halts the processors. It is critical that publishing to the ring buffer has stopped
* before calling this method, otherwise it may never return.
*
* This method will not shutdown the executor, nor will it await the final termination of the
* processor threads.
*/
public void shutdown()
{
try
{
shutdown(-1, TimeUnit.MILLISECONDS);
}
catch (final TimeoutException e)
{
exceptionHandler.handleOnShutdownException(e);
}
}
/**
* Waits until all events currently in the disruptor have been processed by all event processors
* and then halts the processors.
*
* This method will not shutdown the executor, nor will it await the final termination of the
* processor threads.
*
* @param timeout the amount of time to wait for all events to be processed. -1
will give an infinite timeout
* @param timeUnit the unit the timeOut is specified in
* @throws TimeoutException if a timeout occurs before shutdown completes.
*/
public void shutdown(final long timeout, final TimeUnit timeUnit) throws TimeoutException
{
final long timeOutAt = System.currentTimeMillis() + timeUnit.toMillis(timeout);
while (hasBacklog())
{
if (timeout >= 0 && System.currentTimeMillis() > timeOutAt)
{
throw TimeoutException.INSTANCE;
}
// Busy spin
}
halt();
}
/**
* The {@link RingBuffer} used by this Disruptor. This is useful for creating custom
* event processors if the behaviour of {@link BatchEventProcessor} is not suitable.
*
* @return the ring buffer used by this Disruptor.
*/
public RingBuffer getRingBuffer()
{
return ringBuffer;
}
/**
* Get the value of the cursor indicating the published sequence.
*
* @return value of the cursor for events that have been published.
*/
public long getCursor()
{
return ringBuffer.getCursor();
}
/**
* The capacity of the data structure to hold entries.
*
* @return the size of the RingBuffer.
* @see com.lmax.disruptor.Sequencer#getBufferSize()
*/
public long getBufferSize()
{
return ringBuffer.getBufferSize();
}
/**
* Get the event for a given sequence in the RingBuffer.
*
* @param sequence for the event.
* @return event for the sequence.
* @see RingBuffer#get(long)
*/
public T get(final long sequence)
{
return ringBuffer.get(sequence);
}
/**
* Get the {@link SequenceBarrier} used by a specific handler. Note that the {@link SequenceBarrier}
* may be shared by multiple event handlers.
*
* @param handler the handler to get the barrier for.
* @return the SequenceBarrier used by handler.
*/
public SequenceBarrier getBarrierFor(final EventHandler handler)
{
return consumerRepository.getBarrierFor(handler);
}
/**
* Gets the sequence value for the specified event handlers.
*
* @param b1 eventHandler to get the sequence for.
* @return eventHandler's sequence
*/
public long getSequenceValueFor(final EventHandler b1)
{
return consumerRepository.getSequenceFor(b1).get();
}
/**
* Confirms if all messages have been consumed by all event processors
*/
private boolean hasBacklog()
{
final long cursor = ringBuffer.getCursor();
for (final Sequence consumer : consumerRepository.getLastSequenceInChain(false))
{
if (cursor > consumer.get())
{
return true;
}
}
return false;
}
EventHandlerGroup createEventProcessors(
final Sequence[] barrierSequences,
final EventHandler super T>[] eventHandlers)
{
checkNotStarted();
final Sequence[] processorSequences = new Sequence[eventHandlers.length];
final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);
for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
{
final EventHandler super T> eventHandler = eventHandlers[i];
final BatchEventProcessor batchEventProcessor =
new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);
if (exceptionHandler != null)
{
batchEventProcessor.setExceptionHandler(exceptionHandler);
}
consumerRepository.add(batchEventProcessor, eventHandler, barrier);
processorSequences[i] = batchEventProcessor.getSequence();
}
updateGatingSequencesForNextInChain(barrierSequences, processorSequences);
return new EventHandlerGroup<>(this, consumerRepository, processorSequences);
}
private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences)
{
if (processorSequences.length > 0)
{
ringBuffer.addGatingSequences(processorSequences);
for (final Sequence barrierSequence : barrierSequences)
{
ringBuffer.removeGatingSequence(barrierSequence);
}
consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
}
}
EventHandlerGroup createEventProcessors(
final Sequence[] barrierSequences, final EventProcessorFactory[] processorFactories)
{
final EventProcessor[] eventProcessors = new EventProcessor[processorFactories.length];
for (int i = 0; i < processorFactories.length; i++)
{
eventProcessors[i] = processorFactories[i].createEventProcessor(ringBuffer, barrierSequences);
}
return handleEventsWith(eventProcessors);
}
EventHandlerGroup createWorkerPool(
final Sequence[] barrierSequences, final WorkHandler super T>[] workHandlers)
{
final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences);
final WorkerPool workerPool = new WorkerPool<>(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers);
consumerRepository.add(workerPool, sequenceBarrier);
final Sequence[] workerSequences = workerPool.getWorkerSequences();
updateGatingSequencesForNextInChain(barrierSequences, workerSequences);
return new EventHandlerGroup<>(this, consumerRepository, workerSequences);
}
private void checkNotStarted()
{
if (started.get())
{
throw new IllegalStateException("All event handlers must be added before calling starts.");
}
}
private void checkOnlyStartedOnce()
{
if (!started.compareAndSet(false, true))
{
throw new IllegalStateException("Disruptor.start() must only be called once.");
}
}
@Override
public String toString()
{
return "Disruptor{" +
"ringBuffer=" + ringBuffer +
", started=" + started +
", executor=" + executor +
'}';
}
}