/*
* 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.*;
import com.lmax.disruptor.util.Util;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A DSL-style API for setting up the disruptor pattern around a ring buffer.
*
* <p>A simple example of setting up the disruptor with two event handlers that must process events in order:</p>
*
* <pre><code> 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();</code></pre>
*
* @param <T> the type of event used.
*/
public class Disruptor<T>
{
private final RingBuffer<T> ringBuffer;
private final Executor executor;
private final EventProcessorRepository<T> eventProcessorRepository = new EventProcessorRepository<T>();
private final AtomicBoolean started = new AtomicBoolean(false);
private final EventPublisher<T> eventPublisher;
private ExceptionHandler exceptionHandler;
/**
* Create a new Disruptor.
*
* @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.
*/
public Disruptor(final EventFactory<T> eventFactory, final int ringBufferSize, final Executor executor)
{
this(new RingBuffer<T>(eventFactory, ringBufferSize), executor);
}
/**
* Create a new Disruptor.
*
* @param eventFactory the factory to create events in the ring buffer.
* @param executor an {@link Executor} to execute event processors.
* @param claimStrategy the claim strategy to use for the ring buffer.
* @param waitStrategy the wait strategy to use for the ring buffer.
*/
public Disruptor(final EventFactory<T> eventFactory, final Executor executor,
final ClaimStrategy claimStrategy,
final WaitStrategy waitStrategy)
{
this(new RingBuffer<T>(eventFactory, claimStrategy, waitStrategy), executor);
}
private Disruptor(final RingBuffer<T> ringBuffer, final Executor executor)
{
this.ringBuffer = ringBuffer;
this.executor = executor;
eventPublisher = new EventPublisher<T>(ringBuffer);
}
/**
* Set up event handlers to handleEventException events from the ring buffer. These handlers will process events
* as soon as they become available, in parallel.
* <p/>
* <p>This method can be used as the start of a chain. For example if the handler <code>A</code> must
* process events before handler <code>B</code>:</p>
* <p/>
* <pre><code>dw.handleEventsWith(A).then(B);</code></pre>
*
* @param handlers the event handlers that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
@SuppressWarnings("varargs")
public EventHandlerGroup<T> handleEventsWith(final EventHandler<T>... handlers)
{
return createEventProcessors(new EventProcessor[0], handlers);
}
/**
* Set up custom event processors to handleEventException events from the ring buffer. The Disruptor will
* automatically start this processors when {@link #start()} is called.
*
* @param processors the event processors that will process events.
* @return a {@link EventHandlerGroup} that can be used to chain dependencies.
*/
public EventHandlerGroup<T> handleEventsWith(final EventProcessor... processors)
{
for (EventProcessor processor : processors)
{
eventProcessorRepository.add(processor);
}
return new EventHandlerGroup<T>(this, eventProcessorRepository, processors);
}
/**
* 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}.
*/
public void handleExceptionsWith(final ExceptionHandler exceptionHandler)
{
this.exceptionHandler = exceptionHandler;
}
/**
* Override the default exception handler for a specific handler.
* <pre>disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);</pre>
*
* @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<T> eventHandler)
{
return new ExceptionHandlerSetting<T>(eventHandler, eventProcessorRepository);
}
/**
* Create a group of event handlers to be used as a dependency.
* For example if the handler <code>A</code> must process events before handler <code>B</code>:
* <p/>
* <pre><code>dw.after(A).handleEventsWith(B);</code></pre>
*
* @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.
*/
@SuppressWarnings("varargs")
public EventHandlerGroup<T> after(final EventHandler<T>... handlers)
{
EventProcessor[] selectedEventProcessors = new EventProcessor[handlers.length];
for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++)
{
selectedEventProcessors[i] = eventProcessorRepository.getEventProcessorFor(handlers[i]);
}
return new EventHandlerGroup<T>(this, eventProcessorRepository, selectedEventProcessors);
}
/**
* 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 hte specified event processors.
* @see #after(com.lmax.disruptor.EventHandler[])
*/
public EventHandlerGroup<T> after(final EventProcessor... processors)
{
for (EventProcessor processor : processors)
{
eventProcessorRepository.add(processor);
}
return new EventHandlerGroup<T>(this, eventProcessorRepository, processors);
}
/**
* Publish an event to the ring buffer.
*
* @param eventTranslator the translator that will load data into the event.
*/
public void publishEvent(final EventTranslator<T> eventTranslator)
{
eventPublisher.publishEvent(eventTranslator);
}
/**
* 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<T> start()
{
EventProcessor[] gatingProcessors = eventProcessorRepository.getLastEventProcessorsInChain();
ringBuffer.setGatingSequences(Util.getSequencesFor(gatingProcessors));
checkOnlyStartedOnce();
for (EventProcessorInfo<T> eventProcessorInfo : eventProcessorRepository)
{
executor.execute(eventProcessorInfo.getEventProcessor());
}
return ringBuffer;
}
/**
* Calls {@link com.lmax.disruptor.EventProcessor#halt()} on all of the event processors created via this disruptor.
*/
public void halt()
{
for (EventProcessorInfo<?> eventprocessorInfo : eventProcessorRepository)
{
eventprocessorInfo.getEventProcessor().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.
*
* <p>This method will not shutdown the executor, nor will it await the final termination of the
* processor threads.</p>
*/
public void shutdown()
{
while (hasBacklog())
{
// Busy spin
}
halt();
}
/**
* The 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<T> getRingBuffer()
{
return ringBuffer;
}
/**
* 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 <i>handler</i>.
*/
public SequenceBarrier getBarrierFor(final EventHandler<T> handler)
{
return eventProcessorRepository.getBarrierFor(handler);
}
private boolean hasBacklog()
{
final long cursor = ringBuffer.getCursor();
for (EventProcessor consumer : eventProcessorRepository.getLastEventProcessorsInChain())
{
if (cursor != consumer.getSequence().get())
{
return true;
}
}
return false;
}
EventHandlerGroup<T> createEventProcessors(final EventProcessor[] barrierEventProcessors,
final EventHandler<T>[] eventHandlers)
{
checkNotStarted();
final EventProcessor[] createdEventProcessors = new EventProcessor[eventHandlers.length];
final SequenceBarrier barrier = ringBuffer.newBarrier(Util.getSequencesFor(barrierEventProcessors));
for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
{
final EventHandler<T> eventHandler = eventHandlers[i];
final BatchEventProcessor<T> batchEventProcessor = new BatchEventProcessor<T>(ringBuffer, barrier, eventHandler);
if (exceptionHandler != null)
{
batchEventProcessor.setExceptionHandler(exceptionHandler);
}
eventProcessorRepository.add(batchEventProcessor, eventHandler, barrier);
createdEventProcessors[i] = batchEventProcessor;
}
if (createdEventProcessors.length > 0)
{
eventProcessorRepository.unMarkEventProcessorsAsEndOfChain(barrierEventProcessors);
}
return new EventHandlerGroup<T>(this, eventProcessorRepository, createdEventProcessors);
}
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.");
}
}
}