/* * Copyright (c) 2011-2014 Pivotal Software, Inc. * * 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 io.muoncore.transport.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.Environment; import reactor.core.Dispatcher; import reactor.core.dispatch.wait.AgileWaitingStrategy; import reactor.core.dispatch.wait.WaitingMood; import reactor.core.support.Assert; import reactor.core.support.NamedDaemonThreadFactory; import reactor.core.support.Recyclable; import reactor.fn.Consumer; import reactor.jarjar.com.lmax.disruptor.*; import reactor.jarjar.com.lmax.disruptor.dsl.Disruptor; import reactor.jarjar.com.lmax.disruptor.dsl.ProducerType; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Implementation of a {@link reactor.core.Dispatcher} that uses a * {@link RingBuffer} to queue tasks to execute. * * @author Jon Brisbin * @author Stephane Maldini */ public final class RingBufferLocalDispatcher implements Dispatcher, WaitingMood { private final Logger log = LoggerFactory.getLogger(getClass()); private final ExecutorService executor; private final Disruptor<RingBufferTask> disruptor; private final RingBuffer<RingBufferTask> ringBuffer; private final WaitingMood waitingMood; protected final List<Task> tailRecursionPile = new ArrayList<Task>(); protected final int backlog; protected int tailRecurseSeq = -1; protected int tailRecursionPileSize = 0; /** * Creates a new {@code RingBufferDispatcher} with the given {@code name}. * It will use a RingBuffer with 1024 slots, configured with a producer type * of {@link ProducerType#MULTI MULTI} and a {@link BlockingWaitStrategy * blocking wait strategy}. * * @param name * The name of the dispatcher. */ public RingBufferLocalDispatcher(String name) { this(name, 32768, null, ProducerType.MULTI, new AgileWaitingStrategy()); // this(name, DEFAULT_BUFFER_SIZE); } /** * Creates a new {@code RingBufferDispatcher} with the given {@code name} * and {@param bufferSize}, configured with a producer type of * {@link ProducerType#MULTI MULTI} and a {@link BlockingWaitStrategy * blocking wait strategy}. * * @param name * The name of the dispatcher * @param bufferSize * The size to configure the ring buffer with */ public RingBufferLocalDispatcher(String name, int bufferSize) { this(name, bufferSize, null, ProducerType.MULTI, new AgileWaitingStrategy()); } /** * Creates a new {@literal RingBufferDispatcher} with the given {@code name} * . It will use a {@link RingBuffer} with {@code bufferSize} slots, * configured with a producer type of {@link ProducerType#MULTI MULTI} and a * {@link BlockingWaitStrategy blocking wait. A given @param * uncaughtExceptionHandler} will catch anything not handled e.g. by the * owning {@code reactor.bus.EventBus} or {@code reactor.rx.Stream}. * * @param name * The name of the dispatcher * @param bufferSize * The size to configure the ring buffer with * @param uncaughtExceptionHandler * The last resort exception handler */ public RingBufferLocalDispatcher(String name, int bufferSize, final Consumer<Throwable> uncaughtExceptionHandler) { this(name, bufferSize, uncaughtExceptionHandler, ProducerType.MULTI, new BlockingWaitStrategy()); } /** * Creates a new {@literal RingBufferDispatcher} with the given {@code name} * . It will use a {@link RingBuffer} with {@code bufferSize} slots, * configured with the given {@code producerType}, * {@param uncaughtExceptionHandler} and {@code waitStrategy}. A null * {@param uncaughtExceptionHandler} will make this dispatcher logging such * exceptions. * * @param name * The name of the dispatcher * @param bufferSize * The size to configure the ring buffer with * @param producerType * The producer type to configure the ring buffer with * @param waitStrategy * The wait strategy to configure the ring buffer with * @param uncaughtExceptionHandler * The last resort exception handler */ @SuppressWarnings({ "unchecked" }) public RingBufferLocalDispatcher(String name, int bufferSize, final Consumer<Throwable> uncaughtExceptionHandler, ProducerType producerType, WaitStrategy waitStrategy) { this.backlog = bufferSize; expandTailRecursionPile(backlog); if (WaitingMood.class.isAssignableFrom(waitStrategy.getClass())) { this.waitingMood = (WaitingMood) waitStrategy; } else { this.waitingMood = null; } this.executor = Executors.newSingleThreadExecutor(new NamedDaemonThreadFactory(name, getContext())); this.disruptor = new Disruptor<RingBufferTask>(new EventFactory<RingBufferTask>() { @Override public RingBufferTask newInstance() { return new RingBufferTask(); } }, bufferSize, executor, producerType, waitStrategy); this.disruptor.handleExceptionsWith(new ExceptionHandler<Object>() { @Override public void handleEventException(Throwable ex, long sequence, Object event) { handleOnStartException(ex); } @Override public void handleOnStartException(Throwable ex) { if (null != uncaughtExceptionHandler) { uncaughtExceptionHandler.accept(ex); } else { log.error(ex.getMessage(), ex); } } @Override public void handleOnShutdownException(Throwable ex) { handleOnStartException(ex); } }); this.disruptor.handleEventsWith(new EventHandler<RingBufferTask>() { @Override public void onEvent(RingBufferTask task, long sequence, boolean endOfBatch) throws Exception { task.run(); } }); this.ringBuffer = disruptor.start(); } public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) { boolean alive = alive(); shutdown(); try { executor.awaitTermination(timeout, timeUnit); if (alive) { disruptor.shutdown(); } } catch (InterruptedException e) { return false; } return true; } public void shutdown() { executor.shutdown(); disruptor.shutdown(); alive.compareAndSet(true, false); } public void forceShutdown() { executor.shutdownNow(); disruptor.halt(); alive.compareAndSet(true, false); } public long remainingSlots() { return ringBuffer.remainingCapacity(); } @Override public void nervous() { if (waitingMood != null) { execute(new Runnable() { @Override public void run() { waitingMood.nervous(); } }); } } @Override public void calm() { if (waitingMood != null) { execute(new Runnable() { @Override public void run() { waitingMood.calm(); } }); } } protected Task tryAllocateTask() throws InsufficientCapacityException { try { long seqId = ringBuffer.tryNext(); return ringBuffer.get(seqId).setSequenceId(seqId); } catch (reactor.jarjar.com.lmax.disruptor.InsufficientCapacityException e) { throw InsufficientCapacityException.INSTANCE; } } protected Task allocateTask() { long seqId = ringBuffer.next(); return ringBuffer.get(seqId).setSequenceId(seqId); } protected void execute(Task task) { ringBuffer.publish(((RingBufferTask) task).getSequenceId()); } private class RingBufferTask extends SingleThreadTask { private long sequenceId; public long getSequenceId() { return sequenceId; } public RingBufferTask setSequenceId(long sequenceId) { this.sequenceId = sequenceId; return this; } } public boolean supportsOrdering() { return true; } public long backlogSize() { return backlog; } public int getTailRecursionPileSize() { return tailRecursionPileSize; } protected void expandTailRecursionPile(int amount) { int toAdd = amount * 2; for (int i = 0; i < toAdd; i++) { tailRecursionPile.add(new SingleThreadTask()); } this.tailRecursionPileSize += toAdd; } protected Task allocateRecursiveTask() { int next = ++tailRecurseSeq; if (next == tailRecursionPileSize) { expandTailRecursionPile(backlog); } Task elem = tailRecursionPile.get(next); return elem; } protected class SingleThreadTask extends Task { @Override public void run() { route(this); // Process any recursive tasks if (tailRecurseSeq < 0) { return; } int next = -1; while (next < tailRecurseSeq) { route(tailRecursionPile.get(++next)); } // clean up extra tasks next = tailRecurseSeq; int max = backlog * 2; while (next >= max) { tailRecursionPile.remove(next--); } tailRecursionPileSize = max; tailRecurseSeq = -1; } } protected static final int DEFAULT_BUFFER_SIZE = 1024; private final AtomicBoolean alive = new AtomicBoolean(true); public final ClassLoader context = new ClassLoader(Thread.currentThread().getContextClassLoader()) { }; public boolean alive() { return alive.get(); } public boolean awaitAndShutdown() { return awaitAndShutdown(Integer.MAX_VALUE, TimeUnit.SECONDS); } /** * Dispatchers can be traced through a {@code contextClassLoader} to let * producers adapting their dispatching strategy * * @return boolean true if the programs is already run by this dispatcher */ public boolean inContext() { // FIX: Commenting this should fix the errors from muon-clojure // return context == Thread.currentThread().getContextClassLoader(); return false; } protected final ClassLoader getContext() { return context; } public final <E> void tryDispatch(E event, Consumer<E> eventConsumer, Consumer<Throwable> errorConsumer) { Assert.isTrue(alive(), "This Dispatcher has been shut down."); boolean isInContext = inContext(); Task task; if (isInContext) { task = allocateRecursiveTask(); } else { try { task = tryAllocateTask(); } catch (InsufficientCapacityException e) { throw new IllegalStateException("Error in task allocation"); } } task.setData(event).setErrorConsumer(errorConsumer).setEventConsumer(eventConsumer); if (!isInContext) { execute(task); } } public final <E> void dispatch(E event, Consumer<E> eventConsumer, Consumer<Throwable> errorConsumer) { Assert.isTrue(alive(), "This Dispatcher has been shut down."); Assert.isTrue(eventConsumer != null, "The signal consumer has not been passed."); boolean isInContext = inContext(); Task task; if (isInContext) { task = allocateRecursiveTask(); } else { task = allocateTask(); } task.setData(event).setErrorConsumer(errorConsumer).setEventConsumer(eventConsumer); if (!isInContext) { execute(task); } } public void execute(final Runnable command) { dispatch(null, new Consumer<Object>() { @Override public void accept(Object ev) { command.run(); } }, null); } @SuppressWarnings("unchecked") protected static void route(Task task) { try { if (task.eventConsumer == null) return; task.eventConsumer.accept(task.data); } catch (Exception e) { if (task.errorConsumer != null) { task.errorConsumer.accept(e); } else if (Environment.alive()) { Environment.get().routeError(e); } } finally { task.recycle(); } } public String toString() { return getClass().getSimpleName().replaceAll("Dispatcher", ""); } public abstract class Task implements Runnable, Recyclable { protected volatile Object data; @SuppressWarnings("rawtypes") protected volatile Consumer eventConsumer; protected volatile Consumer<Throwable> errorConsumer; public Task setData(Object data) { this.data = data; return this; } public Task setEventConsumer(Consumer<?> eventConsumer) { this.eventConsumer = eventConsumer; return this; } public Task setErrorConsumer(Consumer<Throwable> errorConsumer) { this.errorConsumer = errorConsumer; return this; } @Override public void recycle() { data = null; errorConsumer = null; eventConsumer = null; } } }